Interface Job<T>

A cancellable asynchronous operation with automatic resource cleanup.

You can add cleanup callbacks to a job via must() or its .must() method. When the job is ended or canceled, the callbacks are (synchronously) run in reverse order -- a bit like a delayed and distributed collection of finally blocks.

Jobs implement the Promise interface (then, catch, and finally) so they can be passed to Promise-using APIs or awaited by async functions. They also implement Yielding, so you can await their results from a start() using yield *. They also have .return() and .throw() methods so you can end a job with a result or error.

Most jobs, however, are not intended to produce results, and are merely canceled (using .end() or .restart()).

Jobs can be created and accessed using start(), detached.start(), makeJob(), and getJob().

interface Job<T> {
    [iterator](): JobIterator<T>;
    [toStringTag]: string;
    asyncCatch(handler: ((this: Job<any>, err: any) => unknown)): this;
    asyncThrow(err: any): this;
    bind<F extends ((...args: any[]) => any)>(fn: F): F;
    catch<TResult>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>)): Promise<T | TResult>;
    connect<T>(src: Stream<T>, sink: Sink<T>, inlet?: Throttle | Inlet): Connection;
    do(action: ((res?: JobResult<T>) => unknown)): this;
    end: (() => void);
    finally(onfinally?: (() => void)): Promise<T>;
    must(cleanup?: OptionalCleanup<T>): this;
    onCancel(cb: (() => unknown)): this;
    onError(cb: ((err: any) => unknown)): this;
    onValue(cb: ((val: T) => unknown)): this;
    release(cleanup: CleanupFn<T>): DisposeFn;
    restart(): this;
    result(): JobResult<T>;
    return(val: T): this;
    run<F extends PlainFunction>(fn: F, ...args: Parameters<F>): ReturnType<F>;
    start<T>(init?: StartFn<T, void> | StartObj<T>): Job<T>;
    start<T, This>(thisArg: This, fn: StartFn<T, This>): Job<T>;
    then<TResult1, TResult2>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>), onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>)): Promise<TResult1 | TResult2>;
    throw(err: any): this;
}

Type Parameters

  • T = any

Hierarchy (view full)

Execution Control

Handling Errors

Obtaining Results

Producing Results

Resource Tracking

Other

Execution Control

  • Wrap a function so this job will be active when it's called.

    Type Parameters

    • F extends ((...args: any[]) => any)

    Parameters

    • fn: F

      The function to wrap

    Returns F

    A function with the same signature(s), but will have this job active when called.

    Remarks

    Note that if the supplied function has any custom properties, they will not be available on the returned function at runtime, even though TypeScript will act as if they are present at compile time. This is because the only way to copy all overloads of a function signature is to copy the exact type (as TypeScript has no way to generically say, "this is a function with all the same overloads, but none of the properties").

  • Start a nested job that will end when the given stream does

    This is basically shorthand for start<void>(job => void src(sink, job, inlet)) -- i.e. a quick way to subscribe to a finite and/or pausable stream.

    Type Parameters

    • T

    Parameters

    • src: Stream<T>

      An event source or signal

    • sink: Sink<T>

      A callback that will receive the events or values

    • Optional inlet: Throttle | Inlet

      Optional - a throttle() to control backpressure

    Returns Connection

    A job that can be aborted to end the subscription, and which will end naturally (with a void return or error) if the stream ends itself.

end: (() => void)

Release all resources held by the job.

