Ctx<T>

Ctx helps detach all Handlers that were attached in the goal of acompishing a certain task once the said task is done or aborted.

Get Ctx instance usingEvt.newCtx<T>() or Evt.getCtx(obj)

The only difference between CtxVoid and Ctx<void> is that ctxVoid.done() can be called without argument when ctx<void>.done(result)must be called with an argument (null or undefined).

ctx.done(result?)

Detach, from the Evt instances they are attached to, all Handlers bound to the context.

Once you have called ctx.done() the ctx can't be re-used. If you attach another handler using this ctx, it will be immediately detached.

Calling this method causes the Evt returned by ctx.getEvtDone() to be posted.

To test if ctx.done() have been invoked already you can use:ctx.getEvtDone().postCount !== 0

Returns

ReturnType<ctx.getHandlers()> All the Handlers that were bound to the context. They are now detached, calling ctx.getHandler() just after ctx.done() returns an empty array.

Parameter

  • T for Ctx<T>

  • none for VoidCtx

ctx.abort(error)

Equivalent of ctx.done() to use when the task did not go through.

When a fλ operator returns { "DETACH": ctx, "err": error }, ctx.abort(error) is invoked.

Returns

ReturnType<ctx.done()> (cf ctx.done )

Parameter

Error an error that describes what went wrong.

ctx.evtDoneOrAborted

Tracks when ctx.done or ctx.abort are invoked.

For most use cases, it is more convenient to use ctx.waitFor([timeout])

Returns

  • For VoidCtx an Evt that posts:

    • { handlers: Handler.WithEvt[] } when ctx.done() is called.

    • { error: Error, handlers: Handler.WithEvt[] } when ctx.abort(error) is called.

  • For Ctx<T>, an Evt that post:

    • { result: Result; handlers: Handler.WithEvt[]; } when ctx.done(result) is called.

    • { error: Error, handlers: Handlers.WithEvt[]; } when ctx.abort(error) is called.

Handler.WithEvt<T> is just a type alias for an object that wraps a handler and the Evt it is attached to: { handler: Handler<T, any>, evt: Evt<T> }

Example

import { Evt } from "evt";
import { EventEmitter } from "events";

const ctx= Evt.newCtx();

const evtText = new Evt<string>();
const evtTime = new Evt<number>();

evtText.$attach(
    text=> [ text.length ],
    ctx, 
    count => console.log("1: " + count)
);

evtTime.waitFor(
    time => time < 0,
    ctx,
).then(time=> console.log("2: " +  time));

evtText
    .pipe(ctx)
    .pipe(text => [text.toUpperCase()])
    .attach(upperCaseText=> console.log("3: " + upperCaseText))
    ;

Evt.merge(ctx, [ evtText, evtTime ])
    .attach(textOrTime => console.log("4: " + textOrTime))
    ;

const ee= new EventEmitter();

Evt.from<string>(ctx, ee, "text")
    .attach(text=> console.log("5: " + text))
    ;


evtText.post("foo"); //Prints "1: 3" "3: FOO" "4: foo"
ee.emit("text", "bar"); //Prints "5: bar"

console.log(evtText.getHandlers().length); //Prints "3"
console.log(evtTime.getHandlers().length); //Prints "2"

console.log(ee.listenerCount("text")); //Print "1"

ctx.evtDoneOrAborted.attachOnce(
    ({handlers})=> {

        console.log(
            handlers.filter(({ evt })=> evt === evtText).length +
            " handlers detached from evtText"
        );

        console.log(
            handlers.filter(({ evt })=> evt === evtTime).length +
            " handlers detached from evtTime"
        );

        console.log(
            handlers.length + " handlers detached total"
        );

    }
);

//Prints:
//"3 handlers detached from evtText"
//"2 handlers detached from evtTime"
//"5 handlers detached total"
ctx.done();

console.log(evtText.getHandlers().length); //Prints "0"
console.log(evtTime.getHandlers().length); //Prints "0"
console.log(ee.listenerCount("text")); //Print "0"

