Move memory, not copies.
Pass an ArrayBuffer in the transfer list and it changes threads without being duplicated. Results can travel back the same way.
Let easy-web-worker handle everything between the threads. You keep the power of native Web Workers without dealing with all the complexity that usually comes with them.
It handles the plumbing between both sides: message routing, request matching, promises, errors, cancellation, progress, transferable objects, lifecycle management, and worker pools.
export const worker = defineWorker(() => ({
fibonacci,
}));
function fibonacci(n: number): number {
return n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2);
}import type { worker as MathWorker } from './math.worker';
import mathWorkerUrl from './math.worker?worker&url';
const mathWorker = createWorker<typeof MathWorker>(mathWorkerUrl);
// typed as number, computed on another thread
const result = await mathWorker.fibonacci(40); // 102334155Same task, two ways to run it. On the main thread the page freezes until the answer arrives: no scrolling, no typing, no animation. In a Worker you get the same answer and the page never stops.
Open the full exampleThis runs in your browser, against the same package files that are published to npm.
Loading the live example…
Loading the live example…
This is the least you write by hand to get one answer back from a Worker. And that is before errors, cancellation, progress, types, or a second method.
See what replaces itconst fibonacci = (n) => (n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2));
self.onmessage = ({ data: { id, n } }) => {
self.postMessage({ id, result: fibonacci(n) });
};const worker = new Worker(new URL('./fibonacci.worker.js', import.meta.url));
const pending = new Map();
let nextId = 0;
worker.onmessage = ({ data: { id, result } }) => {
pending.get(id)(result);
pending.delete(id);
};
export const fibonacci = (n) =>
new Promise((resolve) => {
const id = nextId++;
pending.set(id, resolve);
worker.postMessage({ id, n });
});Loading the live example…
Loading the live example…
Every call returns a cancelable promise. Progress arrives through it, and canceling it tells the worker to stop. No second channel to invent.
Cancel and report progressThe cancellation reaches the worker, so the work stops there instead of finishing for nobody.
One worker runs one task at a time, so three searches wait in line. Set maxWorkers and the same object runs them on three native Workers at once. Same answers, sooner, and the calling code does not change.
Workers are created as calls arrive, so an idle application pays for none of them.
Loading the live example…
Loading the live example…
Begin with one method. Reach for the rest only when the application needs it.
Pass an ArrayBuffer in the transfer list and it changes threads without being duplicated. Results can travel back the same way.
Give createWorker a function and it becomes the worker. Same methods, same inferred types, nothing to configure.
A URL, a native Worker, your own pool, or the message API you already use. Typed methods can sit on top of any of them.
No mystery around the small print.
A real one. Every worker is a native Worker created by the browser, on its own thread. The library only replaces the message plumbing between both sides.
No. It runs anywhere Web Workers do, with React, Vue, Angular, Svelte or none. Worker files follow the rules of your bundler; the guide lists each setup.
A cancellation is a message, and a worker stuck in a synchronous loop cannot receive it. Work that pauses can stop cleanly; for the rest you restart the worker. How cancellation works.
No. The message API is still there and fully supported. Typed methods are built on top of it, and both can live in the same worker.
Write Worker code like an API. Call it like normal asynchronous JavaScript.