@reactodia/worker-proxy is a TypeScript/JavaScript library for the browser that makes it easy to run background tasks with dedicated Web Workers with automatic connection lifecycle and transparently mapped worker logic via Proxy objects.
Install with:
npm install --save @reactodia/worker-proxycalc.worker.ts module:
import{connectWorker}from'@reactodia/worker-proxy/protocol';// Define a class with methods which return Promise// with results to the worker caller side:classCalculator{constructor(options: {precision: number;}){ ... }add(a: number,b: number): Promise<number>{
...
}}// Setup communication protocol with the worker caller connectWorker(Calculator);using-calc.ts module:
import{refCountedWorker}from'@reactodia/worker-proxy';importtype{Calculator}from'./calc.worker.ts';// Create a ref-counted worker definitionconstcalcWorker=refCountedWorker<typeofCalculator>(()=>newWorker(newURL('./calc.worker.js',import.meta.url)),[{calcPrecision: 2}]);// Get a proxy and connect to the workerconstcalculator=calcWorker.acquire();// Call a worker methodconstsum1=awaitcalculator.add(2,3);constsum2=awaitcalculator.add(10,20);// Release the worker when its no longer neededcalcWorker.release();Although the library does not expose a built-in hook to use workers from a React component to avoid dependencies, it should be trivial to define a simple hook like this:
importtype{RefCountedWorker}from'@reactodia/worker-proxy';functionuseWorker<T>(worker: RefCountedWorker<T>): T{React.useEffect(()=>{worker.acquire();return()=>worker.release();},[worker]);returnworker.getProxy();}Then it can be used in a component this way:
import{refCountedWorker}from'@reactodia/worker-proxy';importtype{Calculator}from'./calc.worker.ts';constCalcWorker=refCountedWorker<typeofCalculator>(()=>newWorker(newURL('./calc.worker.js',import.meta.url)),[{calcPrecision: 2}]);functionMyComponent(){constcalculator=useWorker(CalcWorker);
...
}The library has the following exports:
Creates a ref-counted Web Worker definition which automatically manages the lifecycle of a worker (create, connect, disconnect) and exposes it behind a Proxy object implementing the same interface as the mapped worker.
Parameters:
factory:() => Worker- callback to construct a Worker instance on demandconstructorArgs:ConstructorParameters<T>- constructor arguments to initialize the worker with its connected class constructor before any other calls
Returns: RefCountedWorker<WorkerObject<InstanceType<T>>>
exportinterfaceRefCountedWorker<T>{/** * Returns an lifecycle state of the connected Worker instance. */getState(): 'disconnected'|'blocked'|'ready'|'connecting'|'connected';/** * Returns a cached Proxy instance to transparently call the worker logic. * * Important: calls to the proxy will be resolved only after `acquire()` * method have been called at least once. */getProxy(): T;/** * Increments a ref-count and connects to the worker if needed. */acquire(): T;/** * Decrements a ref-count and disconnects from the worker if ref-count is zero. */release(): void;}Establishes a specific connection protocol between the callee (worker) and external caller (which created a worker via new Worker(...) constructor).
The protocol assumes the worker exposes an RPC-like interface via a class where every public method returns a Promise. This interface is transparently mapped from the caller to the worker via messages.
The communication protocol is defined in terms of messages to be sent with postMessage() and received with onmessage from withing the worker.
The worker expect the first message to be a WorkerCall with method === "constructor" to create an instance of the connected class.
Here are the exported definitions for the message types:
exportinterfaceWorkerCall{readonlytype: 'call';readonlyid: number;readonlymethod: string;readonlyargs: readonlyunknown[];}exportinterfaceWorkerCallSuccess{readonlytype: 'success';readonlyid: number;readonlyresult: unknown;}exportinterfaceWorkerCallError{readonlytype: 'error';readonlyid: number;readonlyerror: unknown;}The library is distributed under MIT license, see LICENSE.