Arrange for all cleanup functions and result consumers added to the job (via release, must, do, etc.) be called in the appropriate order. When the call to end() returns, all child jobs will have been notified of their cancellation. (But not all of their cleanups or result consumers may have run yet, in the event that another job's end() is in progress when this method is called.)

If any callbacks throw exceptions, they're converted to unhandled promise rejections (so that all of them will be called, even if one throws an error).

Note: this method is a bound function, so you can pass it as a callback to another job, event source, etc.

Type declaration

    • (): void
    • Returns void

  • Restart this job - works just like .end(), except that the job isn't ended, so cleanup callbacks can be added again and won't be invoked until the next restart or the job is ended. Note that the job's startup code will not be rerun: this just runs an early cleanup and then "uncancels" the job, changing its result() from CancelResult back to undefined. It's up to you to do any needed re-initialization.

    Unlinke .end(), restart() guarantees that all cleanups and result consumers for the target job will have completed running when it returns.

    Returns this

    See

    The restarting wrapper can be used to make a function that runs over and over in the same job, restarting each time.

  • Invoke a function with this job as the active one, so that calling the global must function will add cleanup callbacks to it, getJob will return it, etc. (Note: signal dependency tracking is disabled for the duration of the call.)

    Type Parameters

    Parameters

    • fn: F

      The function to call

    • Rest ...args: Parameters<F>

      The arguments to call it with, if any

    Returns ReturnType<F>

    The result of calling fn(...args)

Handling Errors

  • Set up a callback to receive unhandled errors from child jobs.

    Setting an async-catch handler allows you to create robust parent jobs that log or report errors and restart either a single job or an entire group of them, in the event that a child job malfunctions in a way that's not caught elsewhere.

    Parameters

    • handler: ((this: Job<any>, err: any) => unknown)

      Either an error-receiving callback, or null. If null, asyncThrow()n errors for the job will be passed to the job's throw() method instead. If a callback is given, it's called with this bound to the relevant job instance.

        • (this: Job<any>, err: any): unknown
        • Parameters

          • this: Job<any>
          • err: any

          Returns unknown

    Returns this

  • Informs a job of an unhandled error from one of its children.

    If the job has an .asyncCatch() handler set, it will be called with the error, otherwise the job will end with the supplied error. If the error then isn't handled by a listener on the job, the error will cascade to an asyncThrow on the job's parent, until the detached job and its asyncCatch handler is reached. (Which defaults to creating an unhandled promise rejection.)

    Note: application code should not normally need to call this method directly, as it's automatically invoked on a job's parent if the job fails with no error listeners. (That is, if a job result isn't awaited by anything and has no onError handlers, and the job throws, then the error is automatically asyncThrow()n to the job's parent.)

    Parameters

    • err: any

      The error thrown by the child job

    Returns this

Obtaining Results

  • Invoke a callback with the result of a job. Similar to Job.must(), except that do callbacks run in FIFO order after all Job.must() and Job.release() callbacks are done for the same job.

    These callbacks are used internally to implement promises, and should generally be used when you want to perform actions based on the result of a job. (Whereas Job.must() callbacks are intended to clean up resources used by the job itself, and Job.release() callbacks are used to notify other activities (such as child jobs) that they are being canceled.)

    Parameters

    Returns this

    Remarks

    The .onError(), .onValue(), and .onCancel() provide shortcuts for creating do callbacks that only run under specific end conditions.

  • Invoke a callback if the job ends with an cancellation or .restart().

    This is shorthand for a .do() callback that checks for an error and marks it handled, so it uses the same relative order and runs in the same group as other .do callbacks.

    Parameters

    • cb: (() => unknown)

      A callback that will receive the error

        • (): unknown
        • Returns unknown

    Returns this

  • Invoke a callback if the job ends with an error.

    This is shorthand for a .do() callback that checks for an error and marks it handled, so it uses the same relative order and runs in the same group as other .do callbacks.

    Parameters

    • cb: ((err: any) => unknown)

      A callback that will receive the error

        • (err: any): unknown
        • Parameters

          • err: any

          Returns unknown

    Returns this

  • Invoke a callback if the job ends with a return() value.

    This is shorthand for a .do() callback that checks for a value result, so it uses the same relative order and runs in the same group as other .do callbacks.

    Parameters

    • cb: ((val: T) => unknown)

      A callback that will receive the value

        • (val: T): unknown
        • Parameters

          • val: T

          Returns unknown

    Returns this

Producing Results

  • End the job with a return value, passing a ValueResult to the cleanup callbacks. (Throws an error if the job is already ended or is currently restarting.) Provides the same execution and ordering guarantees as .end().

    Parameters

    • val: T

    Returns this

  • End the job with a thrown error, passing an ErrorResult to the cleanup callbacks. (Throws an error if the job is already ended or is currently restarting.) Provides the same execution and ordering guarantees as .end().

    Note: since this immediately ends the job with an error, it should only be called by the job when it is no longer able to continue. If you want to notify a job about an error in a different job, you may want to use .asyncThrow() instead.

    Parameters

    • err: any

    Returns this

Resource Tracking

  • Add a cleanup callback to be run when the job is ended or restarted. (Non-function values are ignored.) If the job has already ended, the callback will be invoked asynchronously in the next microtask. Cleanup functions are run in LIFO order, after any Job.release() callbacks (including those of the job's children), but before any Job.do() callbacks are run for the same job.

    Generally speaking, this method is used within a job to arrange for used resources to be cleaned up or to undo other state that was only supposed to be active while the job was running.

    Parameters

    Returns this

  • Create a mutual-cleanup link with a resource that might be stopped or terminated in some way before the job ends. (Like a child process, a server connection, etc.)

    If a job uses a lot of such resources, using Job.must callbacks to trigger each one would result in an ever growing number of callbacks (and uncollectable reference to the no-longer-usable resources). So this method lets you remove a cleanup function when it's no longer needed: when the resource is closed or finished, invoking the callback returned by this method will remove the cleanup callback from the job, allowing the resource to be freed before the job ends, without accumulating an endless number of callbacks in the job. (Uneventful also uses this mechanism internally to link child jobs to their parents.)

    In order to ensure that all such "child" jobs, resources, and activities are marked as canceled before any side effects (such as events, callbacks or I/O operations) can occur, Uneventful prioritizes all release callbacks to run before any other callbacks of any kind. Since release callbacks are used for child jobs, this means that the entire job subtree is notified immediately of cancellation, before any other actions are taken. This ensures that no "stray" operations can continue, unaware that their job is canceled.

    This means, however, that release callbacks must do only simple actions that can't result in arbitrary code being synchronously run. (Some safe examples would be setting flags, cancelling event subscriptions, removing things from internal queues, etc.) Synchronously triggering events or other callbacks, however, runs the risk of that code doing things it wouldn't have done if it knew its job were canceled.

    Note that if you still need such actions to happen, your release callback can always add a new Job.must() or Job.do() callback at that point, and the callback will then get done during a later phase of job cleanup, without losing the benefits of the mutual-cleanup process.

    Parameters

    • cleanup: CleanupFn<T>

      A cleanup callback. It will receive a JobResult, and its return value is ignored.

    Returns DisposeFn

    A callback that should be used to remove the passed-in cleanup callback from the job, if the resource is disposed of before the job ends.

Other

[toStringTag]: string
  • Attaches a callback for only the rejection of the Promise.

    Type Parameters

    • TResult = never

    Parameters

    • Optional onrejected: ((reason: any) => TResult | PromiseLike<TResult>)

      The callback to execute when the Promise is rejected.

    Returns Promise<T | TResult>

    A Promise for the completion of the callback.

  • Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The resolved value cannot be modified from the callback.

    Parameters

    • Optional onfinally: (() => void)

      The callback to execute when the Promise is settled (fulfilled or rejected).

        • (): void
        • Returns void

    Returns Promise<T>

    A Promise for the completion of the callback.

  • Attaches callbacks for the resolution and/or rejection of the Promise.

    Type Parameters

    • TResult1 = T
    • TResult2 = never

    Parameters

    Returns Promise<TResult1 | TResult2>

    A Promise for the completion of which ever callback is executed.