# easy-web-worker

> This is a library to easily handle and create web-workers, if you are a web developer and never tried this... do not wait any longer! Workers are an amazing option to improve user experience and make your webpages even greater

Latest version **8.0.3** (published 2026-10-05) · ISC license · 0 weekly downloads

## Install

```sh
npm install easy-web-worker
pnpm add easy-web-worker
yarn add easy-web-worker
bun add easy-web-worker
```

## Health

**Score 65/100 (B)** — status: active.

Positive: has types; esm support; no vulnerabilities; recently updated; high maintenance score.

Warnings: low downloads.

## Facts

| | |
|---|---|
| Version | 8.0.3 |
| Published | 2026-10-05 |
| First published | 2021-01-13 |
| Weekly downloads | 0 |
| License | ISC |
| TypeScript types | bundled |
| Module format | ESM + CommonJS |
| Dependencies | 1 |
| Unpacked size | 159.4 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| GitHub stars | 26 |
| Author | johnny quesada |
| Maintainers | johnny-qc |
| Keywords | react, web, worker, workers, threads, thread, typescript |

## Links

- npm: https://www.npmjs.com/package/easy-web-worker
- Repository: https://github.com/johnny-quesada-developer/easy-web-worker
- Homepage: https://johnny-quesada-developer.github.io/easy-web-worker/
- Issues: https://github.com/johnny-quesada-developer/easy-web-worker/issues
- npm.io page: https://npm.io/package/easy-web-worker

## Dependencies (1)

