easy-web-worker
![]()
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.
easy-web-worker handles the plumbing between both sides: message routing, request matching, promises, errors, cancellation, progress, transferable objects, lifecycle management, and worker pools.
Website · Documentation · Examples · API reference · Video Tutorial
Created by Johnny Quesada.
Start with a single worker
Imagine defining your worker like this:
export const worker = defineWorker(() => ({
fibonacci,
}));
function fibonacci(n: number): number {
return n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2);
}
And calling it like this:
import type { worker as MathWorker } from './math.worker';
import mathWorkerUrl from './math.worker?worker&url';
/**
* The type of the worker is inferred from it's declaration,
* so everything is typed on the main thread.
*/
const mathWorker = createWorker<typeof MathWorker>(mathWorkerUrl);
const result = await mathWorker.fibonacci(40); // 102334155
The computation runs inside a real native Web Worker. The page keeps scrolling, typing and animating while it runs.
No manual postMessage.
No message IDs.
No response matching.
No hand-written promise bridge.
Just functions.
See it live → Same task, two ways to run it. On the main thread the page freezes. In a Worker it never stops. It runs in your browser, with real Workers.
Built for work that should not block the page
Functions, not messages
Every key you return from defineWorker becomes a typed method on the main thread.
// The worker does the heavy lifting
const worker = defineWorker(() => ({
countWords: (text: string) => text.trim().split(/\s+/).length,
}));
// The main thread remains 100% responsive
await text.countWords(article); // CancelablePromise<number>
Return a value, get a promise
A method resolves with what it returns and rejects with what it throws. async works the same way.
const worker = defineWorker(() => ({
parseReport: async (url: string) => {
const response = await fetch(url);
if (!response.ok) throw new Error('Report not available');
return buildReport(await response.text());
},
}));
Cancellation that reaches the Worker
Every call returns a CancelablePromise. Canceling it tells the Worker to stop.
const task = math.findPrimes(50_000_000);
task.cancel('Canceled by user');
Run it live → · Read the guide
Progress on the same call
Long-running methods report progress through the promise they already returned.
await math.findPrimes(50_000_000).onProgress((percentage) => {
progressBar.value = percentage;
});
Run it live → · Read the guide
Every core, one option
Set maxWorkers and the same object spreads calls across several native Workers.
const math = createWorker<MathWorker>(source, { maxWorkers: 4 });
// three calls, three threads, at the same time
await Promise.all([math.fibonacci(40), math.fibonacci(41), math.fibonacci(42)]);
Run it live → · Read the guide
Move large buffers instead of copying them
Pass transferable objects as the second argument. Ownership moves and nothing is cloned.
const blurred = await images.blur(buffer, [buffer]);
Run it live → · Read the guide
Any way you create a Worker
A Worker file, a native Worker, your own pool, or just a function. The API on top stays the same.
createWorker<MathWorker>(new URL('./math.worker.ts', import.meta.url)); // Worker file
createWorker<MathWorker>(workerUrl); // URL generated by your bundler
createWorker<MathWorker>(new Worker(...)); // existing Worker
createWorker<MathWorker>([worker1, worker2]); // existing pool
createWorker(() => ({ fibonacci })); // a function, no file at all
Every source, with the setup for each bundler →
No Worker file required
Pass a function instead of a file. Same methods, same inferred types, nothing to configure.
const math = createWorker(() => {
const fibonacci = (n: number): number =>
n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2);
return { fibonacci };
});
await math.fibonacci(40); // 102334155, typed as number
Run it live → · Read the guide
Installation
npm install easy-web-worker
- Any framework: built on the native Worker API of the browser, with no dependency on React, Vue, Angular, Svelte or any other UI library.
- Promises: calls return a
CancelablePromisefromeasy-cancelable-promise, installed with the package.
Quick Start
Your first typed worker
// math.worker.ts
import { defineWorker } from 'easy-web-worker/defineWorker';
const fibonacci = (n: number): number =>
n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2);
// 1. Define it (in the Worker file)
const worker = defineWorker(() => ({ fibonacci }));
export type MathWorker = typeof worker;
// main.ts
import { createWorker } from 'easy-web-worker/createWorker';
import type { MathWorker } from './math.worker';
// 2. Create it (on the main thread)
const math = createWorker<MathWorker>(
new URL('./math.worker.ts', import.meta.url),
{
workerOptions: { type: 'module' },
}
);
// 3. Call it
const result = await math.fibonacci(40); // 102334155
The main thread imports only the type of the Worker. TypeScript infers both sides: math.fibonacci takes a number and returns a CancelablePromise<number>. Change the Worker, and the main thread stops compiling until it matches.
Shipping with a bundler? See Every way to create a worker for the production setup.
Prefer a step-by-step page? Getting started covers the same code, and Keep the page responsive runs it in your browser.
Progress and cancellation
Wrap a method with onMessage to receive the message of the call.
// math.worker.ts
const worker = defineWorker(({ onMessage }) => ({
findPrimes: onMessage(async (limit: number, message) => {
const primes: number[] = [];
for (let candidate = 2; candidate <= limit; candidate++) {
if (isPrime(candidate)) primes.push(candidate);
if (candidate % 100_000 !== 0) continue;
// give the Worker a moment to receive a cancellation
await new Promise((resolve) => setTimeout(resolve));
if (!message.isPending()) break;
message.reportProgress((candidate / limit) * 100);
}
return primes;
}),
}));
// main.ts
const task = math.findPrimes(50_000_000).onProgress((percentage) => {
progressBar.value = percentage;
});
cancelButton.onclick = () => task.cancel('Canceled by user');
const primes = await task;
Progress and cancellation belong to the call itself, not to a second channel.
This exact pattern runs live in Progress and cancellation: start a long search, watch it advance, and cancel it.
Core Features Deep Dive
Typed workers with defineWorker and createWorker
Write the Worker as a set of methods and call them from the main thread like local async functions.
The Basics
// text.worker.ts
const worker = defineWorker(() => ({
countWords: (text: string) => text.trim().split(/\s+/).length,
findDuplicates: (lines: string[]) =>
lines.filter((line, index) => lines.indexOf(line) !== index),
}));
export type TextWorker = typeof worker;
// main.ts
const text = createWorker<TextWorker>(source);
await text.countWords(article); // number
await text.findDuplicates(lines); // string[]
The contract between the threads lives next to the code that implements it. There is no second file of message types to keep in sync.
How the types cross the threads:
| In the Worker | On the main thread |
|---|---|
() => string |
() => CancelablePromise<string> |
(value: number) => number |
(payload: number, transfer?) => CancelablePromise<number> |
async (ids: string[]) => User[] |
(payload: string[], transfer?) => CancelablePromise<User[]> |
(value?: number) => number |
(payload?: number, transfer?) => CancelablePromise<number> |
A method receives one payload. Send several values as an object or a tuple. Required payloads are enforced when TypeScript runs with strict or strictNullChecks.
What it replaces
fibonacci(40) on the main thread freezes the page for about a second. A Web Worker fixes that, but the native API gives you messages, not functions. This is the least you write by hand:
// fibonacci.worker.js
self.onmessage = ({ data: { id, n } }) => {
self.postMessage({ id, result: fibonacci(n) });
};
// main.js
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);
};
const fibonacci = (n) =>
new Promise((resolve) => {
const id = nextId++;
pending.set(id, resolve);
worker.postMessage({ id, n });
});
And that is before errors, cancellation, progress, types, or a second method.
Return values and errors
// report.worker.ts
const worker = defineWorker(() => ({
parseReport: async (url: string) => {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Report not available: ${response.status}`);
}
return buildReport(await response.text());
},
}));
// main.ts
try {
const report = await reports.parseReport('/reports/2026.csv');
} catch (error) {
console.log((error as Error).message); // 'Report not available: 404'
}
An error rejects that one call. The Worker keeps serving the next one.
Three ways to write a method
const worker = defineWorker(({ onMessage }) => ({
// 1. a plain function
double: (value: number) => value * 2,
// 2. onMessage: same thing, with the message and the event typed for you
count: onMessage(async (to: number, message, event) => {
message.reportProgress(50);
return to;
}),
// 3. onMessage().handle: only the message, you complete it
wait: onMessage<number, string>().handle((message, event) => {
const timeoutId = setTimeout(
() => message.resolve('done'),
message.payload
);
message.onCancel(() => clearTimeout(timeoutId));
}),
}));
| Style | Completes the call | Types |
|---|---|---|
| Plain function | With the returned value | Inferred |
onMessage((payload, message, event) => ...) |
With the returned value | Inferred, message typed |
onMessage<P, R>().handle((message) => ...) |
You do | Explicit, message.resolve typed |
All three are called the same way from the main thread. Use the third for timers, streams, event-based APIs, or to transfer a result.
If a method completes the message itself with resolve, reject or cancel, its return value is ignored.
The message
Available in onMessage, in .handle(...), and in the message API.
| Member | Purpose |
|---|---|
payload |
Value sent from the main thread |
resolve(result?, transfer?) |
Resolves the call |
reject(reason?, transfer?) |
Rejects the call |
cancel(reason?, transfer?) |
Cancels the call from inside the Worker |
reportProgress(percentage, payload?, transfer?) |
Reports progress and keeps the call pending |
onCancel, onResolve, onReject, onProgress |
Subscribe to each outcome; return unsubscribe |
onFinalize |
Runs once the call ends, whatever the outcome |
isPending(), getStatus() |
Current state of the call |
Cancellation
The cancellation reaches the Worker, so the work stops there instead of finishing for nobody. React to it with message.onCancel(...) or check message.isPending().
Canceling twice, or after the call finished, does nothing.
A Worker that never pauses cannot receive a cancellation. See Stop work that never pauses.
Transferable objects
// main.ts
const buffer = await file.arrayBuffer();
const blurred = await images.blur(buffer, [buffer]);
Returned values are cloned. To transfer a result back, complete the call yourself:
// images.worker.ts
const worker = defineWorker(({ onMessage }) => ({
blur: onMessage<ArrayBuffer, ArrayBuffer>().handle((message) => {
const result = applyBlur(message.payload);
message.resolve(result, [result]);
}),
}));
unwrap: the controls behind the methods
The object returned by createWorker holds only your methods, so names like send, dispose or reboot stay free for your own API. unwrap reaches the EasyWebWorker instance behind it.
import { createWorker, unwrap } from 'easy-web-worker/createWorker';
const math = createWorker<MathWorker>(source);
await unwrap(math).cancelAll('Leaving the page');
await unwrap(math).reboot();
await unwrap(math).dispose();
EasyWebWorker method |
Purpose |
|---|---|
send(payload?, transfer?) |
Sends a message to the default handler |
sendToMethod<TResult, TPayload>(method, payload?, transfer?) |
Sends a message to a named method |
cancelAll(reason?, { force? }) |
Cancels every pending call; force restarts instead of waiting |
override(payload?, reason?, { force? }) |
Cancels the pending calls and sends a new message |
overrideAfterCurrent(payload?, reason?) |
Keeps the current call, cancels the rest, sends a new message |
reboot(reason?) |
Terminates the Workers, rejects pending calls, starts again |
dispose() |
Cancels pending calls and releases the Workers |
The same helper works inside the Worker file, where it returns the Worker-side controls:
import { defineWorker, unwrap } from 'easy-web-worker/defineWorker';
const worker = defineWorker(() => ({
shutdown: () => unwrap(worker).close(),
}));
unwrap(worker).importScripts('https://example.com/library.js');
// handles messages sent without a method: send, override, overrideAfterCurrent
unwrap(worker).onMessage((message) => {
message.resolve();
});
One name is not available for a method: then. The worker object is not a promise, so await math never sends a message.
Entry points
Import each side from its own entry, so the Worker bundle carries no main-thread code:
| Entry | Use it in | Exports |
|---|---|---|
easy-web-worker/defineWorker |
The Worker file | defineWorker, unwrap, types |
easy-web-worker/createWorker |
The main thread | createWorker, unwrap, types |
Everything is also exported from the root easy-web-worker entry.
On the website: Typed workers · Methods and the message · TypeScript · API reference
Every way to create a worker
Pick the source that fits how your project builds and serves its Workers. createWorker, createEasyWebWorker and new EasyWebWorker(...) accept all of them. A function means methods for createWorker and messages for the other two.
| Source | Best for | Pool with maxWorkers |
reboot / force |
|---|---|---|---|
URL of a Worker file |
Worker files in a bundled project | ||
string path |
A Worker file you serve yourself | ||
Existing Worker |
Bundlers that detect new Worker(new URL(...)) |
One Worker | |
Existing Worker[] |
Bringing your own pool | The array is the pool | |
| Function | Workers with no file at all | ||
| Function array | Workers composed from reusable pieces |
A Worker file in development
const math = createWorker<MathWorker>(
new URL('./math.worker.ts', import.meta.url),
{
workerOptions: { type: 'module' },
}
);
type: 'module' is required when the Worker file uses import or export, which every defineWorker file does.
Vite: development and production
In development, Vite serves the TypeScript file. A production build needs the Worker URL that Vite generates:
import { createWorker } from 'easy-web-worker/createWorker';
import workerUrl from './math.worker?worker&url';
import type { MathWorker } from './math.worker';
// the Worker URL in production, the TypeScript file in development
const isProduction = import.meta.env.MODE === 'production';
const math = createWorker<MathWorker>(
isProduction ? workerUrl : new URL('./math.worker.ts', import.meta.url),
{
workerOptions: { type: 'module' },
}
);
The same source works with the message API:
import { EasyWebWorker } from 'easy-web-worker';
const worker = new EasyWebWorker(
isProduction ? workerUrl : new URL('./TextDiff.worker.ts', import.meta.url),
{
workerOptions: { type: 'module' },
}
);
A native Worker you create
webpack 5, Parcel and Vite all detect new Worker(new URL(...)) and bundle the file for you. Create the Worker that way and hand it over:
const math = createWorker<MathWorker>(
new Worker(new URL('./math.worker.ts', import.meta.url), { type: 'module' })
);
The library cannot create that Worker again, so reboot and force are not available. Leave keepAlive at its default so the Worker is not terminated when idle.
Your own pool
Pass an array of native Workers and calls are distributed across them:
const createMathWorker = () =>
new Worker(new URL('./math.worker.ts', import.meta.url), { type: 'module' });
const math = createWorker<MathWorker>([
createMathWorker(),
createMathWorker(),
createMathWorker(),
]);
A file you serve yourself
A Worker script that is already built and public only needs its path:
const math = createWorker<MathWorker>('/workers/math.worker.js');
A function, with no file at all
const math = createWorker(() => ({
double: (value: number) => value * 2,
}));
await math.double(21); // 42
The function returns the methods, exactly like the builder of defineWorker. See Runtime Workers for scope rules, parameters and external scripts.
On the website: Creating workers · Troubleshooting
Worker pools
One Worker runs one task at a time. A pool runs several at once, behind the same object.
Scale on demand
const math = createWorker<MathWorker>(source, {
maxWorkers: 4,
});
// three calls, three threads, at the same time
const results = await Promise.all([
math.fibonacci(40),
math.fibonacci(41),
math.fibonacci(42),
]);
The calling code does not change. It does not know whether one Worker or four are behind it.
Workers are created as concurrent calls arrive, so an idle application pays for none of them.
Warm up or release the pool
createWorker<MathWorker>(source, {
maxWorkers: 4,
warmUpWorkers: true, // create the whole pool up front, for the fastest first call
});
createWorker<MathWorker>(source, {
maxWorkers: 4,
keepAlive: false, // terminate idle Workers...
terminationDelay: 5_000, // ...after five seconds without work
});
Configuration
| Option | Default | Purpose |
|---|---|---|
maxWorkers |
1 |
Maximum number of native Workers |
warmUpWorkers |
true for one Worker |
Creates the pool during initialization |
keepAlive |
Same as warmUpWorkers |
Keeps idle Workers alive |
terminationDelay |
1000 ms |
Wait before terminating idle Workers |
workerOptions |
{} |
Native WorkerOptions, such as { type: 'module' } |
onWorkerError |
Rethrows | Receives the uncaught errors of the Worker |
scripts |
[] |
Scripts imported into a runtime Worker |
primitiveParameters |
[] |
Static values for a runtime Worker, in context |
The same options apply to createWorker, createEasyWebWorker and new EasyWebWorker(...).
On the website: Worker pools · Live example: one worker or three
Runtime Workers: a function as the Worker
Give createWorker a function instead of a file. No extra file, no bundler configuration.
The Basics
import { createWorker } from 'easy-web-worker/createWorker';
const math = createWorker(({ onMessage }) => {
const fibonacci = (n: number): number =>
n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2);
return {
fibonacci,
countTo: onMessage(async (limit: number, message) => {
for (let step = 1; step <= limit; step++) {
message.reportProgress((step * 100) / limit);
}
return limit;
}),
};
});
const result = await math.fibonacci(40); // 102334155
The function is the builder of defineWorker: it receives onMessage, returns the methods, and their types are inferred. There is no type to export or import, because both sides are in the same file.
Everything else works the same: cancellation, progress, transferable objects, unwrap, and pools with maxWorkers.
Worker scope
The function becomes the source of a real Worker, so it cannot use variables from outside.
const greeting = 'Hello';
createWorker(() => ({
// `greeting` does not exist inside the Worker
greet: (name: string) => `${greeting} ${name}`,
}));
Everything the Worker needs must be defined inside the function, imported as a script, sent as a payload, or passed through primitiveParameters.
Parameters and the scope of the Worker
The second parameter is the global scope of the Worker. Static values arrive in context.primitiveParameters:
const worker = createWorker(
(_helpers, context) => {
const [prefix] = context.primitiveParameters;
return {
label: (text: string) => `${prefix} ${text}`,
};
},
{
primitiveParameters: ['Result:'] as [string],
}
);
await worker.label('complete'); // 'Result: complete'
The message API inside the function
The function also receives easyWorker, the worker instance behind the methods. Everything the message API offers stays available, so existing runtime Workers can move over at their own pace.
const worker = createWorker<{
double: (value: number) => number;
triple: (value: number) => number;
}>(({ easyWorker }) => {
// named handler, message style
easyWorker.onMessage<number, number>('double', (message) => {
message.resolve(message.payload * 2);
});
// handler for messages sent without a method
easyWorker.onMessage((message) => {
message.resolve();
});
return {
triple: (value: number) => value * 3,
shutdown: () => easyWorker.close(),
};
});
await worker.double(21); // 42
await worker.triple(21); // 63
Only the returned methods are inferred. Handlers registered through easyWorker are described in the generic, as above, or called with unwrap(worker).sendToMethod(...).
Import scripts
External libraries load with the scripts option and appear in the scope of the Worker:
const text = createWorker(
(_helpers, context) => {
// the script adds `Diff` to the scope of the Worker
const { diffWords } = context.Diff as {
diffWords: (before: string, after: string) => unknown[];
};
return {
compare: ({ before, after }: { before: string; after: string }) =>
diffWords(before, after),
};
},
{
scripts: ['https://cdn.jsdelivr.net/npm/diff@9.0.0/dist/diff.min.js'],
}
);
const changes = await text.compare({ before, after });
No Worker file is required, and the script does not become part of your application bundle.
Compose a Worker from several functions
Pass an array and every function runs in the same Worker. They share its scope, and the methods they return are merged.
const worker = createWorker([
// reusable piece: leaves a helper in the scope of the Worker
(_helpers, context) => {
context.round = (value: number) => Math.round(value * 100) / 100;
},
() => ({
double: (value: number) => value * 2,
describe: () => 'first',
}),
(_helpers, context) => {
const round = context.round as (value: number) => number;
return {
average: (values: number[]) =>
round(values.reduce((total, value) => total + value, 0) / values.length),
// repeated method: the last function wins
describe: () => 'last',
};
},
]);
await worker.double(21); // 42
await worker.average([1, 2, 2]); // 1.67
await worker.describe(); // 'last'
Types are merged the same way: worker.describe takes the signature of the last function. Write the array inline so each function keeps its own type.
Runtime Workers with the message API
createEasyWebWorker builds a Worker from a function too, with messages instead of methods:
import { createEasyWebWorker } from 'easy-web-worker';
const worker = createEasyWebWorker<number, number>(({ onMessage }) => {
onMessage((message) => {
message.resolve(message.payload * 2);
});
});
await worker.send(21); // 42
The first generic is the payload of send(). The second is the value passed to message.resolve(). It also accepts an array of functions that share the scope of the Worker, to compose a Worker from reusable pieces.
On the website: Runtime workers · Live example: a worker without a file
The message API with StaticEasyWebWorker
defineWorker is built on a message API that you can use directly, in Worker files and in runtime Workers.
The Basics
// worker.ts
import { createStaticEasyWebWorker } from 'easy-web-worker/createStaticEasyWebWorker';
const { onMessage } = createStaticEasyWebWorker<number, number>((message) => {
message.resolve(message.payload * 2);
});
onMessage<string, string>('uppercase', (message) => {
message.resolve(message.payload.toUpperCase());
});
// main.ts
import { createEasyWebWorker } from 'easy-web-worker';
const worker = createEasyWebWorker<number, number>(source);
await worker.send(21); // default handler
await worker.sendToMethod<string, string>('uppercase', 'hello'); // result type first
Typed methods for an existing Worker
A Worker written with the message API can still be called with typed methods. Describe them:
const worker = createWorker<{ uppercase: (text: string) => string }>(source);
await worker.uppercase('hello');
On the website: Message API · API reference
Advanced Patterns
Latest request wins
For search, previews and autocomplete, an older request is worthless once a newer one exists.
const search = createEasyWebWorker<string, SearchResult[]>(searchBody);
input.oninput = async () => {
// cancels what is pending, then sends the new message
const results = await search.override(input.value, 'Superseded');
render(results);
};
overrideAfterCurrent lets the call in progress finish and replaces only what is waiting behind it.
Stop work that never pauses
A Worker stuck in a synchronous loop cannot receive a cancellation. Restart it instead:
import { unwrap } from 'easy-web-worker/createWorker';
// terminates the Workers, rejects the pending calls, starts fresh ones
await unwrap(math).cancelAll('Taking too long', { force: true });
Clean up when the owner goes away
await unwrap(math).dispose(); // typed workers
await worker.dispose(); // EasyWebWorker
dispose cancels the pending calls, revokes the generated Worker URL when there is one, and terminates the Workers.
On the website: Cancellation and progress · Testing · Troubleshooting
Documentation and examples
Everything in this README runs for real on the easy-web-worker website: real Web Workers, in your browser, against the same files that are published to npm.
See it before you install it
| Live example | What you will see |
|---|---|
| Keep the page responsive | The same computation on the main thread and in a Worker. One freezes the page, the other does not. |
| Progress and cancellation | A long search that reports how far it is, and stops inside the Worker when you cancel it. |
| Worker pool | Three searches with one Worker and then with three. Same answers, sooner. |
| Transferable buffers | A large buffer moved to the Worker instead of copied. |
| Worker without a file | A Worker created from a function, with no extra file and no bundler setup. |
Every example shows its full source next to the running result.
Then go deeper
| Guide | What you will find |
|---|---|
| Getting started | Define your first worker and call it from the main thread. |
| Guides | Typed workers, cancellation, progress, transferables, pools and bundler setup. |
| API reference | defineWorker, createWorker, unwrap, the message and the configuration. |
| Testing | Test worker logic as plain functions, and the calling code without a Worker. |
| Troubleshooting | Symptoms, causes and fixes. |
| Platform and versions | Browser support and what changes when upgrading. |
Built to grow with your application
Without easy-web-worker |
With easy-web-worker |
|---|---|
postMessage() protocols |
Functions |
| Manual request IDs | Automatic routing |
| Response matching | Promises |
| Custom error transport | Throw / catch |
| Custom cancellation protocol | task.cancel() |
| Custom progress messages | .onProgress() |
| Hand-maintained TypeScript contracts | Inferred types |
| Manual Worker orchestration | Built-in pools |
| Manual lifecycle handling | Warm-up, keep-alive and idle termination |
The Worker is still a real Web Worker, and the browser still provides the isolation and the extra thread. easy-web-worker removes what you would otherwise build around it.
Write Worker code like an API, call it like normal asynchronous JavaScript, and scale it without rebuilding the communication layer.
Get Started Now
npm install easy-web-worker
Then in your Worker file:
import { defineWorker } from 'easy-web-worker/defineWorker';
const fibonacci = (n: number): number =>
n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2);
const worker = defineWorker(() => ({ fibonacci }));
export type MathWorker = typeof worker;
And in your app:
import { createWorker } from 'easy-web-worker/createWorker';
import type { MathWorker } from './math.worker';
const math = createWorker<MathWorker>(source);
console.log(await math.fibonacci(40)); // 102334155
Continue with the guides and interactive examples to build your next worker.
Collaborators
Built by Johnny Quesada
easy-web-worker exists because using another thread should not require building a messaging framework first.
If it makes your codebase simpler, consider starring the project. It helps other developers discover it.
Star on GitHub · Read the docs · Run the examples · Report an issue
Earlier resources
Demos and walkthroughs from previous versions, kept for reference. For the current API, start with the documentation website.