easy-web-worker 7.0.6 โ 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/EasyWebWorker.cjs +1 -0
- package/EasyWebWorker.d.ts +2 -2
- package/EasyWebWorker.js +1 -1
- package/EasyWebWorker.mjs +1 -0
- package/EasyWebWorkerMessage.cjs +1 -0
- package/EasyWebWorkerMessage.d.ts +2 -2
- package/EasyWebWorkerMessage.js +1 -1
- package/EasyWebWorkerMessage.mjs +1 -0
- package/README.md +670 -734
- package/StaticEasyWebWorker.cjs +1 -0
- package/StaticEasyWebWorker.d.ts +1 -1
- package/StaticEasyWebWorker.js +1 -1
- package/StaticEasyWebWorker.mjs +1 -0
- package/buildWorker.cjs +1 -0
- package/buildWorker.d.ts +7 -0
- package/buildWorker.js +1 -0
- package/buildWorker.mjs +1 -0
- package/bundle.cjs +1 -0
- package/bundle.js +1 -1
- package/bundle.mjs +1 -0
- package/createBlobWorker.cjs +2 -0
- package/createBlobWorker.d.ts +1 -0
- package/createBlobWorker.js +2 -1
- package/createBlobWorker.mjs +2 -0
- package/createEasyWebWorker.cjs +1 -0
- package/createEasyWebWorker.js +1 -1
- package/createEasyWebWorker.mjs +1 -0
- package/createStaticEasyWebWorker.cjs +1 -0
- package/createStaticEasyWebWorker.js +1 -1
- package/createStaticEasyWebWorker.mjs +1 -0
- package/createWorker.cjs +4 -0
- package/createWorker.d.ts +53 -0
- package/createWorker.js +4 -0
- package/createWorker.mjs +4 -0
- package/defineWorker.cjs +1 -0
- package/defineWorker.d.ts +31 -0
- package/defineWorker.js +1 -0
- package/defineWorker.mjs +1 -0
- package/getDefineWorkerTemplate.cjs +1 -0
- package/getDefineWorkerTemplate.d.ts +7 -0
- package/getDefineWorkerTemplate.js +1 -0
- package/getDefineWorkerTemplate.mjs +1 -0
- package/getWorkerTemplate.cjs +1 -0
- package/getWorkerTemplate.d.ts +3 -7
- package/getWorkerTemplate.js +1 -1
- package/getWorkerTemplate.mjs +1 -0
- package/index.d.ts +3 -0
- package/package.json +61 -68
- package/types.cjs +1 -0
- package/types.d.ts +115 -0
- package/types.js +1 -1
- package/types.mjs +0 -0
- package/uniqueId.cjs +1 -0
- package/uniqueId.js +1 -1
- package/uniqueId.mjs +1 -0
- package/unwrap.cjs +1 -0
- package/unwrap.d.ts +16 -0
- package/unwrap.js +1 -0
- package/unwrap.mjs +1 -0
- package/webpack.config.js +0 -86
package/README.md
CHANGED
|
@@ -8,1093 +8,1045 @@
|
|
|
8
8
|
|
|
9
9
|
<div align="center">
|
|
10
10
|
|
|
11
|
-
**
|
|
11
|
+
## **Web Workers are powerful. Now they're also easy.**
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Let `easy-web-worker` handle everything between the threads.
|
|
14
|
+
|
|
15
|
+
You keep the power of native Web Workers without dealing with all the complexity that usually comes with them.
|
|
16
|
+
|
|
17
|
+
`easy-web-worker` handles the plumbing between both sides: message routing, request matching, promises, errors, cancellation, progress, transferable objects, lifecycle management, and worker pools.
|
|
14
18
|
|
|
15
19
|
[](https://www.npmjs.com/package/easy-web-worker)
|
|
16
20
|
[](https://www.npmjs.com/package/easy-web-worker)
|
|
17
21
|
[](https://github.com/johnny-quesada-developer/easy-web-worker/blob/main/LICENSE)
|
|
18
22
|
|
|
19
|
-
[**
|
|
23
|
+
[**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)
|
|
24
|
+
|
|
25
|
+
Created by [Johnny Quesada](https://github.com/johnny-quesada-developer).
|
|
20
26
|
|
|
21
27
|
</div>
|
|
22
28
|
|
|
23
29
|
---
|
|
24
30
|
|
|
25
|
-
##
|
|
31
|
+
## Start with a single worker
|
|
32
|
+
|
|
33
|
+
**Imagine defining your worker like this:**
|
|
26
34
|
|
|
27
35
|
```ts
|
|
28
|
-
|
|
36
|
+
export const worker = defineWorker(() => ({
|
|
37
|
+
fibonacci,
|
|
38
|
+
}));
|
|
29
39
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
40
|
+
function fibonacci(n: number): number {
|
|
41
|
+
return n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2);
|
|
42
|
+
}
|
|
33
43
|
```
|
|
34
44
|
|
|
35
|
-
**
|
|
45
|
+
**And calling it like this:**
|
|
36
46
|
|
|
37
47
|
```ts
|
|
38
|
-
|
|
48
|
+
import type { worker as MathWorker } from './math.worker';
|
|
49
|
+
import mathWorkerUrl from './math.worker?worker&url';
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The type of the worker is inferred from it's declaration,
|
|
53
|
+
* so everything is typed on the main thread.
|
|
54
|
+
*/
|
|
55
|
+
const mathWorker = createWorker<typeof MathWorker>(mathWorkerUrl);
|
|
39
56
|
|
|
40
|
-
|
|
57
|
+
const result = await mathWorker.fibonacci(40); // 102334155
|
|
41
58
|
```
|
|
42
59
|
|
|
43
|
-
|
|
60
|
+
The computation runs inside a real native Web Worker. The page keeps scrolling, typing and animating while it runs.
|
|
61
|
+
|
|
62
|
+
No manual `postMessage`.
|
|
63
|
+
No message IDs.
|
|
64
|
+
No response matching.
|
|
65
|
+
No hand-written promise bridge.
|
|
66
|
+
|
|
67
|
+
Just functions.
|
|
68
|
+
|
|
69
|
+
> **[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.
|
|
44
70
|
|
|
45
71
|
---
|
|
46
72
|
|
|
47
|
-
##
|
|
73
|
+
## Built for work that should not block the page
|
|
48
74
|
|
|
49
|
-
###
|
|
75
|
+
### **Functions, not messages**
|
|
50
76
|
|
|
51
|
-
|
|
77
|
+
Every key you return from `defineWorker` becomes a typed method on the main thread.
|
|
52
78
|
|
|
53
79
|
```ts
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
});
|
|
80
|
+
// The worker does the heavy lifting
|
|
81
|
+
const worker = defineWorker(() => ({
|
|
82
|
+
countWords: (text: string) => text.trim().split(/\s+/).length,
|
|
83
|
+
}));
|
|
84
|
+
```
|
|
60
85
|
|
|
61
|
-
|
|
86
|
+
```ts
|
|
87
|
+
// The main thread remains 100% responsive
|
|
88
|
+
await text.countWords(article); // CancelablePromise<number>
|
|
62
89
|
```
|
|
63
90
|
|
|
64
|
-
|
|
91
|
+
[**How typed workers work โ**](https://johnny-quesada-developer.github.io/easy-web-worker/docs/typed-workers/)
|
|
92
|
+
|
|
93
|
+
### **Return a value, get a promise**
|
|
65
94
|
|
|
66
|
-
|
|
95
|
+
A method resolves with what it returns and rejects with what it throws. `async` works the same way.
|
|
67
96
|
|
|
68
97
|
```ts
|
|
69
|
-
const worker =
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
return fibonacci(n - 1) + fibonacci(n - 2);
|
|
73
|
-
};
|
|
98
|
+
const worker = defineWorker(() => ({
|
|
99
|
+
parseReport: async (url: string) => {
|
|
100
|
+
const response = await fetch(url);
|
|
74
101
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
102
|
+
if (!response.ok) throw new Error('Report not available');
|
|
103
|
+
|
|
104
|
+
return buildReport(await response.text());
|
|
105
|
+
},
|
|
106
|
+
}));
|
|
79
107
|
```
|
|
80
108
|
|
|
81
|
-
|
|
109
|
+
[**Return values and errors โ**](https://johnny-quesada-developer.github.io/easy-web-worker/docs/typed-workers/#return-values-and-errors)
|
|
110
|
+
|
|
111
|
+
### **Cancellation that reaches the Worker**
|
|
82
112
|
|
|
83
|
-
Every
|
|
113
|
+
Every call returns a `CancelablePromise`. Canceling it tells the Worker to stop.
|
|
84
114
|
|
|
85
115
|
```ts
|
|
86
|
-
const task =
|
|
116
|
+
const task = math.findPrimes(50_000_000);
|
|
87
117
|
|
|
88
|
-
task.cancel('
|
|
118
|
+
task.cancel('Canceled by user');
|
|
89
119
|
```
|
|
90
120
|
|
|
91
|
-
|
|
121
|
+
[**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)
|
|
92
122
|
|
|
93
|
-
###
|
|
123
|
+
### **Progress on the same call**
|
|
94
124
|
|
|
95
|
-
Long-running
|
|
125
|
+
Long-running methods report progress through the promise they already returned.
|
|
96
126
|
|
|
97
127
|
```ts
|
|
98
|
-
|
|
99
|
-
|
|
128
|
+
await math.findPrimes(50_000_000).onProgress((percentage) => {
|
|
129
|
+
progressBar.value = percentage;
|
|
100
130
|
});
|
|
101
131
|
```
|
|
102
132
|
|
|
103
|
-
|
|
133
|
+
[**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)
|
|
104
134
|
|
|
105
|
-
|
|
135
|
+
### **Every core, one option**
|
|
136
|
+
|
|
137
|
+
Set `maxWorkers` and the same object spreads calls across several native Workers.
|
|
106
138
|
|
|
107
139
|
```ts
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
140
|
+
const math = createWorker<MathWorker>(source, { maxWorkers: 4 });
|
|
141
|
+
|
|
142
|
+
// three calls, three threads, at the same time
|
|
143
|
+
await Promise.all([math.fibonacci(40), math.fibonacci(41), math.fibonacci(42)]);
|
|
111
144
|
```
|
|
112
145
|
|
|
113
|
-
|
|
146
|
+
[**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/)
|
|
114
147
|
|
|
115
|
-
###
|
|
148
|
+
### **Move large buffers instead of copying them**
|
|
116
149
|
|
|
117
|
-
|
|
150
|
+
Pass transferable objects as the second argument. Ownership moves and nothing is cloned.
|
|
118
151
|
|
|
119
152
|
```ts
|
|
120
|
-
|
|
121
|
-
createEasyWebWorker('./worker.js'); // Static Worker file
|
|
122
|
-
createEasyWebWorker(new URL(...)); // URL
|
|
123
|
-
createEasyWebWorker(new Worker(...)); // Existing Worker
|
|
124
|
-
createEasyWebWorker([worker1, worker2]); // Existing Worker pool
|
|
153
|
+
const blurred = await images.blur(buffer, [buffer]);
|
|
125
154
|
```
|
|
126
155
|
|
|
127
|
-
|
|
156
|
+
[**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/)
|
|
128
157
|
|
|
129
|
-
|
|
158
|
+
### **Any way you create a Worker**
|
|
130
159
|
|
|
131
|
-
|
|
132
|
-
|
|
160
|
+
A Worker file, a native `Worker`, your own pool, or just a function. The API on top stays the same.
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
createWorker<MathWorker>(new URL('./math.worker.ts', import.meta.url)); // Worker file
|
|
164
|
+
createWorker<MathWorker>(workerUrl); // URL generated by your bundler
|
|
165
|
+
createWorker<MathWorker>(new Worker(...)); // existing Worker
|
|
166
|
+
createWorker<MathWorker>([worker1, worker2]); // existing pool
|
|
167
|
+
createWorker(() => ({ fibonacci })); // a function, no file at all
|
|
133
168
|
```
|
|
134
169
|
|
|
135
|
-
|
|
170
|
+
[**Every source, with the setup for each bundler โ**](https://johnny-quesada-developer.github.io/easy-web-worker/docs/creating-workers/)
|
|
136
171
|
|
|
137
|
-
|
|
138
|
-
> If your project still imports `cancelable-promise-jq`, replace it with `easy-cancelable-promise`. The old package name is deprecated.
|
|
172
|
+
### **No Worker file required**
|
|
139
173
|
|
|
140
|
-
|
|
174
|
+
Pass a function instead of a file. Same methods, same inferred types, nothing to configure.
|
|
141
175
|
|
|
142
|
-
|
|
176
|
+
```ts
|
|
177
|
+
const math = createWorker(() => {
|
|
178
|
+
const fibonacci = (n: number): number =>
|
|
179
|
+
n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2);
|
|
143
180
|
|
|
144
|
-
|
|
181
|
+
return { fibonacci };
|
|
182
|
+
});
|
|
145
183
|
|
|
146
|
-
|
|
147
|
-
|
|
184
|
+
await math.fibonacci(40); // 102334155, typed as number
|
|
185
|
+
```
|
|
148
186
|
|
|
149
|
-
|
|
150
|
-
const fibonacci = (n: number): number => {
|
|
151
|
-
if (n <= 1) return n;
|
|
152
|
-
return fibonacci(n - 1) + fibonacci(n - 2);
|
|
153
|
-
};
|
|
187
|
+
[**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/)
|
|
154
188
|
|
|
155
|
-
|
|
156
|
-
message.resolve(fibonacci(message.payload));
|
|
157
|
-
});
|
|
158
|
-
});
|
|
189
|
+
---
|
|
159
190
|
|
|
160
|
-
|
|
191
|
+
## Installation
|
|
161
192
|
|
|
162
|
-
|
|
193
|
+
```bash
|
|
194
|
+
npm install easy-web-worker
|
|
163
195
|
```
|
|
164
196
|
|
|
165
|
-
|
|
197
|
+
- **Any framework**: built on the native Worker API of the browser, with no dependency on React, Vue, Angular, Svelte or any other UI library.
|
|
198
|
+
- **Promises**: calls return a `CancelablePromise` from [`easy-cancelable-promise`](https://www.npmjs.com/package/easy-cancelable-promise), installed with the package.
|
|
166
199
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
Run [`jsdiff`](https://www.npmjs.com/package/diff) inside a Web Worker so text comparison never blocks the main thread.
|
|
200
|
+
---
|
|
170
201
|
|
|
171
|
-
|
|
202
|
+
## Quick Start
|
|
172
203
|
|
|
173
|
-
|
|
204
|
+
### Your first typed worker
|
|
174
205
|
|
|
175
206
|
```ts
|
|
176
|
-
//
|
|
177
|
-
import {
|
|
207
|
+
// math.worker.ts
|
|
208
|
+
import { defineWorker } from 'easy-web-worker/defineWorker';
|
|
178
209
|
|
|
179
|
-
const
|
|
180
|
-
(
|
|
181
|
-
onMessage((message, context) => {
|
|
182
|
-
const { input1, input2 } = message.payload;
|
|
210
|
+
const fibonacci = (n: number): number =>
|
|
211
|
+
n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2);
|
|
183
212
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
213
|
+
// 1. Define it (in the Worker file)
|
|
214
|
+
const worker = defineWorker(() => ({ fibonacci }));
|
|
215
|
+
|
|
216
|
+
export type MathWorker = typeof worker;
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
// main.ts
|
|
221
|
+
import { createWorker } from 'easy-web-worker/createWorker';
|
|
222
|
+
import type { MathWorker } from './math.worker';
|
|
223
|
+
|
|
224
|
+
// 2. Create it (on the main thread)
|
|
225
|
+
const math = createWorker<MathWorker>(
|
|
226
|
+
new URL('./math.worker.ts', import.meta.url),
|
|
187
227
|
{
|
|
188
|
-
|
|
228
|
+
workerOptions: { type: 'module' },
|
|
189
229
|
}
|
|
190
230
|
);
|
|
191
231
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
input2: 'Web Workers are incredibly powerful.',
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
console.log(result);
|
|
232
|
+
// 3. Call it
|
|
233
|
+
const result = await math.fibonacci(40); // 102334155
|
|
198
234
|
```
|
|
199
235
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
- โ
No separate Worker file
|
|
203
|
-
- โ
No npm import for jsdiff in your application bundle
|
|
204
|
-
- โ
No manual `importScripts()`
|
|
205
|
-
- โ
No `postMessage` / `onmessage` plumbing
|
|
206
|
-
- โ
Type-safe request and result
|
|
207
|
-
- โ
Real background execution
|
|
236
|
+
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.
|
|
208
237
|
|
|
209
|
-
|
|
238
|
+
> Shipping with a bundler? See [Every way to create a worker](#every-way-to-create-a-worker) for the production setup.
|
|
239
|
+
>
|
|
240
|
+
> 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.
|
|
210
241
|
|
|
211
|
-
|
|
242
|
+
### Progress and cancellation
|
|
212
243
|
|
|
213
|
-
|
|
244
|
+
Wrap a method with `onMessage` to receive the message of the call.
|
|
214
245
|
|
|
215
246
|
```ts
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
const easyWorker = new StaticEasyWebWorker();
|
|
247
|
+
// math.worker.ts
|
|
248
|
+
const worker = defineWorker(({ onMessage }) => ({
|
|
249
|
+
findPrimes: onMessage(async (limit: number, message) => {
|
|
250
|
+
const primes: number[] = [];
|
|
221
251
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
const { input1, input2 } = message.payload;
|
|
252
|
+
for (let candidate = 2; candidate <= limit; candidate++) {
|
|
253
|
+
if (isPrime(candidate)) primes.push(candidate);
|
|
225
254
|
|
|
226
|
-
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
easyWorker.onMessage<string, string>('uppercase', (message) => {
|
|
230
|
-
message.resolve(message.payload.toUpperCase());
|
|
231
|
-
});
|
|
232
|
-
```
|
|
255
|
+
if (candidate % 100_000 !== 0) continue;
|
|
233
256
|
|
|
234
|
-
|
|
257
|
+
// give the Worker a moment to receive a cancellation
|
|
258
|
+
await new Promise((resolve) => setTimeout(resolve));
|
|
235
259
|
|
|
236
|
-
|
|
237
|
-
import { EasyWebWorker } from 'easy-web-worker';
|
|
238
|
-
import workerUrl from './TextDiff.worker?worker&url';
|
|
260
|
+
if (!message.isPending()) break;
|
|
239
261
|
|
|
240
|
-
|
|
241
|
-
|
|
262
|
+
message.reportProgress((candidate / limit) * 100);
|
|
263
|
+
}
|
|
242
264
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
type: 'module',
|
|
248
|
-
},
|
|
249
|
-
}
|
|
250
|
-
);
|
|
265
|
+
return primes;
|
|
266
|
+
}),
|
|
267
|
+
}));
|
|
268
|
+
```
|
|
251
269
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
270
|
+
```ts
|
|
271
|
+
// main.ts
|
|
272
|
+
const task = math.findPrimes(50_000_000).onProgress((percentage) => {
|
|
273
|
+
progressBar.value = percentage;
|
|
255
274
|
});
|
|
256
|
-
```
|
|
257
275
|
|
|
258
|
-
|
|
276
|
+
cancelButton.onclick = () => task.cancel('Canceled by user');
|
|
259
277
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
| ๐ CDN + runtime Worker | Fastest setup, demos, small integrations, no Worker file |
|
|
263
|
-
| ๐ Static Worker | Module imports, larger Worker code, bundler control, production architecture |
|
|
278
|
+
const primes = await task;
|
|
279
|
+
```
|
|
264
280
|
|
|
265
|
-
|
|
281
|
+
Progress and cancellation belong to the call itself, not to a second channel.
|
|
266
282
|
|
|
267
|
-
|
|
268
|
-
- โ
Cancellation
|
|
269
|
-
- โ
Progress reporting
|
|
270
|
-
- โ
Named Worker methods
|
|
271
|
-
- โ
Worker pooling when needed
|
|
283
|
+
> 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.
|
|
272
284
|
|
|
273
285
|
---
|
|
274
286
|
|
|
275
|
-
##
|
|
287
|
+
## Core Features Deep Dive
|
|
276
288
|
|
|
277
|
-
###
|
|
289
|
+
### Typed workers with `defineWorker` and `createWorker`
|
|
278
290
|
|
|
279
|
-
|
|
291
|
+
Write the Worker as a set of methods and call them from the main thread like local async functions.
|
|
280
292
|
|
|
281
|
-
####
|
|
293
|
+
#### The Basics
|
|
282
294
|
|
|
283
295
|
```ts
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
({ onMessage }) => {
|
|
288
|
-
onMessage((message) => {
|
|
289
|
-
message.resolve(`Message from Worker: ${message.payload}`);
|
|
290
|
-
});
|
|
291
|
-
}
|
|
292
|
-
);
|
|
296
|
+
// text.worker.ts
|
|
297
|
+
const worker = defineWorker(() => ({
|
|
298
|
+
countWords: (text: string) => text.trim().split(/\s+/).length,
|
|
293
299
|
|
|
294
|
-
|
|
300
|
+
findDuplicates: (lines: string[]) =>
|
|
301
|
+
lines.filter((line, index) => lines.indexOf(line) !== index),
|
|
302
|
+
}));
|
|
295
303
|
|
|
296
|
-
|
|
304
|
+
export type TextWorker = typeof worker;
|
|
297
305
|
```
|
|
298
306
|
|
|
299
|
-
The first generic controls the `send()` payload. The second controls the value returned when the Worker calls `message.resolve()`.
|
|
300
|
-
|
|
301
307
|
```ts
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
#### ๐ฏ Named Worker Methods
|
|
308
|
+
// main.ts
|
|
309
|
+
const text = createWorker<TextWorker>(source);
|
|
306
310
|
|
|
307
|
-
|
|
311
|
+
await text.countWords(article); // number
|
|
312
|
+
await text.findDuplicates(lines); // string[]
|
|
313
|
+
```
|
|
308
314
|
|
|
309
|
-
|
|
310
|
-
const worker = createEasyWebWorker<string, string>(({ onMessage }) => {
|
|
311
|
-
onMessage((message) => {
|
|
312
|
-
message.resolve(`default: ${message.payload}`);
|
|
313
|
-
});
|
|
315
|
+
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.
|
|
314
316
|
|
|
315
|
-
|
|
316
|
-
message.resolve(message.payload * 2);
|
|
317
|
-
});
|
|
317
|
+
How the types cross the threads:
|
|
318
318
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
319
|
+
| In the Worker | On the main thread |
|
|
320
|
+
| --------------------------------- | ------------------------------------------------------------- |
|
|
321
|
+
| `() => string` | `() => CancelablePromise<string>` |
|
|
322
|
+
| `(value: number) => number` | `(payload: number, transfer?) => CancelablePromise<number>` |
|
|
323
|
+
| `async (ids: string[]) => User[]` | `(payload: string[], transfer?) => CancelablePromise<User[]>` |
|
|
324
|
+
| `(value?: number) => number` | `(payload?: number, transfer?) => CancelablePromise<number>` |
|
|
324
325
|
|
|
325
|
-
|
|
326
|
-
const doubled = await worker.sendToMethod<number, number>('double', 21);
|
|
327
|
-
const total = await worker.sendToMethod<
|
|
328
|
-
number,
|
|
329
|
-
{ first: number; second: number }
|
|
330
|
-
>('sum', { first: 20, second: 22 });
|
|
331
|
-
```
|
|
326
|
+
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`.
|
|
332
327
|
|
|
333
|
-
|
|
328
|
+
#### What it replaces
|
|
334
329
|
|
|
335
|
-
|
|
330
|
+
`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:
|
|
336
331
|
|
|
337
|
-
|
|
332
|
+
```ts
|
|
333
|
+
// fibonacci.worker.js
|
|
334
|
+
self.onmessage = ({ data: { id, n } }) => {
|
|
335
|
+
self.postMessage({ id, result: fibonacci(n) });
|
|
336
|
+
};
|
|
337
|
+
```
|
|
338
338
|
|
|
339
339
|
```ts
|
|
340
|
-
|
|
340
|
+
// main.js
|
|
341
|
+
const worker = new Worker(new URL('./fibonacci.worker.js', import.meta.url));
|
|
342
|
+
const pending = new Map();
|
|
343
|
+
let nextId = 0;
|
|
344
|
+
|
|
345
|
+
worker.onmessage = ({ data: { id, result } }) => {
|
|
346
|
+
pending.get(id)(result);
|
|
347
|
+
pending.delete(id);
|
|
348
|
+
};
|
|
341
349
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
350
|
+
const fibonacci = (n) =>
|
|
351
|
+
new Promise((resolve) => {
|
|
352
|
+
const id = nextId++;
|
|
353
|
+
|
|
354
|
+
pending.set(id, resolve);
|
|
355
|
+
worker.postMessage({ id, n });
|
|
346
356
|
});
|
|
347
|
-
});
|
|
348
357
|
```
|
|
349
358
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
- Defined inside the Worker body
|
|
353
|
-
- Included through reusable Worker templates
|
|
354
|
-
- Imported as a script
|
|
355
|
-
- Sent in a message
|
|
356
|
-
- Passed as a primitive parameter
|
|
357
|
-
|
|
358
|
-
#### ๐ฆ Primitive Parameters
|
|
359
|
+
And that is before errors, cancellation, progress, types, or a second method.
|
|
359
360
|
|
|
360
|
-
|
|
361
|
+
#### Return values and errors
|
|
361
362
|
|
|
362
363
|
```ts
|
|
363
|
-
|
|
364
|
+
// report.worker.ts
|
|
365
|
+
const worker = defineWorker(() => ({
|
|
366
|
+
parseReport: async (url: string) => {
|
|
367
|
+
const response = await fetch(url);
|
|
364
368
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
369
|
+
if (!response.ok) {
|
|
370
|
+
throw new Error(`Report not available: ${response.status}`);
|
|
371
|
+
}
|
|
368
372
|
|
|
369
|
-
|
|
370
|
-
message.resolve(`${prefix} complete`);
|
|
371
|
-
});
|
|
373
|
+
return buildReport(await response.text());
|
|
372
374
|
},
|
|
373
|
-
|
|
374
|
-
primitiveParameters: [prefix],
|
|
375
|
-
}
|
|
376
|
-
);
|
|
377
|
-
|
|
378
|
-
console.log(await worker.send()); // Result: complete
|
|
375
|
+
}));
|
|
379
376
|
```
|
|
380
377
|
|
|
381
|
-
|
|
378
|
+
```ts
|
|
379
|
+
// main.ts
|
|
380
|
+
try {
|
|
381
|
+
const report = await reports.parseReport('/reports/2026.csv');
|
|
382
|
+
} catch (error) {
|
|
383
|
+
console.log((error as Error).message); // 'Report not available: 404'
|
|
384
|
+
}
|
|
385
|
+
```
|
|
382
386
|
|
|
383
|
-
|
|
387
|
+
An error rejects that one call. The Worker keeps serving the next one.
|
|
384
388
|
|
|
385
|
-
|
|
389
|
+
#### Three ways to write a method
|
|
386
390
|
|
|
387
391
|
```ts
|
|
388
|
-
const worker =
|
|
389
|
-
|
|
390
|
-
|
|
392
|
+
const worker = defineWorker(({ onMessage }) => ({
|
|
393
|
+
// 1. a plain function
|
|
394
|
+
double: (value: number) => value * 2,
|
|
395
|
+
|
|
396
|
+
// 2. onMessage: same thing, with the message and the event typed for you
|
|
397
|
+
count: onMessage(async (to: number, message, event) => {
|
|
398
|
+
message.reportProgress(50);
|
|
399
|
+
|
|
400
|
+
return to;
|
|
401
|
+
}),
|
|
402
|
+
|
|
403
|
+
// 3. onMessage().handle: only the message, you complete it
|
|
404
|
+
wait: onMessage<number, string>().handle((message, event) => {
|
|
405
|
+
const timeoutId = setTimeout(
|
|
406
|
+
() => message.resolve('done'),
|
|
407
|
+
message.payload
|
|
408
|
+
);
|
|
409
|
+
|
|
410
|
+
message.onCancel(() => clearTimeout(timeoutId));
|
|
411
|
+
}),
|
|
412
|
+
}));
|
|
413
|
+
```
|
|
391
414
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
415
|
+
| Style | Completes the call | Types |
|
|
416
|
+
| --------------------------------------------- | ----------------------- | --------------------------------- |
|
|
417
|
+
| Plain function | With the returned value | Inferred |
|
|
418
|
+
| `onMessage((payload, message, event) => ...)` | With the returned value | Inferred, `message` typed |
|
|
419
|
+
| `onMessage<P, R>().handle((message) => ...)` | You do | Explicit, `message.resolve` typed |
|
|
396
420
|
|
|
397
|
-
|
|
398
|
-
});
|
|
399
|
-
});
|
|
421
|
+
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.
|
|
400
422
|
|
|
401
|
-
|
|
402
|
-
console.log(percentage);
|
|
403
|
-
});
|
|
404
|
-
```
|
|
423
|
+
If a method completes the message itself with `resolve`, `reject` or `cancel`, its return value is ignored.
|
|
405
424
|
|
|
406
|
-
|
|
425
|
+
#### The message
|
|
407
426
|
|
|
408
|
-
|
|
409
|
-
25
|
|
410
|
-
50
|
|
411
|
-
75
|
|
412
|
-
100
|
|
413
|
-
```
|
|
427
|
+
Available in `onMessage`, in `.handle(...)`, and in the message API.
|
|
414
428
|
|
|
415
|
-
|
|
429
|
+
| Member | Purpose |
|
|
430
|
+
| ------------------------------------------------- | --------------------------------------------- |
|
|
431
|
+
| `payload` | Value sent from the main thread |
|
|
432
|
+
| `resolve(result?, transfer?)` | Resolves the call |
|
|
433
|
+
| `reject(reason?, transfer?)` | Rejects the call |
|
|
434
|
+
| `cancel(reason?, transfer?)` | Cancels the call from inside the Worker |
|
|
435
|
+
| `reportProgress(percentage, payload?, transfer?)` | Reports progress and keeps the call pending |
|
|
436
|
+
| `onCancel`, `onResolve`, `onReject`, `onProgress` | Subscribe to each outcome; return unsubscribe |
|
|
437
|
+
| `onFinalize` | Runs once the call ends, whatever the outcome |
|
|
438
|
+
| `isPending()`, `getStatus()` | Current state of the call |
|
|
416
439
|
|
|
417
|
-
Cancellation
|
|
440
|
+
#### Cancellation
|
|
418
441
|
|
|
419
|
-
|
|
420
|
-
const worker = createEasyWebWorker<number, number>(({ onMessage }) => {
|
|
421
|
-
onMessage((message) => {
|
|
422
|
-
const interval = setInterval(() => {
|
|
423
|
-
// long-running work...
|
|
424
|
-
}, 100);
|
|
442
|
+
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()`.
|
|
425
443
|
|
|
426
|
-
|
|
427
|
-
clearInterval(interval);
|
|
444
|
+
Canceling twice, or after the call finished, does nothing.
|
|
428
445
|
|
|
429
|
-
|
|
446
|
+
A Worker that never pauses cannot receive a cancellation. See [Stop work that never pauses](#stop-work-that-never-pauses).
|
|
430
447
|
|
|
431
|
-
|
|
432
|
-
});
|
|
433
|
-
});
|
|
434
|
-
});
|
|
448
|
+
#### Transferable objects
|
|
435
449
|
|
|
436
|
-
|
|
450
|
+
```ts
|
|
451
|
+
// main.ts
|
|
452
|
+
const buffer = await file.arrayBuffer();
|
|
437
453
|
|
|
438
|
-
|
|
454
|
+
const blurred = await images.blur(buffer, [buffer]);
|
|
439
455
|
```
|
|
440
456
|
|
|
441
|
-
|
|
457
|
+
Returned values are cloned. To transfer a result back, complete the call yourself:
|
|
442
458
|
|
|
443
459
|
```ts
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
});
|
|
460
|
+
// images.worker.ts
|
|
461
|
+
const worker = defineWorker(({ onMessage }) => ({
|
|
462
|
+
blur: onMessage<ArrayBuffer, ArrayBuffer>().handle((message) => {
|
|
463
|
+
const result = applyBlur(message.payload);
|
|
464
|
+
|
|
465
|
+
message.resolve(result, [result]);
|
|
466
|
+
}),
|
|
467
|
+
}));
|
|
452
468
|
```
|
|
453
469
|
|
|
454
|
-
####
|
|
470
|
+
#### `unwrap`: the controls behind the methods
|
|
455
471
|
|
|
456
|
-
|
|
472
|
+
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.
|
|
457
473
|
|
|
458
474
|
```ts
|
|
459
|
-
|
|
460
|
-
const unsubscribeResolve = message.onResolve(() => {});
|
|
461
|
-
const unsubscribeReject = message.onReject(() => {});
|
|
462
|
-
const unsubscribeCancel = message.onCancel(() => {});
|
|
463
|
-
const unsubscribeProgress = message.onProgress(() => {});
|
|
464
|
-
const unsubscribeFinalize = message.onFinalize(() => {});
|
|
465
|
-
|
|
466
|
-
const status = message.getStatus();
|
|
467
|
-
const pending = message.isPending();
|
|
468
|
-
|
|
469
|
-
// Unsubscribe whenever you no longer need a listener.
|
|
470
|
-
unsubscribeResolve();
|
|
471
|
-
unsubscribeReject();
|
|
472
|
-
unsubscribeCancel();
|
|
473
|
-
unsubscribeProgress();
|
|
474
|
-
unsubscribeFinalize();
|
|
475
|
+
import { createWorker, unwrap } from 'easy-web-worker/createWorker';
|
|
475
476
|
|
|
476
|
-
|
|
477
|
-
|
|
477
|
+
const math = createWorker<MathWorker>(source);
|
|
478
|
+
|
|
479
|
+
await unwrap(math).cancelAll('Leaving the page');
|
|
480
|
+
await unwrap(math).reboot();
|
|
481
|
+
await unwrap(math).dispose();
|
|
478
482
|
```
|
|
479
483
|
|
|
480
|
-
|
|
484
|
+
| `EasyWebWorker` method | Purpose |
|
|
485
|
+
| -------------------------------------------------------------- | --------------------------------------------------------------- |
|
|
486
|
+
| `send(payload?, transfer?)` | Sends a message to the default handler |
|
|
487
|
+
| `sendToMethod<TResult, TPayload>(method, payload?, transfer?)` | Sends a message to a named method |
|
|
488
|
+
| `cancelAll(reason?, { force? })` | Cancels every pending call; `force` restarts instead of waiting |
|
|
489
|
+
| `override(payload?, reason?, { force? })` | Cancels the pending calls and sends a new message |
|
|
490
|
+
| `overrideAfterCurrent(payload?, reason?)` | Keeps the current call, cancels the rest, sends a new message |
|
|
491
|
+
| `reboot(reason?)` | Terminates the Workers, rejects pending calls, starts again |
|
|
492
|
+
| `dispose()` | Cancels pending calls and releases the Workers |
|
|
481
493
|
|
|
482
|
-
|
|
494
|
+
The same helper works inside the Worker file, where it returns the Worker-side controls:
|
|
483
495
|
|
|
484
496
|
```ts
|
|
485
|
-
|
|
486
|
-
buffer: ArrayBuffer;
|
|
487
|
-
};
|
|
497
|
+
import { defineWorker, unwrap } from 'easy-web-worker/defineWorker';
|
|
488
498
|
|
|
489
|
-
const worker =
|
|
490
|
-
|
|
491
|
-
|
|
499
|
+
const worker = defineWorker(() => ({
|
|
500
|
+
shutdown: () => unwrap(worker).close(),
|
|
501
|
+
}));
|
|
492
502
|
|
|
493
|
-
|
|
503
|
+
unwrap(worker).importScripts('https://example.com/library.js');
|
|
494
504
|
|
|
495
|
-
|
|
496
|
-
|
|
505
|
+
// handles messages sent without a method: send, override, overrideAfterCurrent
|
|
506
|
+
unwrap(worker).onMessage((message) => {
|
|
507
|
+
message.resolve();
|
|
497
508
|
});
|
|
498
|
-
|
|
499
|
-
const buffer = new ArrayBuffer(1_000_000);
|
|
500
|
-
|
|
501
|
-
const processedBuffer = await worker.send({ buffer }, [buffer]);
|
|
502
509
|
```
|
|
503
510
|
|
|
504
|
-
|
|
511
|
+
One name is not available for a method: `then`. The worker object is not a promise, so `await math` never sends a message.
|
|
505
512
|
|
|
506
|
-
|
|
513
|
+
#### Entry points
|
|
507
514
|
|
|
508
|
-
|
|
515
|
+
Import each side from its own entry, so the Worker bundle carries no main-thread code:
|
|
509
516
|
|
|
510
|
-
|
|
517
|
+
| Entry | Use it in | Exports |
|
|
518
|
+
| ------------------------------ | --------------- | ------------------------------- |
|
|
519
|
+
| `easy-web-worker/defineWorker` | The Worker file | `defineWorker`, `unwrap`, types |
|
|
520
|
+
| `easy-web-worker/createWorker` | The main thread | `createWorker`, `unwrap`, types |
|
|
511
521
|
|
|
512
|
-
|
|
522
|
+
Everything is also exported from the root `easy-web-worker` entry.
|
|
513
523
|
|
|
514
|
-
|
|
524
|
+
> **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/)
|
|
515
525
|
|
|
516
|
-
|
|
517
|
-
import { createStaticEasyWebWorker } from 'easy-web-worker/createStaticEasyWebWorker';
|
|
526
|
+
---
|
|
518
527
|
|
|
519
|
-
|
|
528
|
+
### Every way to create a worker
|
|
520
529
|
|
|
521
|
-
|
|
522
|
-
message.resolve(message.payload * 2);
|
|
523
|
-
});
|
|
530
|
+
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.
|
|
524
531
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
532
|
+
| Source | Best for | Pool with `maxWorkers` | `reboot` / `force` |
|
|
533
|
+
| ---------------------- | ----------------------------------------------- | ---------------------- | ------------------ |
|
|
534
|
+
| `URL` of a Worker file | Worker files in a bundled project | โ
| โ
|
|
|
535
|
+
| `string` path | A Worker file you serve yourself | โ
| โ
|
|
|
536
|
+
| Existing `Worker` | Bundlers that detect `new Worker(new URL(...))` | One Worker | โ |
|
|
537
|
+
| Existing `Worker[]` | Bringing your own pool | The array is the pool | โ |
|
|
538
|
+
| Function | Workers with no file at all | โ
| โ
|
|
|
539
|
+
| Function array | Workers composed from reusable pieces | โ
| โ
|
|
|
529
540
|
|
|
530
|
-
|
|
541
|
+
#### A Worker file in development
|
|
531
542
|
|
|
532
543
|
```ts
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
544
|
+
const math = createWorker<MathWorker>(
|
|
545
|
+
new URL('./math.worker.ts', import.meta.url),
|
|
546
|
+
{
|
|
547
|
+
workerOptions: { type: 'module' },
|
|
548
|
+
}
|
|
549
|
+
);
|
|
538
550
|
```
|
|
539
551
|
|
|
540
|
-
|
|
552
|
+
`type: 'module'` is required when the Worker file uses `import` or `export`, which every `defineWorker` file does.
|
|
541
553
|
|
|
542
|
-
####
|
|
554
|
+
#### Vite: development and production
|
|
555
|
+
|
|
556
|
+
In development, Vite serves the TypeScript file. A production build needs the Worker URL that Vite generates:
|
|
543
557
|
|
|
544
558
|
```ts
|
|
545
|
-
import
|
|
546
|
-
import
|
|
559
|
+
import { createWorker } from 'easy-web-worker/createWorker';
|
|
560
|
+
import workerUrl from './math.worker?worker&url';
|
|
561
|
+
import type { MathWorker } from './math.worker';
|
|
547
562
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
? workerUrl
|
|
551
|
-
: new URL('./worker.ts', import.meta.url);
|
|
563
|
+
// the Worker URL in production, the TypeScript file in development
|
|
564
|
+
const isProduction = import.meta.env.MODE === 'production';
|
|
552
565
|
|
|
553
|
-
const
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
}
|
|
566
|
+
const math = createWorker<MathWorker>(
|
|
567
|
+
isProduction ? workerUrl : new URL('./math.worker.ts', import.meta.url),
|
|
568
|
+
{
|
|
569
|
+
workerOptions: { type: 'module' },
|
|
570
|
+
}
|
|
571
|
+
);
|
|
558
572
|
```
|
|
559
573
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
Already have a `Worker` instance? Wrap it directly.
|
|
574
|
+
The same source works with the message API:
|
|
563
575
|
|
|
564
576
|
```ts
|
|
565
|
-
|
|
566
|
-
type: 'module',
|
|
567
|
-
});
|
|
577
|
+
import { EasyWebWorker } from 'easy-web-worker';
|
|
568
578
|
|
|
569
|
-
const worker =
|
|
579
|
+
const worker = new EasyWebWorker(
|
|
580
|
+
isProduction ? workerUrl : new URL('./TextDiff.worker.ts', import.meta.url),
|
|
581
|
+
{
|
|
582
|
+
workerOptions: { type: 'module' },
|
|
583
|
+
}
|
|
584
|
+
);
|
|
570
585
|
```
|
|
571
586
|
|
|
572
|
-
|
|
587
|
+
#### A native Worker you create
|
|
588
|
+
|
|
589
|
+
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:
|
|
573
590
|
|
|
574
591
|
```ts
|
|
575
|
-
const
|
|
576
|
-
new Worker('./worker
|
|
577
|
-
|
|
578
|
-
]);
|
|
592
|
+
const math = createWorker<MathWorker>(
|
|
593
|
+
new Worker(new URL('./math.worker.ts', import.meta.url), { type: 'module' })
|
|
594
|
+
);
|
|
579
595
|
```
|
|
580
596
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
---
|
|
584
|
-
|
|
585
|
-
### 3๏ธโฃ Concurrency & Worker Pool
|
|
597
|
+
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.
|
|
586
598
|
|
|
587
|
-
|
|
599
|
+
#### Your own pool
|
|
588
600
|
|
|
589
|
-
|
|
601
|
+
Pass an array of native Workers and calls are distributed across them:
|
|
590
602
|
|
|
591
603
|
```ts
|
|
592
|
-
const
|
|
593
|
-
({
|
|
594
|
-
const fibonacci = (n: number): number => {
|
|
595
|
-
if (n <= 1) return n;
|
|
596
|
-
return fibonacci(n - 1) + fibonacci(n - 2);
|
|
597
|
-
};
|
|
598
|
-
|
|
599
|
-
onMessage((message) => {
|
|
600
|
-
message.resolve(fibonacci(message.payload));
|
|
601
|
-
});
|
|
602
|
-
},
|
|
603
|
-
{
|
|
604
|
-
maxWorkers: 4,
|
|
605
|
-
}
|
|
606
|
-
);
|
|
604
|
+
const createMathWorker = () =>
|
|
605
|
+
new Worker(new URL('./math.worker.ts', import.meta.url), { type: 'module' });
|
|
607
606
|
|
|
608
|
-
const
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
607
|
+
const math = createWorker<MathWorker>([
|
|
608
|
+
createMathWorker(),
|
|
609
|
+
createMathWorker(),
|
|
610
|
+
createMathWorker(),
|
|
612
611
|
]);
|
|
613
612
|
```
|
|
614
613
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
#### ๐ฅ Warm Up the Pool
|
|
614
|
+
#### A file you serve yourself
|
|
618
615
|
|
|
619
|
-
|
|
616
|
+
A Worker script that is already built and public only needs its path:
|
|
620
617
|
|
|
621
618
|
```ts
|
|
622
|
-
const
|
|
623
|
-
maxWorkers: 4,
|
|
624
|
-
warmUpWorkers: true,
|
|
625
|
-
});
|
|
619
|
+
const math = createWorker<MathWorker>('/workers/math.worker.js');
|
|
626
620
|
```
|
|
627
621
|
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
#### ๐ค Dispose Idle Workers Automatically
|
|
622
|
+
#### A function, with no file at all
|
|
631
623
|
|
|
632
624
|
```ts
|
|
633
|
-
const
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
terminationDelay: 5_000,
|
|
637
|
-
});
|
|
638
|
-
```
|
|
625
|
+
const math = createWorker(() => ({
|
|
626
|
+
double: (value: number) => value * 2,
|
|
627
|
+
}));
|
|
639
628
|
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
#### โ๏ธ Concurrency Defaults
|
|
629
|
+
await math.double(21); // 42
|
|
630
|
+
```
|
|
643
631
|
|
|
644
|
-
|
|
645
|
-
| -------------------------- | --------------------------------------------------------------------------- |
|
|
646
|
-
| `maxWorkers` | Defaults to `1` |
|
|
647
|
-
| `warmUpWorkers` | Defaults to `true` for a single-worker configuration; otherwise `false` |
|
|
648
|
-
| `keepAlive` | Defaults to the resolved `warmUpWorkers` value unless explicitly configured |
|
|
649
|
-
| `terminationDelay` | Defaults to `1000` ms |
|
|
650
|
-
| Existing `Worker[]` source | Pool size comes from the supplied array and the workers are kept alive |
|
|
632
|
+
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.
|
|
651
633
|
|
|
652
|
-
|
|
634
|
+
> **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/)
|
|
653
635
|
|
|
654
636
|
---
|
|
655
637
|
|
|
656
|
-
###
|
|
638
|
+
### Worker pools
|
|
657
639
|
|
|
658
|
-
|
|
640
|
+
One Worker runs one task at a time. A pool runs several at once, behind the same object.
|
|
659
641
|
|
|
660
|
-
|
|
661
|
-
import { createEasyWebWorker, type EasyWebWorkerBody } from 'easy-web-worker';
|
|
642
|
+
#### Scale on demand
|
|
662
643
|
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
const worker = createEasyWebWorker([
|
|
668
|
-
commonTools,
|
|
669
|
-
({ onMessage }, context) => {
|
|
670
|
-
onMessage(async (message) => {
|
|
671
|
-
const result = await context.doSomething();
|
|
644
|
+
```ts
|
|
645
|
+
const math = createWorker<MathWorker>(source, {
|
|
646
|
+
maxWorkers: 4,
|
|
647
|
+
});
|
|
672
648
|
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
649
|
+
// three calls, three threads, at the same time
|
|
650
|
+
const results = await Promise.all([
|
|
651
|
+
math.fibonacci(40),
|
|
652
|
+
math.fibonacci(41),
|
|
653
|
+
math.fibonacci(42),
|
|
676
654
|
]);
|
|
677
|
-
|
|
678
|
-
await worker.send();
|
|
679
655
|
```
|
|
680
656
|
|
|
681
|
-
|
|
657
|
+
The calling code does not change. It does not know whether one Worker or four are behind it.
|
|
682
658
|
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
### 5๏ธโฃ Import Scripts into Runtime Workers
|
|
659
|
+
Workers are created as concurrent calls arrive, so an idle application pays for none of them.
|
|
686
660
|
|
|
687
|
-
|
|
661
|
+
#### Warm up or release the pool
|
|
688
662
|
|
|
689
663
|
```ts
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
message.resolve();
|
|
695
|
-
});
|
|
696
|
-
},
|
|
697
|
-
{
|
|
698
|
-
scripts: ['https://example.com/worker-library.js'],
|
|
699
|
-
}
|
|
700
|
-
);
|
|
701
|
-
```
|
|
702
|
-
|
|
703
|
-
For example, if the imported script adds something to the Worker global scope:
|
|
664
|
+
createWorker<MathWorker>(source, {
|
|
665
|
+
maxWorkers: 4,
|
|
666
|
+
warmUpWorkers: true, // create the whole pool up front, for the fastest first call
|
|
667
|
+
});
|
|
704
668
|
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
669
|
+
createWorker<MathWorker>(source, {
|
|
670
|
+
maxWorkers: 4,
|
|
671
|
+
keepAlive: false, // terminate idle Workers...
|
|
672
|
+
terminationDelay: 5_000, // ...after five seconds without work
|
|
673
|
+
});
|
|
709
674
|
```
|
|
710
675
|
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
---
|
|
714
|
-
|
|
715
|
-
## ๐ฅ Advanced Patterns
|
|
716
|
-
|
|
717
|
-
### ๐๏ธ Build a Worker API with Named Methods
|
|
718
|
-
|
|
719
|
-
```ts
|
|
720
|
-
type User = {
|
|
721
|
-
id: string;
|
|
722
|
-
name: string;
|
|
723
|
-
};
|
|
724
|
-
|
|
725
|
-
const worker = createEasyWebWorker(({ onMessage }) => {
|
|
726
|
-
const users = new Map<string, User>();
|
|
676
|
+
#### Configuration
|
|
727
677
|
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
678
|
+
| Option | Default | Purpose |
|
|
679
|
+
| --------------------- | ----------------------- | ---------------------------------------------------- |
|
|
680
|
+
| `maxWorkers` | `1` | Maximum number of native Workers |
|
|
681
|
+
| `warmUpWorkers` | `true` for one Worker | Creates the pool during initialization |
|
|
682
|
+
| `keepAlive` | Same as `warmUpWorkers` | Keeps idle Workers alive |
|
|
683
|
+
| `terminationDelay` | `1000` ms | Wait before terminating idle Workers |
|
|
684
|
+
| `workerOptions` | `{}` | Native `WorkerOptions`, such as `{ type: 'module' }` |
|
|
685
|
+
| `onWorkerError` | Rethrows | Receives the uncaught errors of the Worker |
|
|
686
|
+
| `scripts` | `[]` | Scripts imported into a runtime Worker |
|
|
687
|
+
| `primitiveParameters` | `[]` | Static values for a runtime Worker, in `context` |
|
|
732
688
|
|
|
733
|
-
|
|
734
|
-
message.resolve(users.get(message.payload) ?? null);
|
|
735
|
-
});
|
|
689
|
+
The same options apply to `createWorker`, `createEasyWebWorker` and `new EasyWebWorker(...)`.
|
|
736
690
|
|
|
737
|
-
|
|
738
|
-
message.resolve(users.delete(message.payload));
|
|
739
|
-
});
|
|
740
|
-
});
|
|
691
|
+
> **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/)
|
|
741
692
|
|
|
742
|
-
|
|
743
|
-
id: '42',
|
|
744
|
-
name: 'Johnny',
|
|
745
|
-
});
|
|
746
|
-
|
|
747
|
-
const user = await worker.sendToMethod<User | null, string>('getUser', '42');
|
|
748
|
-
```
|
|
693
|
+
---
|
|
749
694
|
|
|
750
|
-
|
|
695
|
+
### Runtime Workers: a function as the Worker
|
|
751
696
|
|
|
752
|
-
|
|
697
|
+
Give `createWorker` a function instead of a file. No extra file, no bundler configuration.
|
|
753
698
|
|
|
754
|
-
The
|
|
699
|
+
#### The Basics
|
|
755
700
|
|
|
756
701
|
```ts
|
|
757
|
-
|
|
702
|
+
import { createWorker } from 'easy-web-worker/createWorker';
|
|
758
703
|
|
|
759
|
-
const
|
|
760
|
-
const
|
|
761
|
-
(
|
|
762
|
-
);
|
|
704
|
+
const math = createWorker(({ onMessage }) => {
|
|
705
|
+
const fibonacci = (n: number): number =>
|
|
706
|
+
n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2);
|
|
763
707
|
|
|
764
|
-
|
|
765
|
-
|
|
708
|
+
return {
|
|
709
|
+
fibonacci,
|
|
766
710
|
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
711
|
+
countTo: onMessage(async (limit: number, message) => {
|
|
712
|
+
for (let step = 1; step <= limit; step++) {
|
|
713
|
+
message.reportProgress((step * 100) / limit);
|
|
714
|
+
}
|
|
770
715
|
|
|
771
|
-
|
|
716
|
+
return limit;
|
|
717
|
+
}),
|
|
772
718
|
};
|
|
719
|
+
});
|
|
773
720
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
const filter = message.payload.trim().toLowerCase();
|
|
721
|
+
const result = await math.fibonacci(40); // 102334155
|
|
722
|
+
```
|
|
777
723
|
|
|
778
|
-
|
|
779
|
-
message.reportProgress(((index + 1) / items.length) * 100);
|
|
724
|
+
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.
|
|
780
725
|
|
|
781
|
-
|
|
782
|
-
});
|
|
726
|
+
Everything else works the same: cancellation, progress, transferable objects, `unwrap`, and pools with `maxWorkers`.
|
|
783
727
|
|
|
784
|
-
|
|
785
|
-
});
|
|
786
|
-
});
|
|
787
|
-
```
|
|
728
|
+
#### Worker scope
|
|
788
729
|
|
|
789
|
-
|
|
730
|
+
The function becomes the source of a real Worker, so it **cannot use variables from outside**.
|
|
790
731
|
|
|
791
732
|
```ts
|
|
792
|
-
const
|
|
793
|
-
.send('johnny')
|
|
794
|
-
.onProgress((percentage) => console.log(percentage));
|
|
733
|
+
const greeting = 'Hello';
|
|
795
734
|
|
|
796
|
-
|
|
735
|
+
createWorker(() => ({
|
|
736
|
+
// `greeting` does not exist inside the Worker
|
|
737
|
+
greet: (name: string) => `${greeting} ${name}`,
|
|
738
|
+
}));
|
|
797
739
|
```
|
|
798
740
|
|
|
799
|
-
|
|
741
|
+
Everything the Worker needs must be defined inside the function, imported as a script, sent as a payload, or passed through `primitiveParameters`.
|
|
800
742
|
|
|
801
|
-
|
|
743
|
+
#### Parameters and the scope of the Worker
|
|
744
|
+
|
|
745
|
+
The second parameter is the global scope of the Worker. Static values arrive in `context.primitiveParameters`:
|
|
802
746
|
|
|
803
747
|
```ts
|
|
804
|
-
const
|
|
805
|
-
|
|
806
|
-
|
|
748
|
+
const worker = createWorker(
|
|
749
|
+
(_helpers, context) => {
|
|
750
|
+
const [prefix] = context.primitiveParameters;
|
|
751
|
+
|
|
752
|
+
return {
|
|
753
|
+
label: (text: string) => `${prefix} ${text}`,
|
|
754
|
+
};
|
|
755
|
+
},
|
|
756
|
+
{
|
|
757
|
+
primitiveParameters: ['Result:'] as [string],
|
|
758
|
+
}
|
|
807
759
|
);
|
|
760
|
+
|
|
761
|
+
await worker.label('complete'); // 'Result: complete'
|
|
808
762
|
```
|
|
809
763
|
|
|
810
|
-
|
|
764
|
+
#### The message API inside the function
|
|
811
765
|
|
|
812
|
-
|
|
766
|
+
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.
|
|
813
767
|
|
|
814
768
|
```ts
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
>
|
|
769
|
+
const worker = createWorker<{
|
|
770
|
+
double: (value: number) => number;
|
|
771
|
+
triple: (value: number) => number;
|
|
772
|
+
}>(({ easyWorker }) => {
|
|
773
|
+
// named handler, message style
|
|
774
|
+
easyWorker.onMessage<number, number>('double', (message) => {
|
|
775
|
+
message.resolve(message.payload * 2);
|
|
776
|
+
});
|
|
819
777
|
|
|
820
|
-
|
|
778
|
+
// handler for messages sent without a method
|
|
779
|
+
easyWorker.onMessage((message) => {
|
|
780
|
+
message.resolve();
|
|
781
|
+
});
|
|
821
782
|
|
|
822
|
-
|
|
783
|
+
return {
|
|
784
|
+
triple: (value: number) => value * 3,
|
|
785
|
+
shutdown: () => easyWorker.close(),
|
|
786
|
+
};
|
|
787
|
+
});
|
|
823
788
|
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
latestPayload,
|
|
827
|
-
'Queue replaced'
|
|
828
|
-
);
|
|
789
|
+
await worker.double(21); // 42
|
|
790
|
+
await worker.triple(21); // 63
|
|
829
791
|
```
|
|
830
792
|
|
|
831
|
-
|
|
793
|
+
Only the returned methods are inferred. Handlers registered through `easyWorker` are described in the generic, as above, or called with `unwrap(worker).sendToMethod(...)`.
|
|
832
794
|
|
|
833
|
-
|
|
795
|
+
#### Import scripts
|
|
834
796
|
|
|
835
|
-
|
|
797
|
+
External libraries load with the `scripts` option and appear in the scope of the Worker:
|
|
836
798
|
|
|
837
799
|
```ts
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
800
|
+
const text = createWorker(
|
|
801
|
+
(_helpers, context) => {
|
|
802
|
+
// the script adds `Diff` to the scope of the Worker
|
|
803
|
+
const { diffWords } = context.Diff as {
|
|
804
|
+
diffWords: (before: string, after: string) => unknown[];
|
|
805
|
+
};
|
|
842
806
|
|
|
843
|
-
|
|
807
|
+
return {
|
|
808
|
+
compare: ({ before, after }: { before: string; after: string }) =>
|
|
809
|
+
diffWords(before, after),
|
|
810
|
+
};
|
|
811
|
+
},
|
|
812
|
+
{
|
|
813
|
+
scripts: ['https://cdn.jsdelivr.net/npm/diff@9.0.0/dist/diff.min.js'],
|
|
814
|
+
}
|
|
815
|
+
);
|
|
844
816
|
|
|
845
|
-
|
|
817
|
+
const changes = await text.compare({ before, after });
|
|
818
|
+
```
|
|
846
819
|
|
|
847
|
-
|
|
820
|
+
No Worker file is required, and the script does not become part of your application bundle.
|
|
848
821
|
|
|
849
|
-
|
|
822
|
+
#### Compose a Worker from several functions
|
|
850
823
|
|
|
851
|
-
|
|
824
|
+
Pass an array and every function runs in the same Worker. They share its scope, and the methods they return are merged.
|
|
852
825
|
|
|
853
826
|
```ts
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
Worker[]
|
|
860
|
-
```
|
|
827
|
+
const worker = createWorker([
|
|
828
|
+
// reusable piece: leaves a helper in the scope of the Worker
|
|
829
|
+
(_helpers, context) => {
|
|
830
|
+
context.round = (value: number) => Math.round(value * 100) / 100;
|
|
831
|
+
},
|
|
861
832
|
|
|
862
|
-
|
|
833
|
+
() => ({
|
|
834
|
+
double: (value: number) => value * 2,
|
|
835
|
+
describe: () => 'first',
|
|
836
|
+
}),
|
|
863
837
|
|
|
864
|
-
|
|
865
|
-
const
|
|
866
|
-
```
|
|
838
|
+
(_helpers, context) => {
|
|
839
|
+
const round = context.round as (value: number) => number;
|
|
867
840
|
|
|
868
|
-
|
|
841
|
+
return {
|
|
842
|
+
average: (values: number[]) =>
|
|
843
|
+
round(values.reduce((total, value) => total + value, 0) / values.length),
|
|
869
844
|
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
845
|
+
// repeated method: the last function wins
|
|
846
|
+
describe: () => 'last',
|
|
847
|
+
};
|
|
848
|
+
},
|
|
849
|
+
]);
|
|
874
850
|
|
|
875
|
-
|
|
851
|
+
await worker.double(21); // 42
|
|
852
|
+
await worker.average([1, 2, 2]); // 1.67
|
|
853
|
+
await worker.describe(); // 'last'
|
|
876
854
|
```
|
|
877
855
|
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
| Option | Purpose |
|
|
881
|
-
| --------------------- | ------------------------------------------------------------------------------------------------------------------------ |
|
|
882
|
-
| `scripts` | Scripts to include in a runtime Worker |
|
|
883
|
-
| `workerOptions` | Native [`WorkerOptions`](https://developer.mozilla.org/en-US/docs/Web/API/Worker/Worker) passed when a Worker is created |
|
|
884
|
-
| `onWorkerError` | Handles native Worker errors |
|
|
885
|
-
| `maxWorkers` | Maximum number of native Workers in the pool |
|
|
886
|
-
| `keepAlive` | Keep created Workers alive after work completes |
|
|
887
|
-
| `terminationDelay` | Delay before idle Workers are terminated when `keepAlive` is `false` |
|
|
888
|
-
| `warmUpWorkers` | Create the configured Worker pool during initialization |
|
|
889
|
-
| `primitiveParameters` | Static primitive initialization values exposed through `context.primitiveParameters` |
|
|
856
|
+
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.
|
|
890
857
|
|
|
891
|
-
|
|
858
|
+
#### Runtime Workers with the message API
|
|
892
859
|
|
|
893
|
-
|
|
860
|
+
`createEasyWebWorker` builds a Worker from a function too, with messages instead of methods:
|
|
894
861
|
|
|
895
862
|
```ts
|
|
896
|
-
|
|
897
|
-
```
|
|
863
|
+
import { createEasyWebWorker } from 'easy-web-worker';
|
|
898
864
|
|
|
899
|
-
|
|
865
|
+
const worker = createEasyWebWorker<number, number>(({ onMessage }) => {
|
|
866
|
+
onMessage((message) => {
|
|
867
|
+
message.resolve(message.payload * 2);
|
|
868
|
+
});
|
|
869
|
+
});
|
|
900
870
|
|
|
901
|
-
|
|
902
|
-
const result = await worker.send(payload, [buffer]);
|
|
871
|
+
await worker.send(21); // 42
|
|
903
872
|
```
|
|
904
873
|
|
|
905
|
-
|
|
874
|
+
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.
|
|
906
875
|
|
|
907
|
-
|
|
876
|
+
> **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/)
|
|
908
877
|
|
|
909
|
-
|
|
878
|
+
---
|
|
910
879
|
|
|
911
|
-
|
|
912
|
-
const result = await worker.sendToMethod<Result, Payload>('calculate', payload);
|
|
913
|
-
```
|
|
880
|
+
### The message API with `StaticEasyWebWorker`
|
|
914
881
|
|
|
915
|
-
|
|
882
|
+
`defineWorker` is built on a message API that you can use directly, in Worker files and in runtime Workers.
|
|
916
883
|
|
|
917
|
-
|
|
884
|
+
#### The Basics
|
|
918
885
|
|
|
919
886
|
```ts
|
|
920
|
-
|
|
921
|
-
|
|
887
|
+
// worker.ts
|
|
888
|
+
import { createStaticEasyWebWorker } from 'easy-web-worker/createStaticEasyWebWorker';
|
|
922
889
|
|
|
923
|
-
|
|
890
|
+
const { onMessage } = createStaticEasyWebWorker<number, number>((message) => {
|
|
891
|
+
message.resolve(message.payload * 2);
|
|
892
|
+
});
|
|
924
893
|
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
force: true,
|
|
894
|
+
onMessage<string, string>('uppercase', (message) => {
|
|
895
|
+
message.resolve(message.payload.toUpperCase());
|
|
928
896
|
});
|
|
929
897
|
```
|
|
930
898
|
|
|
931
|
-
|
|
899
|
+
```ts
|
|
900
|
+
// main.ts
|
|
901
|
+
import { createEasyWebWorker } from 'easy-web-worker';
|
|
932
902
|
|
|
933
|
-
|
|
903
|
+
const worker = createEasyWebWorker<number, number>(source);
|
|
934
904
|
|
|
935
|
-
|
|
936
|
-
|
|
905
|
+
await worker.send(21); // default handler
|
|
906
|
+
await worker.sendToMethod<string, string>('uppercase', 'hello'); // result type first
|
|
937
907
|
```
|
|
938
908
|
|
|
939
|
-
|
|
909
|
+
#### Typed methods for an existing Worker
|
|
940
910
|
|
|
941
|
-
|
|
911
|
+
A Worker written with the message API can still be called with typed methods. Describe them:
|
|
942
912
|
|
|
943
913
|
```ts
|
|
944
|
-
const
|
|
945
|
-
```
|
|
914
|
+
const worker = createWorker<{ uppercase: (text: string) => string }>(source);
|
|
946
915
|
|
|
947
|
-
|
|
916
|
+
await worker.uppercase('hello');
|
|
917
|
+
```
|
|
948
918
|
|
|
949
|
-
|
|
919
|
+
> **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/)
|
|
950
920
|
|
|
951
|
-
|
|
952
|
-
worker.reboot('Worker configuration reset');
|
|
953
|
-
```
|
|
921
|
+
---
|
|
954
922
|
|
|
955
|
-
|
|
923
|
+
## Advanced Patterns
|
|
956
924
|
|
|
957
|
-
###
|
|
925
|
+
### Latest request wins
|
|
958
926
|
|
|
959
|
-
|
|
927
|
+
For search, previews and autocomplete, an older request is worthless once a newer one exists.
|
|
960
928
|
|
|
961
929
|
```ts
|
|
962
|
-
|
|
963
|
-
```
|
|
930
|
+
const search = createEasyWebWorker<string, SearchResult[]>(searchBody);
|
|
964
931
|
|
|
965
|
-
|
|
932
|
+
input.oninput = async () => {
|
|
933
|
+
// cancels what is pending, then sends the new message
|
|
934
|
+
const results = await search.override(input.value, 'Superseded');
|
|
966
935
|
|
|
967
|
-
|
|
936
|
+
render(results);
|
|
937
|
+
};
|
|
938
|
+
```
|
|
968
939
|
|
|
969
|
-
|
|
940
|
+
`overrideAfterCurrent` lets the call in progress finish and replaces only what is waiting behind it.
|
|
970
941
|
|
|
971
|
-
###
|
|
942
|
+
### Stop work that never pauses
|
|
972
943
|
|
|
973
|
-
|
|
944
|
+
A Worker stuck in a synchronous loop cannot receive a cancellation. Restart it instead:
|
|
974
945
|
|
|
975
946
|
```ts
|
|
976
|
-
|
|
977
|
-
console.log(message.payload);
|
|
978
|
-
});
|
|
979
|
-
```
|
|
980
|
-
|
|
981
|
-
### โ
`resolve(result?, transfer?)`
|
|
982
|
-
|
|
983
|
-
Resolve the main-thread promise.
|
|
947
|
+
import { unwrap } from 'easy-web-worker/createWorker';
|
|
984
948
|
|
|
985
|
-
|
|
986
|
-
|
|
949
|
+
// terminates the Workers, rejects the pending calls, starts fresh ones
|
|
950
|
+
await unwrap(math).cancelAll('Taking too long', { force: true });
|
|
987
951
|
```
|
|
988
952
|
|
|
989
|
-
###
|
|
990
|
-
|
|
991
|
-
Reject the main-thread promise.
|
|
953
|
+
### Clean up when the owner goes away
|
|
992
954
|
|
|
993
955
|
```ts
|
|
994
|
-
|
|
956
|
+
await unwrap(math).dispose(); // typed workers
|
|
957
|
+
await worker.dispose(); // EasyWebWorker
|
|
995
958
|
```
|
|
996
959
|
|
|
997
|
-
|
|
960
|
+
`dispose` cancels the pending calls, revokes the generated Worker URL when there is one, and terminates the Workers.
|
|
998
961
|
|
|
999
|
-
|
|
962
|
+
> **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/)
|
|
1000
963
|
|
|
1001
|
-
|
|
1002
|
-
message.cancel('No longer needed');
|
|
1003
|
-
```
|
|
1004
|
-
|
|
1005
|
-
### ๐ `reportProgress(percentage, payload?, transfer?)`
|
|
964
|
+
---
|
|
1006
965
|
|
|
1007
|
-
|
|
966
|
+
## Documentation and examples
|
|
1008
967
|
|
|
1009
|
-
|
|
1010
|
-
message.reportProgress(50, {
|
|
1011
|
-
processed: 500,
|
|
1012
|
-
total: 1000,
|
|
1013
|
-
});
|
|
1014
|
-
```
|
|
968
|
+
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.
|
|
1015
969
|
|
|
1016
|
-
###
|
|
970
|
+
### See it before you install it
|
|
1017
971
|
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
972
|
+
| Live example | What you will see |
|
|
973
|
+
| --- | --- |
|
|
974
|
+
| [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. |
|
|
975
|
+
| [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. |
|
|
976
|
+
| [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. |
|
|
977
|
+
| [Transferable buffers](https://johnny-quesada-developer.github.io/easy-web-worker/examples/transferable-buffers/) | A large buffer moved to the Worker instead of copied. |
|
|
978
|
+
| [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. |
|
|
1025
979
|
|
|
1026
|
-
|
|
980
|
+
Every example shows its full source next to the running result.
|
|
1027
981
|
|
|
1028
|
-
###
|
|
982
|
+
### Then go deeper
|
|
1029
983
|
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
984
|
+
| Guide | What you will find |
|
|
985
|
+
| --- | --- |
|
|
986
|
+
| [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. |
|
|
987
|
+
| [Guides](https://johnny-quesada-developer.github.io/easy-web-worker/docs/) | Typed workers, cancellation, progress, transferables, pools and bundler setup. |
|
|
988
|
+
| [API reference](https://johnny-quesada-developer.github.io/easy-web-worker/docs/api-reference/) | `defineWorker`, `createWorker`, `unwrap`, the message and the configuration. |
|
|
989
|
+
| [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. |
|
|
990
|
+
| [Troubleshooting](https://johnny-quesada-developer.github.io/easy-web-worker/docs/troubleshooting/) | Symptoms, causes and fixes. |
|
|
991
|
+
| [Platform and versions](https://johnny-quesada-developer.github.io/easy-web-worker/docs/platform-and-versions/) | Browser support and what changes when upgrading. |
|
|
1034
992
|
|
|
1035
993
|
---
|
|
1036
994
|
|
|
1037
|
-
##
|
|
995
|
+
## Built to grow with your application
|
|
996
|
+
|
|
997
|
+
| Without `easy-web-worker` | With `easy-web-worker` |
|
|
998
|
+
| ------------------------------------ | ---------------------------------------- |
|
|
999
|
+
| `postMessage()` protocols | Functions |
|
|
1000
|
+
| Manual request IDs | Automatic routing |
|
|
1001
|
+
| Response matching | Promises |
|
|
1002
|
+
| Custom error transport | Throw / catch |
|
|
1003
|
+
| Custom cancellation protocol | `task.cancel()` |
|
|
1004
|
+
| Custom progress messages | `.onProgress()` |
|
|
1005
|
+
| Hand-maintained TypeScript contracts | Inferred types |
|
|
1006
|
+
| Manual Worker orchestration | Built-in pools |
|
|
1007
|
+
| Manual lifecycle handling | Warm-up, keep-alive and idle termination |
|
|
1038
1008
|
|
|
1039
|
-
|
|
1040
|
-
| ------------------- | -------------------------------------------- | ------------------------------------ |
|
|
1041
|
-
| Function template | Small/medium Worker logic with minimal setup | โ
|
|
|
1042
|
-
| Template array | Composable runtime Worker logic | โ
|
|
|
1043
|
-
| `string` / `URL` | Dedicated static Worker file | โ
|
|
|
1044
|
-
| Existing `Worker` | Integrating an existing native Worker | โ
Wrapped |
|
|
1045
|
-
| Existing `Worker[]` | Supplying your own pre-created pool | โ
Wrapped |
|
|
1009
|
+
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.
|
|
1046
1010
|
|
|
1047
|
-
|
|
1011
|
+
> **Write Worker code like an API, call it like normal asynchronous JavaScript, and scale it without rebuilding the communication layer.**
|
|
1048
1012
|
|
|
1049
1013
|
---
|
|
1050
1014
|
|
|
1051
|
-
##
|
|
1015
|
+
## Get Started Now
|
|
1052
1016
|
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
- ๐งช [CodePen example](https://codepen.io/johnnynabetes/full/wvOvygW)
|
|
1057
|
-
- ๐ [MDN โ Using Web Workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers)
|
|
1058
|
-
- ๐ฆ [npm โ easy-web-worker](https://www.npmjs.com/package/easy-web-worker)
|
|
1017
|
+
```bash
|
|
1018
|
+
npm install easy-web-worker
|
|
1019
|
+
```
|
|
1059
1020
|
|
|
1060
|
-
|
|
1021
|
+
Then in your Worker file:
|
|
1022
|
+
|
|
1023
|
+
```ts
|
|
1024
|
+
import { defineWorker } from 'easy-web-worker/defineWorker';
|
|
1061
1025
|
|
|
1062
|
-
|
|
1026
|
+
const fibonacci = (n: number): number =>
|
|
1027
|
+
n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2);
|
|
1063
1028
|
|
|
1064
|
-
|
|
1029
|
+
const worker = defineWorker(() => ({ fibonacci }));
|
|
1065
1030
|
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
| React | โ
|
|
|
1069
|
-
| Angular | โ
|
|
|
1070
|
-
| Vue | โ
|
|
|
1071
|
-
| Svelte | โ
|
|
|
1072
|
-
| Vanilla JavaScript | โ
|
|
|
1073
|
-
| TypeScript | โ
Strongly typed API |
|
|
1031
|
+
export type MathWorker = typeof worker;
|
|
1032
|
+
```
|
|
1074
1033
|
|
|
1075
|
-
|
|
1034
|
+
And in your app:
|
|
1076
1035
|
|
|
1077
|
-
|
|
1036
|
+
```ts
|
|
1037
|
+
import { createWorker } from 'easy-web-worker/createWorker';
|
|
1038
|
+
import type { MathWorker } from './math.worker';
|
|
1078
1039
|
|
|
1079
|
-
|
|
1040
|
+
const math = createWorker<MathWorker>(source);
|
|
1080
1041
|
|
|
1081
|
-
|
|
1042
|
+
console.log(await math.fibonacci(40)); // 102334155
|
|
1043
|
+
```
|
|
1082
1044
|
|
|
1083
|
-
|
|
1084
|
-
| ----------------------------- | -------------------------------------- |
|
|
1085
|
-
| โ
Real native Worker threads | โ Main-thread CPU bottlenecks |
|
|
1086
|
-
| โ
Promise-based messaging | โ Manual message-ID plumbing |
|
|
1087
|
-
| โ
Cancellation | โ Abandoned long-running work |
|
|
1088
|
-
| โ
Progress events | โ Custom progress protocols |
|
|
1089
|
-
| โ
Named Worker methods | โ Giant message-routing switches |
|
|
1090
|
-
| โ
Transferable support | โ Unnecessary large-data cloning |
|
|
1091
|
-
| โ
Worker pools | โ Hand-written concurrency management |
|
|
1092
|
-
| โ
Runtime + static Workers | โ One forced architecture |
|
|
1093
|
-
| โ
TypeScript generics | โ Untyped request/response contracts |
|
|
1045
|
+
Continue with the [guides and interactive examples](https://johnny-quesada-developer.github.io/easy-web-worker/) to build your next worker.
|
|
1094
1046
|
|
|
1095
1047
|
---
|
|
1096
1048
|
|
|
1097
|
-
##
|
|
1049
|
+
## Collaborators
|
|
1098
1050
|
|
|
1099
1051
|
<div align="center">
|
|
1100
1052
|
|
|
@@ -1121,35 +1073,19 @@ The important requirement is a runtime with browser Web Worker support.
|
|
|
1121
1073
|
|
|
1122
1074
|
---
|
|
1123
1075
|
|
|
1124
|
-
##
|
|
1125
|
-
|
|
1126
|
-
```bash
|
|
1127
|
-
npm install easy-web-worker
|
|
1128
|
-
```
|
|
1129
|
-
|
|
1130
|
-
Then:
|
|
1131
|
-
|
|
1132
|
-
```ts
|
|
1133
|
-
import { createEasyWebWorker } from 'easy-web-worker';
|
|
1076
|
+
## Built by Johnny Quesada
|
|
1134
1077
|
|
|
1135
|
-
|
|
1136
|
-
onMessage((message) => {
|
|
1137
|
-
message.resolve(message.payload.toUpperCase());
|
|
1138
|
-
});
|
|
1139
|
-
});
|
|
1078
|
+
`easy-web-worker` exists because using another thread should not require building a messaging framework first.
|
|
1140
1079
|
|
|
1141
|
-
|
|
1142
|
-
// HELLO WORKER
|
|
1143
|
-
```
|
|
1080
|
+
If it makes your codebase simpler, consider starring the project. It helps other developers discover it.
|
|
1144
1081
|
|
|
1145
|
-
**
|
|
1082
|
+
[**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)
|
|
1146
1083
|
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
<div align="center">
|
|
1084
|
+
## Earlier resources
|
|
1150
1085
|
|
|
1151
|
-
|
|
1086
|
+
Demos and walkthroughs from previous versions, kept for reference. For the current API, start with the
|
|
1087
|
+
[documentation website](https://johnny-quesada-developer.github.io/easy-web-worker/).
|
|
1152
1088
|
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1089
|
+
- [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)
|
|
1090
|
+
- [Original video walkthrough](https://www.youtube.com/watch?v=CK-Uri9lDOE)
|
|
1091
|
+
- [Original CodePen example](https://codepen.io/johnnynabetes/full/wvOvygW)
|