- [easy-cancelable-promise](https://npm.io/package/easy-cancelable-promise.md) ^3.0.0

## Alternatives

- [mobx-react](https://npm.io/package/mobx-react.md) — 2.8M weekly downloads
- [rc-tree](https://npm.io/package/rc-tree.md) — 2.6M weekly downloads
- [@react-oauth/google](https://npm.io/package/@react-oauth/google.md) — 1.3M weekly downloads
- [@wagmi/connectors](https://npm.io/package/@wagmi/connectors.md) — 877.0K weekly downloads
- [vee-validate](https://npm.io/package/vee-validate.md) — 836.4K weekly downloads

## Recent versions

- 8.0.3 (latest) — 2026-10-05
- 8.0.2 — 2026-10-05
- 8.0.1 — 2026-10-05
- 8.0.0 — 2026-10-05
- 7.0.6 — 2026-08-17
- 7.0.5 — 2025-12-10
- 7.0.4 — 2025-12-10
- 7.0.3 — 2025-12-10
- 7.0.2 — 2025-02-11
- 7.0.1 — 2024-09-27
- 7.0.0 — 2024-09-27
- 6.2.3 — 2024-09-24
- 6.2.2 — 2024-08-29
- 6.2.1 — 2024-08-29
- 6.2.0 — 2024-02-01
- … 55 more at https://npm.io/package/easy-web-worker/versions

## README

# easy-web-worker 🌟

<div align="center">

![Johnny Quesada](https://raw.githubusercontent.com/johnny-quesada-developer/global-hooks-example/main/public/avatar2.jpeg)

</div>

<div align="center">

## **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.

[![npm version](https://img.shields.io/npm/v/easy-web-worker.svg)](https://www.npmjs.com/package/easy-web-worker)
[![Downloads](https://img.shields.io/npm/dm/easy-web-worker.svg)](https://www.npmjs.com/package/easy-web-worker)
[![License](https://img.shields.io/github/license/johnny-quesada-developer/easy-web-worker)](https://github.com/johnny-quesada-developer/easy-web-worker/blob/main/LICENSE)

[**Website**](https://johnny-quesada-developer.github.io/easy-web-worker/) · [**Documentation**](https://johnny-quesada-developer.github.io/easy-web-worker/docs/) · [**Examples**](https://johnny-quesada-developer.github.io/easy-web-worker/examples/) · [**API reference**](https://johnny-quesada-developer.github.io/easy-web-worker/docs/api-reference/) · [**Video Tutorial**](https://www.youtube.com/watch?v=CK-Uri9lDOE)

Created by [Johnny Quesada](https://github.com/johnny-quesada-developer).

</div>

---

## Start with a single worker

**Imagine defining your worker like this:**

```ts
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:**

```ts
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 →](https://johnny-quesada-developer.github.io/easy-web-worker/#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.

```ts
// The worker does the heavy lifting
const worker = defineWorker(() => ({
  countWords: (text: string) => text.trim().split(/\s+/).length,
}));
```

```ts
// The main thread remains 100% responsive
await text.countWords(article); // CancelablePromise<number>
```

[**How typed workers work →**](https://johnny-quesada-developer.github.io/easy-web-worker/docs/typed-workers/)

### **Return a value, get a promise**

A method resolves with what it returns and rejects with what it throws. `async` works the same way.

```ts
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());
  },
}));
```

[**Return values and errors →**](https://johnny-quesada-developer.github.io/easy-web-worker/docs/typed-workers/#return-values-and-errors)

### **Cancellation that reaches the Worker**

Every call returns a `CancelablePromise`. Canceling it tells the Worker to stop.

```ts
const task = math.findPrimes(50_000_000);

task.cancel('Canceled by user');
```

[**Run it live →**](https://johnny-quesada-developer.github.io/easy-web-worker/examples/progress-and-cancellation/) · [Read the guide](https://johnny-quesada-developer.github.io/easy-web-worker/docs/cancellation-and-progress/#cancellation-reaches-the-worker)

### **Progress on the same call**

Long-running methods report progress through the promise they already returned.

```ts
await math.findPrimes(50_000_000).onProgress((percentage) => {
  progressBar.value = percentage;
});
```

[**Run it live →**](https://johnny-quesada-developer.github.io/easy-web-worker/examples/progress-and-cancellation/) · [Read the guide](https://johnny-quesada-developer.github.io/easy-web-worker/docs/cancellation-and-progress/#progress-on-the-same-call)

### **Every core, one option**

Set `maxWorkers` and the same object spreads calls across several native Workers.

```ts
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 →**](https://johnny-quesada-developer.github.io/easy-web-worker/examples/worker-pool/) · [Read the guide](https://johnny-quesada-developer.github.io/easy-web-worker/docs/worker-pools/)

### **Move large buffers instead of copying them**

Pass transferable objects as the second argument. Ownership moves and nothing is cloned.

```ts
const blurred = await images.blur(buffer, [buffer]);
```

[**Run it live →**](https://johnny-quesada-developer.github.io/easy-web-worker/examples/transferable-buffers/) · [Read the guide](https://johnny-quesada-developer.github.io/easy-web-worker/docs/transferable-objects/)

### **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.

```ts
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 →**](https://johnny-quesada-developer.github.io/easy-web-worker/docs/creating-workers/)

### **No Worker file required**

Pass a function instead of a file. Same methods, same inferred types, nothing to configure.

```ts
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 →**](https://johnny-quesada-developer.github.io/easy-web-worker/examples/worker-without-a-file/) · [Read the guide](https://johnny-quesada-developer.github.io/easy-web-worker/docs/runtime-workers/)

---

## Installation

```bash
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 `CancelablePromise` from [`easy-cancelable-promise`](https://www.npmjs.com/package/easy-cancelable-promise), installed with the package.

---

## Quick Start

### Your first typed worker

```ts
// 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;
```

```ts
// 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](#every-way-to-create-a-worker) for the production setup.
>
> Prefer a step-by-step page? [Getting started](https://johnny-quesada-developer.github.io/easy-web-worker/docs/getting-started/) covers the same code, and [Keep the page responsive](https://johnny-quesada-developer.github.io/easy-web-worker/examples/keep-the-page-responsive/) runs it in your browser.

### Progress and cancellation

Wrap a method with `onMessage` to receive the message of the call.

```ts
// 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;
  }),
}));
```

```ts
// 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](https://johnny-quesada-developer.github.io/easy-web-worker/examples/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

```ts
// 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;
```

```ts
// 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:

```ts
// fibonacci.worker.js
self.onmessage = ({ data: { id, n } }) => {
  self.postMessage({ id, result: fibonacci(n) });
};
```

```ts
// 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

```ts
// 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());
  },
}));
```

```ts
// 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

```ts
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](#stop-work-that-never-pauses).

#### Transferable objects

```ts
// 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:

```ts
// 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.

```ts
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:

```ts
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](https://johnny-quesada-developer.github.io/easy-web-worker/docs/typed-workers/) · [Methods and the message](https://johnny-quesada-developer.github.io/easy-web-worker/docs/methods-and-the-message/) · [TypeScript](https://johnny-quesada-developer.github.io/easy-web-worker/docs/typescript/) · [API reference](https://johnny-quesada-developer.github.io/easy-web-worker/docs/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

```ts
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:

```ts
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:

```ts
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:

```ts
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:

```ts
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:

```ts
const math = createWorker<MathWorker>('/workers/math.worker.js');
```

#### A function, with no file at all

```ts
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](#runtime-workers-a-function-as-the-worker) for scope rules, parameters and external scripts.

> **On the website:** [Creating workers](https://johnny-quesada-developer.github.io/easy-web-worker/docs/creating-workers/) · [Troubleshooting](https://johnny-quesada-developer.github.io/easy-web-worker/docs/troubleshooting/)

---

### Worker pools

One Worker runs one task at a time. A pool runs several at once, behind the same object.

#### Scale on demand

```ts
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

```ts
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](https://johnny-quesada-developer.github.io/easy-web-worker/docs/worker-pools/) · [Live example: one worker or three](https://johnny-quesada-developer.github.io/easy-web-worker/examples/worker-pool/)

---

### Runtime Workers: a function as the Worker

Give `createWorker` a function instead of a file. No extra file, no bundler configuration.

#### The Basics

```ts
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`](#three-ways-to-write-a-method): 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**.

```ts
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`:

```ts
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.

```ts
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:

```ts
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.

```ts
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:

```ts
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](https://johnny-quesada-developer.github.io/easy-web-worker/docs/runtime-workers/) · [Live example: a worker without a file](https://johnny-quesada-developer.github.io/easy-web-worker/examples/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

```ts
// 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());
});
```

```ts
// 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:

```ts
const worker = createWorker<{ uppercase: (text: string) => string }>(source);

await worker.uppercase('hello');
```

> **On the website:** [Message API](https://johnny-quesada-developer.github.io/easy-web-worker/docs/message-api/) · [API reference](https://johnny-quesada-developer.github.io/easy-web-worker/docs/api-reference/)

---

## Advanced Patterns

### Latest request wins

For search, previews and autocomplete, an older request is worthless once a newer one exists.

```ts
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:

```ts
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

```ts
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](https://johnny-quesada-developer.github.io/easy-web-worker/docs/cancellation-and-progress/) · [Testing](https://johnny-quesada-developer.github.io/easy-web-worker/docs/testing/) · [Troubleshooting](https://johnny-quesada-developer.github.io/easy-web-worker/docs/troubleshooting/)

---

## Documentation and examples

Everything in this README runs for real on the [easy-web-worker website](https://johnny-quesada-developer.github.io/easy-web-worker/): 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](https://johnny-quesada-developer.github.io/easy-web-worker/examples/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](https://johnny-quesada-developer.github.io/easy-web-worker/examples/progress-and-cancellation/) | A long search that reports how far it is, and stops inside the Worker when you cancel it. |
| [Worker pool](https://johnny-quesada-developer.github.io/easy-web-worker/examples/worker-pool/) | Three searches with one Worker and then with three. Same answers, sooner. |
| [Transferable buffers](https://johnny-quesada-developer.github.io/easy-web-worker/examples/transferable-buffers/) | A large buffer moved to the Worker instead of copied. |
| [Worker without a file](https://johnny-quesada-developer.github.io/easy-web-worker/examples/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](https://johnny-quesada-developer.github.io/easy-web-worker/docs/getting-started/) | Define your first worker and call it from the main thread. |
| [Guides](https://johnny-quesada-developer.github.io/easy-web-worker/docs/) | Typed workers, cancellation, progress, transferables, pools and bundler setup. |
| [API reference](https://johnny-quesada-developer.github.io/easy-web-worker/docs/api-reference/) | `defineWorker`, `createWorker`, `unwrap`, the message and the configuration. |
| [Testing](https://johnny-quesada-developer.github.io/easy-web-worker/docs/testing/) | Test worker logic as plain functions, and the calling code without a Worker. |
| [Troubleshooting](https://johnny-quesada-developer.github.io/easy-web-worker/docs/troubleshooting/) | Symptoms, causes and fixes. |
| [Platform and versions](https://johnny-quesada-developer.github.io/easy-web-worker/docs/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

```bash
npm install easy-web-worker
```

Then in your Worker file:

```ts
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:

```ts
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](https://johnny-quesada-developer.github.io/easy-web-worker/) to build your next worker.

---

## Collaborators

<div align="center">

<table>
  <tr>
    <td align="center">
      <a href="https://github.com/johnny-quesada-developer">
        <img src="https://avatars.githubusercontent.com/u/62082152?v=4&s=150" width="100" alt="Johnny Quesada" />
        <br />
        <sub><b>Johnny Quesada</b></sub>
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/gabrielecirulli">
        <img src="https://avatars.githubusercontent.com/u/886011?v=4&s=150" width="100" alt="Gabriele Cirulli" />
        <br />
        <sub><b>Gabriele Cirulli</b></sub>
      </a>
    </td>
  </tr>
</table>

</div>

---

## 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**](https://github.com/johnny-quesada-developer/easy-web-worker) · [**Read the docs**](https://johnny-quesada-developer.github.io/easy-web-worker/docs/) · [**Run the examples**](https://johnny-quesada-developer.github.io/easy-web-worker/examples/) · [**Report an issue**](https://github.com/johnny-quesada-developer/easy-web-worker/issues)

## Earlier resources

Demos and walkthroughs from previous versions, kept for reference. For the current API, start with the
[documentation website](https://johnny-quesada-developer.github.io/easy-web-worker/).

- [Original text diff demo](https://johnny-quesada-developer.github.io/easy-web-workers-example/) and its [source](https://github.com/johnny-quesada-developer/easy-web-workers-example)
- [Original video walkthrough](https://www.youtube.com/watch?v=CK-Uri9lDOE)
- [Original CodePen example](https://codepen.io/johnnynabetes/full/wvOvygW)

---
_Source: https://npm.io/package/easy-web-worker · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
