@giancosta86/worker-agent
TypeScript icon, indicating that this package has built-in type declarations

1.1.1 • Public • Published

worker-agent

Typesafe wrapper for worker threads

GitHub CI npm version MIT License

Overview

worker-agent is a TypeScript wrapper designed to simplify interactions with worker threads in NodeJS.

Actually, it implements a fairly common Erlang/Elixir pattern: the sophisticated messaging protocol is hidden behind a variety of abstraction layers - each with different trade-off between simplicity and performance - while still ensuring strongly typed interfaces.

Installation

npm install @giancosta86/worker-agent

or

yarn add @giancosta86/worker-agent

The public API entirely resides in the root package index, so you shouldn't reference specific modules.

Usage

  1. Create a module - an operation module, in worker-agent's parlance - that:

    • can import names from any other module or package

    • should only export your operation - that is, a single function; in TypeScript, the function must be exported via export = myFunction

    The operation function should be:

    • with an arbitrary name

    • with at most one parameter - of arbitrary name and type

    • returning an arbitrary type, including a Promise

    • throwing errors when needed: neither errors nor rejected promises can crash the underlying worker

    For example, to declare a synchronous function:

    function add40(value: number): number {
      return value + 40;
    }
    
    export = add40;

    and to declare an asynchronous function:

    import { someAsyncFunction } from "some-package";
    
    async function specialSum(value: number): Promise<number> {
      const temp = await someAsyncFunction(value);
      return temp + value + 100;
    }
    
    export = specialSum;
  2. Create a new instance of WorkerAgent<TInput, TOutput> or the more expressive PromiseAgent<TInput, TOutput>, passing the path to the operation module: this will start a new worker thread, driven by the agent.

    Furthermore:

    • TInput should be the type of the parameter expected by the operation

    • TOutput should be the operation's return type - or the type T wrapped by its Promise<T>

    For example:

    import { join } from "node:path";
    
    const agent = new PromiseAgent<number, number>(join(__dirname, "my-sum"));

Choosing the right agent type

The actual choice depends on a compromise between simplicity and performance:

  • PromiseAgent is particularly expressive, because of its Promise-based interface

  • WorkerAgent is hyper-minimalist, but it is also more complicated to use

Using PromiseAgent

PromiseAgent is incredibly user-friendly. Just call:

  • its runOperation() method, to obtain a Promise that will either resolve or reject as soon as the worker thread has finished processing the given input

  • its requestExit() method, returning a Promise that will resolve to the worker's exit code.

    Please, note: don't forget to call requestExit() as soon as you have finished using the agent; furthermore, the warning about dangling asynchronous operations - discussed below - applies to this agent, as well.

Using WorkerAgent

WorkerAgent is the original agent implementation - and the more sophisticated as well. In particular, once you have an instance of the agent, you'll need to:

  1. Subscribe to events; to register an event listener, you can call either .on(...) or .once(...) - as usual in NodeJS.

    The available events are:

    • result: the most important event - the one actually returning output values and errors from the operation function called by the worker thread.

      The expected listener is a standard error-first callback - a (err: Error | null, output: TOutput | null) => void function.

      For example:

      agent.on("result", (err, output) => {
        if (err) {
          //Process the error
          return;
        }
      
        //Process the output
      });

      Please, note: the error passed to this callback, when non-null, has a peculiarity: its message is the serialization string - in the form ErrorClass("Message") - of the error that occurred within the worker thread

    • error: sent whenever an non-operational, more serious error occurs within the worker thread - for example, because it couldn't find the operation module. It expects a (error: Error) => void callback.

      Please note: errors thrown by the operation do not trigger error events - instead, they are passed to the error-first callback of the result event

    • exit: sent by the worker thread upon termination, even after an error event. It takes a (exitCode: number) => void callback

  2. Start sending input data - by calling runOperation(input: TInput): every call will send a message to the worker thread queue - passing the given input, ready to be processed by the operation exported by the operation module.

    For example:

    agent.runOperation(90);

    It is a void method - because results - both output values and errors - will be returned later, via the result event.

    You can send multiple operation requests: they are enqueued by the worker thread, ready to be processed one at a time.

  3. Finally, don't forget to call requestExit() to send an exit message to the worker thread's queue.

    Please, note: calling requestExit() enqueues a termination message that will be evaluated as soon as all the previously-enqueued synchronous operations have completed; however, for performance reasons, no check is performed on asynchronous operations - so they will probably remain unfulfilled! Consequently, it is up to you, in your client code, to ensure that all the async operations have settled before calling requestExit().

    A possible solution to the above issue may consist in a counter that is incremented when calling runOperation() and decremented within the result event callback.

Further reference

For additional examples, please consult the test suites in the source code repository.

Dependencies (2)

Dev Dependencies (14)

Package Sidebar

Install

npm i @giancosta86/worker-agent

Weekly Downloads

1

Version

1.1.1

License

MIT

Unpacked Size

30 kB

Total Files

35

Last publish

Collaborators

  • giancosta86