Skip to content
easy-web-worker
LIVESame task. Two ways to run it.

Web Workers are powerful.
Now they're also easy.

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.

math.worker.ts · you write this
export const worker = defineWorker(() => ({
  fibonacci,
}));

function fibonacci(n: number): number {
  return n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2);
}
main.ts · and call it like this
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); // 102334155
Real native Workers TypeScript inference Any framework Open source · ISC
A RESPONSIVE PAGE

The work is heavy.
The page
doesn't have to be.

Same 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 example

This runs in your browser, against the same package files that are published to npm.

Loading the live example…

MESSAGES BECOME FUNCTIONS

The native API gives you
messages. You wanted a function.

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 it
fibonacci.worker.js · by hand
const fibonacci = (n) => (n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2));

self.onmessage = ({ data: { id, n } }) => {
  self.postMessage({ id, result: fibonacci(n) });
};
main.js · by hand
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…

PROGRESS AND CANCELLATION

One call.
Its whole lifecycle.

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 progress

The cancellation reaches the worker, so the work stops there instead of finishing for nobody.

WORKER POOLS

Every core.
One option.

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.

Scale with a pool

Workers are created as calls arrive, so an idle application pays for none of them.

Loading the live example…

Simple to start.
Room for the real world.

Begin with one method. Reach for the rest only when the application needs it.

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.

Transfer large data

A worker without a file.

Give createWorker a function and it becomes the worker. Same methods, same inferred types, nothing to configure.

Create runtime workers

Your existing workers, too.

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.

See every way to create one

A few useful details.

No mystery around the small print.

Is it a real Web Worker, or a simulation?

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.

Does it depend on a framework or a bundler?

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.

Can a call always be canceled?

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.

I already use createEasyWebWorker. Do I have to migrate?

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.

LESS PLUMBING. MORE WORK DONE.

Your next worker
starts here.

Write Worker code like an API. Call it like normal asynchronous JavaScript.

↑↓ Navigate ↵ Open Esc Closeeasy-web-worker docs
Get started

Real Web Workers. Typed methods.
Cancelable promises.