evtText.post("foo"); //Prints nothing
ee.emit("text", "bar"); //Prints nothing

Run the example

ctx.waitFor([timeout])

Tracks via a Promise that resolves when ctx.done() or ctx.abort() is invoked.

Returns

Promise<T> (T is the type argument of Ctx<T> ) A promise that resolve when ctx.done([result]) is invoked.

If ctx.abort(error) is invoked before ctx.done() the promise rejects with error.

If timeout was specified the promise rejects if ctx.done() was not invoked within timeout milliseconds. If it happens ctx.abort(timeoutError) is internally invoked timeoutError being an instance of EvtError.Timeout.

Parameter

number Optional, number of milliseconds before the promise reject if it hasn't fulfilled within this delay.

ctx.getHandlers()

Returns

Handler.WithEvt[] The Handlers that are bound to the context alongside with the Evt instance each one is attached to. The Handlers that are bound to the context but no longer attached to an Evt are not listed ( they are usually freed from memory anyway as there should be nor reference left of them as soon as they are detached ).

Example

//NOTE: Equivalent to evt.detach(ctx);
ctx
    .getHandlers()
    .filter(({ evt }))=> evt === evtString)
    .forEach(({ handler })=> handler.detach())
    ;

ctx.evtAttach

Returns

Evt<Handler.WithEvt<any>> An Evt that posts every time a new handler bound to the context is attached.

import { Evt } from "evt";

const evtText = new Evt<string>();

const ctx= Evt.newCtx();

ctx.evtAttach.attach(handler => console.log(handler.timeout));

const timeout = 43;

evtText.attach(timeout, ()=>{}); //Prints "43"

Run the example

ctx.evtDetach

Same as ctx.getEvtAttach() but post when handlers are detached. Note that a handler being detached does not mean that it has been explicitly detached. One-time handlers and handlers that have timed out are automatically detached.

Comprehensive example

Let us consider a practical use case of Ctx. The task is to download a file, we know the size of the file to download, we have an Evt<Uint8Array> that emits chunks of data, we want to accumulate them until we reach the expected file size. Multiple things can go wrong during the download:

  • The user can cancel the download.

  • The download can take too long.

  • Socket may disconnect .

  • The socket may send more data than expected.

Our expected output is a Promise<Uint8Array> that resolves with the downloaded file or reject if anything went wrong.

This is a possible implementation using Ctx<Uint8Array>:

import { Evt, VoidEvt } from "evt";

function downloadFile(
    { fileSize, evtChunk, evtBtnCancelClick, evtSocketError, timeout }: {
        fileSize: number;
        evtChunk: Evt<Uint8Array>;
        evtBtnCancelClick: VoidEvt;
        evtSocketError: Evt<Error>;
        timeout: number;
    }
): Promise<Uint8Array> {

    const ctxDl = Evt.newCtx<Uint8Array>();

    evtSocketError.attachOnce(
        ctxDl,
        error => ctxDl.abort(error)
    );

    evtBtnCancelClick.attachOnce(
        ctxDl,
        () => ctxDl.abort(new Error("Download canceled"))
    );

    evtChunk
        .pipe(ctxDl)
        .pipe([
            (chunk, { byteLength, chunks }) => [{
                "byteLength": byteLength + chunk.length,
                "chunks": [...chunks, chunk]
            }],
            {
                "byteLength": 0,
                "chunks": id<Uint8Array[]>([])
            }
        ])
        .pipe(({ byteLength }) => byteLength >= fileSize)
        .pipe(({ byteLength, chunks }) => byteLength !== fileSize ?
            { "DETACH": ctxDl, "err": new Error("File is larger than expected") } :
            [chunks]
        )
        .pipe(chunks => [concatTypedArray(chunks, fileSize)])
        .attach(rawFile => ctxDl.done(rawFile))
        ;

    return ctxDl.waitFor(timeout);

}

Run the example

Whether the download is successful or not this use of Ctx enforce that there is no left over handlers on the Evt passed as input once the download attempt has completed.

Last updated