easy-web-worker 7.0.5 โ 7.0.6
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/README.md +943 -327
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,539 +1,1155 @@
|
|
|
1
1
|
# easy-web-worker ๐
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<div align="center">
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+

|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
</div>
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
<div align="center">
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
**Real Web Workers. Simple API. Cancelable work.** ๐
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
_Heavy computation off the main thread โ without fighting the native Worker API._ โจ
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
[](https://www.npmjs.com/package/easy-web-worker)
|
|
16
|
+
[](https://www.npmjs.com/package/easy-web-worker)
|
|
17
|
+
[](https://github.com/johnny-quesada-developer/easy-web-worker/blob/main/LICENSE)
|
|
16
18
|
|
|
17
|
-
|
|
19
|
+
[**Live Demo**](https://johnny-quesada-developer.github.io/easy-web-workers-example/) โข [**Video Tutorial**](https://www.youtube.com/watch?v=CK-Uri9lDOE) โข [**CodePen**](https://codepen.io/johnnynabetes/full/wvOvygW)
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## ๐ฏ The One-Liner
|
|
20
26
|
|
|
21
27
|
```ts
|
|
22
|
-
import createEasyWebWorker from 'easy-web-worker
|
|
28
|
+
import { createEasyWebWorker } from 'easy-web-worker';
|
|
29
|
+
|
|
30
|
+
const worker = createEasyWebWorker<number, number>(({ onMessage }) => {
|
|
31
|
+
onMessage((message) => message.resolve(message.payload * 2));
|
|
32
|
+
});
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**That's it.** No separate worker file. No message-ID plumbing. No manual promise bridge. ๐งต
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
const result = await worker.send(21);
|
|
39
|
+
|
|
40
|
+
console.log(result); // 42
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Your code runs inside a **real native Web Worker**, while the main thread receives a `CancelablePromise`.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## ๐ Why Developers Love This Library
|
|
23
48
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
49
|
+
### ๐ **Small API Surface**
|
|
50
|
+
|
|
51
|
+
If you already understand native Web Workers, the mental model stays familiar:
|
|
52
|
+
|
|
53
|
+
```ts
|
|
27
54
|
const worker = createEasyWebWorker(({ onMessage }) => {
|
|
28
|
-
|
|
55
|
+
onMessage((message) => {
|
|
56
|
+
// Work inside the Worker
|
|
57
|
+
message.resolve('done');
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
await worker.send();
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### โก **Real Background Execution**
|
|
65
|
+
|
|
66
|
+
Move CPU-heavy work away from the browser's main thread so rendering and user interaction can stay responsive.
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
const worker = createEasyWebWorker<number, number>(({ onMessage }) => {
|
|
70
|
+
const fibonacci = (n: number): number => {
|
|
29
71
|
if (n <= 1) return n;
|
|
30
72
|
return fibonacci(n - 1) + fibonacci(n - 2);
|
|
31
73
|
};
|
|
32
74
|
|
|
33
|
-
/**
|
|
34
|
-
* Inside the worker we have to define an action when onMessage
|
|
35
|
-
*/
|
|
36
75
|
onMessage((message) => {
|
|
37
|
-
|
|
38
|
-
* The payload includes whatever parameters are sent from the main thread
|
|
39
|
-
*/
|
|
40
|
-
const { payload } = message;
|
|
41
|
-
const result = fibonacci(payload.base);
|
|
42
|
-
|
|
43
|
-
message.resolve(result);
|
|
44
|
-
//message.reject(); // or reject
|
|
76
|
+
message.resolve(fibonacci(message.payload));
|
|
45
77
|
});
|
|
46
78
|
});
|
|
47
79
|
```
|
|
48
80
|
|
|
49
|
-
|
|
81
|
+
### ๐ **Cancelable Promises**
|
|
82
|
+
|
|
83
|
+
Every `send()` returns a `CancelablePromise` from [`easy-cancelable-promise`](https://www.npmjs.com/package/easy-cancelable-promise).
|
|
50
84
|
|
|
51
85
|
```ts
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
await worker.send(40);
|
|
86
|
+
const task = worker.send(45);
|
|
87
|
+
|
|
88
|
+
task.cancel('No longer needed');
|
|
56
89
|
```
|
|
57
90
|
|
|
58
|
-
|
|
91
|
+
Cancellation can travel **from the main thread into the Worker**, allowing the Worker to react and release resources.
|
|
59
92
|
|
|
60
|
-
|
|
93
|
+
### ๐ **Progress Reporting**
|
|
94
|
+
|
|
95
|
+
Long-running jobs can report progress without resolving the request.
|
|
61
96
|
|
|
62
97
|
```ts
|
|
63
|
-
|
|
64
|
-
|
|
98
|
+
worker.send(payload).onProgress((percentage) => {
|
|
99
|
+
console.log(`${percentage}%`);
|
|
100
|
+
});
|
|
101
|
+
```
|
|
65
102
|
|
|
66
|
-
|
|
103
|
+
### ๐ฆ **Built-In Worker Pool**
|
|
67
104
|
|
|
68
|
-
|
|
69
|
-
? workerUrl
|
|
70
|
-
: new URL('./worker.ts', import.meta.url);
|
|
105
|
+
Scale a single `EasyWebWorker` across multiple native Worker instances.
|
|
71
106
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
},
|
|
107
|
+
```ts
|
|
108
|
+
const worker = createEasyWebWorker(workerBody, {
|
|
109
|
+
maxWorkers: 4,
|
|
76
110
|
});
|
|
111
|
+
```
|
|
77
112
|
|
|
78
|
-
|
|
79
|
-
const worker = createEasyWebWorker(workerSource, {
|
|
80
|
-
workerOptions: {
|
|
81
|
-
type: 'module',
|
|
82
|
-
},
|
|
83
|
-
});
|
|
113
|
+
The public API remains the same โ the library manages worker creation and message distribution.
|
|
84
114
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
115
|
+
### ๐งฉ **Multiple Worker Sources**
|
|
116
|
+
|
|
117
|
+
Use whichever architecture fits the project:
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
createEasyWebWorker(workerBody); // Runtime Worker template
|
|
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
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
---
|
|
89
128
|
|
|
90
|
-
|
|
91
|
-
const worker = createEasyWebWorker('./worker.js');
|
|
92
|
-
const worker = createEasyWebWorker(new Worker('./worker.js')); // ฦ Worker() { [native code] }
|
|
129
|
+
## ๐ฆ Installation
|
|
93
130
|
|
|
94
|
-
|
|
95
|
-
|
|
131
|
+
```bash
|
|
132
|
+
npm install easy-web-worker
|
|
96
133
|
```
|
|
97
134
|
|
|
98
|
-
|
|
135
|
+
`easy-web-worker` uses [`easy-cancelable-promise`](https://www.npmjs.com/package/easy-cancelable-promise) for the promise lifecycle.
|
|
99
136
|
|
|
100
|
-
|
|
137
|
+
> **Upgrading from an old release?**
|
|
138
|
+
> If your project still imports `cancelable-promise-jq`, replace it with `easy-cancelable-promise`. The old package name is deprecated.
|
|
101
139
|
|
|
102
|
-
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## ๐ฌ Quick Start
|
|
143
|
+
|
|
144
|
+
### 30 Seconds to a Web Worker
|
|
103
145
|
|
|
104
146
|
```ts
|
|
105
|
-
|
|
147
|
+
import { createEasyWebWorker } from 'easy-web-worker';
|
|
106
148
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
* which is any information sent from the main thread.*/
|
|
113
|
-
const { payload } = message;
|
|
149
|
+
const fibonacciWorker = createEasyWebWorker<number, number>(({ onMessage }) => {
|
|
150
|
+
const fibonacci = (n: number): number => {
|
|
151
|
+
if (n <= 1) return n;
|
|
152
|
+
return fibonacci(n - 1) + fibonacci(n - 2);
|
|
153
|
+
};
|
|
114
154
|
|
|
115
|
-
|
|
155
|
+
onMessage((message) => {
|
|
156
|
+
message.resolve(fibonacci(message.payload));
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
const result = await fibonacciWorker.send(40);
|
|
116
161
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
message.resolve({ bigArrayBuffer }, [bigArrayBuffer]);
|
|
162
|
+
console.log(result);
|
|
163
|
+
```
|
|
120
164
|
|
|
121
|
-
|
|
122
|
-
* if no transfer is necessary just avoid the second parameter */
|
|
123
|
-
message.reject(new Error('something happened'));
|
|
165
|
+
The Fibonacci calculation executes in a separate native Worker thread instead of blocking the page's main thread. โก
|
|
124
166
|
|
|
125
|
-
|
|
167
|
+
### 60 Seconds to Production-Ready
|
|
126
168
|
|
|
127
|
-
|
|
128
|
-
message.reportProgress(10, metadata, []); // all the methods allows you to send Transferable[]
|
|
169
|
+
Run [`jsdiff`](https://www.npmjs.com/package/diff) inside a Web Worker so text comparison never blocks the main thread.
|
|
129
170
|
|
|
130
|
-
|
|
131
|
-
message.cancel('the operating was canceled from the worker');
|
|
171
|
+
#### ๐ Simple jsdiff implementation.
|
|
132
172
|
|
|
133
|
-
|
|
134
|
-
* regardless of whether this cancellation is internal or external to the worker.*/
|
|
135
|
-
const unsubscribeCancel = message.onCancel((reason) => {});
|
|
173
|
+
No Worker file. No extra bundler configuration. Just give `easy-web-worker` the public script URL:
|
|
136
174
|
|
|
137
|
-
|
|
138
|
-
|
|
175
|
+
```ts
|
|
176
|
+
// example.js
|
|
177
|
+
import { createEasyWebWorker } from 'easy-web-worker';
|
|
139
178
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
179
|
+
const worker = createEasyWebWorker(
|
|
180
|
+
({ onMessage }) => {
|
|
181
|
+
onMessage((message, context) => {
|
|
182
|
+
const { input1, input2 } = message.payload;
|
|
183
|
+
|
|
184
|
+
message.resolve(context.Diff.diffWords(input1, input2));
|
|
185
|
+
});
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
scripts: ['https://cdn.jsdelivr.net/npm/diff@9.0.0/dist/diff.min.js'],
|
|
189
|
+
}
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
const result = await worker.send({
|
|
193
|
+
input1: 'Web Workers are powerful.',
|
|
194
|
+
input2: 'Web Workers are incredibly powerful.',
|
|
151
195
|
});
|
|
152
196
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
197
|
+
console.log(result);
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**That's it.** The CDN script is loaded inside the Worker, `jsdiff` exposes its `Diff` API there, and your main thread only sends data and awaits the result. ๐
|
|
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
|
|
208
|
+
|
|
209
|
+
#### ๐ Static Worker file and specific actions
|
|
210
|
+
|
|
211
|
+
If you prefer normal module imports, your own Worker file, or bundler-controlled dependencies, use `StaticEasyWebWorker`.
|
|
212
|
+
|
|
213
|
+
**`TextDiff.worker.ts`**
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
import { diffWords, type Change } from 'diff';
|
|
217
|
+
import { StaticEasyWebWorker } from 'easy-web-worker';
|
|
218
|
+
import type { ComparePayload, Change } from './TextDiff.types';
|
|
219
|
+
|
|
220
|
+
const easyWorker = new StaticEasyWebWorker();
|
|
221
|
+
|
|
222
|
+
// you can define multiple named methods inside the Worker
|
|
223
|
+
easyWorker.onMessage<ComparePayload, Change[]>('compare', (message) => {
|
|
224
|
+
const { input1, input2 } = message.payload;
|
|
225
|
+
|
|
226
|
+
message.resolve(diffWords(input1, input2));
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
easyWorker.onMessage<string, string>('uppercase', (message) => {
|
|
230
|
+
message.resolve(message.payload.toUpperCase());
|
|
158
231
|
});
|
|
159
232
|
```
|
|
160
233
|
|
|
161
|
-
|
|
234
|
+
**That's the whole Worker.** Now connect it to your application:
|
|
162
235
|
|
|
163
|
-
|
|
236
|
+
```ts
|
|
237
|
+
import { EasyWebWorker } from 'easy-web-worker';
|
|
238
|
+
import workerUrl from './TextDiff.worker?worker&url';
|
|
164
239
|
|
|
165
|
-
|
|
240
|
+
// For vite, use the Worker URL in production and the TypeScript file in development.
|
|
241
|
+
const isProduction = import.meta.env.MODE === 'production';
|
|
166
242
|
|
|
167
|
-
|
|
243
|
+
const worker = new EasyWebWorker(
|
|
244
|
+
isProduction ? workerUrl : new URL('./TextDiff.worker.ts', import.meta.url),
|
|
245
|
+
{
|
|
246
|
+
workerOptions: {
|
|
247
|
+
type: 'module',
|
|
248
|
+
},
|
|
249
|
+
}
|
|
250
|
+
);
|
|
168
251
|
|
|
169
|
-
|
|
252
|
+
const result = await worker.sendToMethod<ComparePayload, Change[]>('compare', {
|
|
253
|
+
input1: 'Web Workers are powerful.',
|
|
254
|
+
input2: 'Web Workers are incredibly powerful.',
|
|
255
|
+
});
|
|
256
|
+
```
|
|
170
257
|
|
|
171
|
-
|
|
258
|
+
Both approaches give you the same core result: a normal JavaScript library doing CPU work inside a **Web Worker**, while your application talks to it through a Promise-based API.
|
|
172
259
|
|
|
173
|
-
|
|
260
|
+
| Approach | Best for |
|
|
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 |
|
|
174
264
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
265
|
+
And in both cases you still get the `easy-web-worker` features around the Worker:
|
|
266
|
+
|
|
267
|
+
- โ
Promise-based communication
|
|
268
|
+
- โ
Cancellation
|
|
269
|
+
- โ
Progress reporting
|
|
270
|
+
- โ
Named Worker methods
|
|
271
|
+
- โ
Worker pooling when needed
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## ๐ Core Features Deep Dive
|
|
276
|
+
|
|
277
|
+
### 1๏ธโฃ Runtime Workers with `createEasyWebWorker`
|
|
278
|
+
|
|
279
|
+
Create a Worker directly from a function template.
|
|
280
|
+
|
|
281
|
+
#### ๐จ The Basics
|
|
282
|
+
|
|
283
|
+
```ts
|
|
284
|
+
import { createEasyWebWorker } from 'easy-web-worker';
|
|
285
|
+
|
|
286
|
+
const backgroundWorker = createEasyWebWorker<string, string>(
|
|
287
|
+
({ onMessage }) => {
|
|
288
|
+
onMessage((message) => {
|
|
289
|
+
message.resolve(`Message from Worker: ${message.payload}`);
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
const result = await backgroundWorker.send('hello!');
|
|
179
295
|
|
|
180
|
-
|
|
296
|
+
console.log(result);
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
The first generic controls the `send()` payload. The second controls the value returned when the Worker calls `message.resolve()`.
|
|
300
|
+
|
|
301
|
+
```ts
|
|
302
|
+
const worker = createEasyWebWorker<Payload, Result>(workerBody);
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
#### ๐ฏ Named Worker Methods
|
|
306
|
+
|
|
307
|
+
A Worker can expose multiple message handlers instead of routing everything through one callback.
|
|
308
|
+
|
|
309
|
+
```ts
|
|
310
|
+
const worker = createEasyWebWorker<string, string>(({ onMessage }) => {
|
|
311
|
+
onMessage((message) => {
|
|
312
|
+
message.resolve(`default: ${message.payload}`);
|
|
181
313
|
});
|
|
182
314
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
315
|
+
onMessage<number, number>('double', (message) => {
|
|
316
|
+
message.resolve(message.payload * 2);
|
|
317
|
+
});
|
|
186
318
|
|
|
187
|
-
|
|
319
|
+
onMessage<{ first: number; second: number }, number>('sum', (message) => {
|
|
320
|
+
const { first, second } = message.payload;
|
|
321
|
+
message.resolve(first + second);
|
|
188
322
|
});
|
|
189
323
|
});
|
|
190
324
|
|
|
191
|
-
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
325
|
+
const defaultResult = await worker.send('hello');
|
|
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 });
|
|
196
331
|
```
|
|
197
332
|
|
|
198
|
-
|
|
333
|
+
This makes it possible to build a small, typed API inside one Worker. ๐งฉ
|
|
199
334
|
|
|
200
|
-
|
|
335
|
+
#### ๐ง Worker Scope โ Important!
|
|
201
336
|
|
|
202
|
-
|
|
203
|
-
- If IPayload is null, the _send_ method will not require parameters
|
|
337
|
+
A runtime Worker body becomes the Worker source. It **cannot close over arbitrary variables from the main thread**.
|
|
204
338
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
```TS
|
|
208
|
-
const message = 'Hello';
|
|
339
|
+
```ts
|
|
340
|
+
const greeting = 'Hello';
|
|
209
341
|
|
|
210
|
-
|
|
342
|
+
createEasyWebWorker(({ onMessage }) => {
|
|
211
343
|
onMessage((message) => {
|
|
212
|
-
|
|
213
|
-
message.resolve(
|
|
344
|
+
// โ `greeting` does not exist inside the Worker scope.
|
|
345
|
+
message.resolve(greeting);
|
|
214
346
|
});
|
|
215
|
-
})
|
|
347
|
+
});
|
|
216
348
|
```
|
|
217
349
|
|
|
218
|
-
|
|
350
|
+
Everything needed by the Worker must be:
|
|
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
|
+
|
|
360
|
+
For small static values that should exist when the runtime Worker is created, use `primitiveParameters`.
|
|
219
361
|
|
|
220
362
|
```ts
|
|
221
|
-
const
|
|
363
|
+
const prefix = 'Result:';
|
|
222
364
|
|
|
223
|
-
|
|
365
|
+
const worker = createEasyWebWorker<null, string>(
|
|
224
366
|
({ onMessage }, context) => {
|
|
225
|
-
const [
|
|
367
|
+
const [prefix] = context.primitiveParameters;
|
|
226
368
|
|
|
227
|
-
|
|
369
|
+
onMessage((message) => {
|
|
370
|
+
message.resolve(`${prefix} complete`);
|
|
371
|
+
});
|
|
228
372
|
},
|
|
229
373
|
{
|
|
230
|
-
primitiveParameters: [
|
|
374
|
+
primitiveParameters: [prefix],
|
|
231
375
|
}
|
|
232
|
-
)
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
console.log(await worker.send()); // Result: complete
|
|
233
379
|
```
|
|
234
380
|
|
|
235
|
-
|
|
236
|
-
If you need t to send data to the worker, please define IPayload while creating a worker. _new EasyWebWorker<IPayload>(_
|
|
237
|
-
You are just allowed to send information to Workers by messages, and vice versa
|
|
381
|
+
Use regular Worker messages for dynamic application data. `primitiveParameters` are best for small initialization values.
|
|
238
382
|
|
|
239
|
-
|
|
383
|
+
#### ๐ Progress Without Resolving
|
|
240
384
|
|
|
241
|
-
|
|
385
|
+
The Worker can report progress as many times as necessary before the message finishes.
|
|
242
386
|
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
|
|
387
|
+
```ts
|
|
388
|
+
const worker = createEasyWebWorker<number[], number>(({ onMessage }) => {
|
|
389
|
+
onMessage((message) => {
|
|
390
|
+
let total = 0;
|
|
246
391
|
|
|
247
|
-
|
|
248
|
-
|
|
392
|
+
message.payload.forEach((value, index, values) => {
|
|
393
|
+
total += value;
|
|
394
|
+
message.reportProgress(((index + 1) / values.length) * 100);
|
|
395
|
+
});
|
|
249
396
|
|
|
250
|
-
|
|
251
|
-
|
|
397
|
+
message.resolve(total);
|
|
398
|
+
});
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
const result = await worker.send([10, 20, 30, 40]).onProgress((percentage) => {
|
|
402
|
+
console.log(percentage);
|
|
403
|
+
});
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
Output:
|
|
407
|
+
|
|
408
|
+
```text
|
|
409
|
+
25
|
|
410
|
+
50
|
|
411
|
+
75
|
|
412
|
+
100
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
#### ๐ Two-Way Cancellation
|
|
416
|
+
|
|
417
|
+
Cancellation is more than rejecting a promise on the main thread. The Worker receives the cancellation event too.
|
|
418
|
+
|
|
419
|
+
```ts
|
|
420
|
+
const worker = createEasyWebWorker<number, number>(({ onMessage }) => {
|
|
421
|
+
onMessage((message) => {
|
|
422
|
+
const interval = setInterval(() => {
|
|
423
|
+
// long-running work...
|
|
424
|
+
}, 100);
|
|
252
425
|
|
|
253
|
-
|
|
254
|
-
|
|
426
|
+
message.onCancel((data) => {
|
|
427
|
+
clearInterval(interval);
|
|
255
428
|
|
|
256
|
-
|
|
257
|
-
message.onCancel(() => {
|
|
258
|
-
// release resources
|
|
259
|
-
})
|
|
429
|
+
const reason = data.worker_cancelation?.reason ?? data.canceled?.reason;
|
|
260
430
|
|
|
261
|
-
|
|
262
|
-
|
|
431
|
+
console.log('Canceled:', reason);
|
|
432
|
+
});
|
|
433
|
+
});
|
|
263
434
|
});
|
|
435
|
+
|
|
436
|
+
const task = worker.send(100);
|
|
437
|
+
|
|
438
|
+
task.cancel('User navigated away');
|
|
264
439
|
```
|
|
265
440
|
|
|
266
|
-
|
|
441
|
+
Inside a Worker, a message can also cancel itself:
|
|
267
442
|
|
|
268
|
-
|
|
443
|
+
```ts
|
|
444
|
+
onMessage((message) => {
|
|
445
|
+
if (!isValid(message.payload)) {
|
|
446
|
+
message.cancel('Invalid payload');
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
269
449
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
// change some progress bar percentage
|
|
273
|
-
}).then(doSomething);
|
|
450
|
+
// continue...
|
|
451
|
+
});
|
|
274
452
|
```
|
|
275
453
|
|
|
276
|
-
|
|
454
|
+
#### ๐ Message Lifecycle
|
|
455
|
+
|
|
456
|
+
Each `IEasyWebWorkerMessage` provides lifecycle hooks:
|
|
457
|
+
|
|
458
|
+
```ts
|
|
459
|
+
onMessage((message) => {
|
|
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
|
+
|
|
476
|
+
message.resolve();
|
|
477
|
+
});
|
|
478
|
+
```
|
|
277
479
|
|
|
278
|
-
|
|
480
|
+
#### ๐ Transferable Objects
|
|
279
481
|
|
|
280
|
-
|
|
482
|
+
Both directions can use native `Transferable[]` values to move ownership instead of cloning large buffers.
|
|
281
483
|
|
|
282
|
-
```
|
|
283
|
-
|
|
284
|
-
|
|
484
|
+
```ts
|
|
485
|
+
type Payload = {
|
|
486
|
+
buffer: ArrayBuffer;
|
|
285
487
|
};
|
|
286
488
|
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
// context will have all stuff we added on other plugins
|
|
291
|
-
const result = await context.doSomething();
|
|
489
|
+
const worker = createEasyWebWorker<Payload, ArrayBuffer>(({ onMessage }) => {
|
|
490
|
+
onMessage((message) => {
|
|
491
|
+
const { buffer } = message.payload;
|
|
292
492
|
|
|
293
|
-
|
|
294
|
-
})]).send();
|
|
493
|
+
// Process the buffer...
|
|
295
494
|
|
|
495
|
+
message.resolve(buffer, [buffer]);
|
|
496
|
+
});
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
const buffer = new ArrayBuffer(1_000_000);
|
|
500
|
+
|
|
501
|
+
const processedBuffer = await worker.send({ buffer }, [buffer]);
|
|
296
502
|
```
|
|
297
503
|
|
|
298
|
-
|
|
504
|
+
This is especially useful for `ArrayBuffer`, media processing, binary parsing, and other data-heavy workloads.
|
|
299
505
|
|
|
300
|
-
|
|
506
|
+
---
|
|
301
507
|
|
|
302
|
-
|
|
508
|
+
### 2๏ธโฃ Static Workers with `StaticEasyWebWorker`
|
|
303
509
|
|
|
304
|
-
|
|
510
|
+
Sometimes a dedicated Worker file is the better architecture โ especially when the Worker has its own modules, build pipeline, or large implementation.
|
|
305
511
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
512
|
+
#### ๐ช The Basics
|
|
513
|
+
|
|
514
|
+
**`worker.ts`**
|
|
515
|
+
|
|
516
|
+
```ts
|
|
517
|
+
import { createStaticEasyWebWorker } from 'easy-web-worker/createStaticEasyWebWorker';
|
|
518
|
+
|
|
519
|
+
const { onMessage } = createStaticEasyWebWorker<number, number>();
|
|
520
|
+
|
|
521
|
+
onMessage((message) => {
|
|
522
|
+
message.resolve(message.payload * 2);
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
onMessage<string, string>('uppercase', (message) => {
|
|
526
|
+
message.resolve(message.payload.toUpperCase());
|
|
527
|
+
});
|
|
309
528
|
```
|
|
310
529
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
|
|
530
|
+
**Main thread**
|
|
531
|
+
|
|
532
|
+
```ts
|
|
533
|
+
import { createEasyWebWorker } from 'easy-web-worker';
|
|
534
|
+
|
|
535
|
+
const worker = createEasyWebWorker<number, number>('./worker.js');
|
|
317
536
|
|
|
537
|
+
const result = await worker.send(21);
|
|
318
538
|
```
|
|
319
539
|
|
|
320
|
-
|
|
540
|
+
You keep the `send()`, cancellation, progress, and message lifecycle API while controlling the Worker file yourself.
|
|
321
541
|
|
|
322
|
-
|
|
542
|
+
#### ๐ Vite + TypeScript
|
|
323
543
|
|
|
324
|
-
|
|
544
|
+
```ts
|
|
545
|
+
import workerUrl from './worker?worker&url';
|
|
546
|
+
import { createEasyWebWorker } from 'easy-web-worker';
|
|
325
547
|
|
|
326
|
-
|
|
548
|
+
const workerSource =
|
|
549
|
+
import.meta.env.MODE === 'production'
|
|
550
|
+
? workerUrl
|
|
551
|
+
: new URL('./worker.ts', import.meta.url);
|
|
327
552
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
553
|
+
const worker = createEasyWebWorker(workerSource, {
|
|
554
|
+
workerOptions: {
|
|
555
|
+
type: 'module',
|
|
556
|
+
},
|
|
557
|
+
});
|
|
558
|
+
```
|
|
331
559
|
|
|
332
|
-
|
|
333
|
-
// imports only the static web worker
|
|
334
|
-
import createStaticEasyWebWorker from 'easy-web-worker/createStaticEasyWebWorker';
|
|
560
|
+
#### ๐ Existing Native Worker
|
|
335
561
|
|
|
336
|
-
|
|
337
|
-
const { onMessage } = createStaticEasyWebWorker();
|
|
562
|
+
Already have a `Worker` instance? Wrap it directly.
|
|
338
563
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
}, 5000);
|
|
564
|
+
```ts
|
|
565
|
+
const nativeWorker = new Worker(new URL('./worker.js', import.meta.url), {
|
|
566
|
+
type: 'module',
|
|
343
567
|
});
|
|
344
568
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
569
|
+
const worker = createEasyWebWorker(nativeWorker);
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
Or provide an existing pool:
|
|
573
|
+
|
|
574
|
+
```ts
|
|
575
|
+
const worker = createEasyWebWorker([
|
|
576
|
+
new Worker('./worker-a.js'),
|
|
577
|
+
new Worker('./worker-b.js'),
|
|
578
|
+
]);
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
When an existing `Worker[]` is supplied, those Worker instances become the pool managed by `EasyWebWorker`.
|
|
582
|
+
|
|
583
|
+
---
|
|
584
|
+
|
|
585
|
+
### 3๏ธโฃ Concurrency & Worker Pool
|
|
586
|
+
|
|
587
|
+
A single `EasyWebWorker` can distribute simultaneous messages across multiple native Workers.
|
|
588
|
+
|
|
589
|
+
#### ๐ฆ Scale on Demand
|
|
590
|
+
|
|
591
|
+
```ts
|
|
592
|
+
const worker = createEasyWebWorker<number, number>(
|
|
593
|
+
({ onMessage }) => {
|
|
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
|
+
);
|
|
607
|
+
|
|
608
|
+
const results = await Promise.all([
|
|
609
|
+
worker.send(40),
|
|
610
|
+
worker.send(41),
|
|
611
|
+
worker.send(42),
|
|
612
|
+
]);
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
With `maxWorkers: 4`, the library can create additional Workers as concurrent requests arrive, up to the configured limit.
|
|
616
|
+
|
|
617
|
+
#### ๐ฅ Warm Up the Pool
|
|
618
|
+
|
|
619
|
+
Create the full pool immediately:
|
|
620
|
+
|
|
621
|
+
```ts
|
|
622
|
+
const worker = createEasyWebWorker(workerBody, {
|
|
623
|
+
maxWorkers: 4,
|
|
624
|
+
warmUpWorkers: true,
|
|
349
625
|
});
|
|
350
626
|
```
|
|
351
627
|
|
|
352
|
-
|
|
628
|
+
Useful when startup latency matters more than keeping the initial resource footprint small.
|
|
629
|
+
|
|
630
|
+
#### ๐ค Dispose Idle Workers Automatically
|
|
353
631
|
|
|
354
632
|
```ts
|
|
355
|
-
|
|
356
|
-
|
|
633
|
+
const worker = createEasyWebWorker(workerBody, {
|
|
634
|
+
maxWorkers: 4,
|
|
635
|
+
keepAlive: false,
|
|
636
|
+
terminationDelay: 5_000,
|
|
357
637
|
});
|
|
358
638
|
```
|
|
359
639
|
|
|
360
|
-
|
|
640
|
+
When there are no queued messages, idle Workers are terminated after the configured delay.
|
|
641
|
+
|
|
642
|
+
#### โ๏ธ Concurrency Defaults
|
|
643
|
+
|
|
644
|
+
| Option | Current behavior |
|
|
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 |
|
|
361
651
|
|
|
362
|
-
|
|
363
|
-
|
|
652
|
+
This lets the default experience behave like a persistent single Worker while making larger pools opt-in and demand-driven.
|
|
653
|
+
|
|
654
|
+
---
|
|
655
|
+
|
|
656
|
+
### 4๏ธโฃ Reusable Worker Templates
|
|
657
|
+
|
|
658
|
+
Runtime Worker bodies are templates, so common Worker functionality can be composed.
|
|
659
|
+
|
|
660
|
+
```ts
|
|
661
|
+
import { createEasyWebWorker, type EasyWebWorkerBody } from 'easy-web-worker';
|
|
662
|
+
|
|
663
|
+
const commonTools: EasyWebWorkerBody = ({ onMessage }, context) => {
|
|
664
|
+
context.doSomething = () => Promise.resolve('Reusable Worker code');
|
|
665
|
+
};
|
|
666
|
+
|
|
667
|
+
const worker = createEasyWebWorker([
|
|
668
|
+
commonTools,
|
|
669
|
+
({ onMessage }, context) => {
|
|
670
|
+
onMessage(async (message) => {
|
|
671
|
+
const result = await context.doSomething();
|
|
672
|
+
|
|
673
|
+
message.resolve(result);
|
|
674
|
+
});
|
|
675
|
+
},
|
|
676
|
+
]);
|
|
364
677
|
|
|
365
678
|
await worker.send();
|
|
366
679
|
```
|
|
367
680
|
|
|
368
|
-
|
|
681
|
+
This is useful when multiple runtime Workers share utilities, message handlers, or initialization logic.
|
|
682
|
+
|
|
683
|
+
---
|
|
369
684
|
|
|
370
|
-
|
|
685
|
+
### 5๏ธโฃ Import Scripts into Runtime Workers
|
|
371
686
|
|
|
372
|
-
|
|
687
|
+
External scripts can be included through the Worker configuration.
|
|
688
|
+
|
|
689
|
+
```ts
|
|
690
|
+
const worker = createEasyWebWorker(
|
|
691
|
+
({ onMessage }, context) => {
|
|
692
|
+
onMessage((message) => {
|
|
693
|
+
context.doSomething();
|
|
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:
|
|
704
|
+
|
|
705
|
+
```js
|
|
706
|
+
// worker-library.js
|
|
707
|
+
self.message = 'Hello from imported script!';
|
|
708
|
+
self.doSomething = () => console.log(self.message);
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
For modern module-heavy Workers, a static Worker file with normal ESM imports is often easier to organize.
|
|
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
|
+
};
|
|
373
724
|
|
|
374
|
-
```TS
|
|
375
|
-
/**
|
|
376
|
-
* Notice that the structure of the worker remains the same;
|
|
377
|
-
* the only changes are in the configuration parameters of the worker.
|
|
378
|
-
* Take a look below.*/
|
|
379
725
|
const worker = createEasyWebWorker(({ onMessage }) => {
|
|
380
|
-
|
|
381
|
-
|
|
726
|
+
const users = new Map<string, User>();
|
|
727
|
+
|
|
728
|
+
onMessage<User, void>('saveUser', (message) => {
|
|
729
|
+
users.set(message.payload.id, message.payload);
|
|
730
|
+
message.resolve();
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
onMessage<string, User | null>('getUser', (message) => {
|
|
734
|
+
message.resolve(users.get(message.payload) ?? null);
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
onMessage<string, boolean>('deleteUser', (message) => {
|
|
738
|
+
message.resolve(users.delete(message.payload));
|
|
739
|
+
});
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
await worker.sendToMethod<void, User>('saveUser', {
|
|
743
|
+
id: '42',
|
|
744
|
+
name: 'Johnny',
|
|
745
|
+
});
|
|
746
|
+
|
|
747
|
+
const user = await worker.sendToMethod<User | null, string>('getUser', '42');
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
One Worker, multiple typed operations, one shared Worker-local state.
|
|
751
|
+
|
|
752
|
+
### ๐ Filter Large Collections with Progress
|
|
753
|
+
|
|
754
|
+
The Worker can retrieve, store, and filter a large collection without blocking the main thread.
|
|
755
|
+
|
|
756
|
+
```ts
|
|
757
|
+
type Item = Record<string, unknown>;
|
|
382
758
|
|
|
383
|
-
|
|
759
|
+
const worker = createEasyWebWorker<string, Item[]>(({ onMessage }) => {
|
|
760
|
+
const collection = fetch('https://api.example.com/items').then(
|
|
761
|
+
(response) => response.json() as Promise<Item[]>
|
|
762
|
+
);
|
|
763
|
+
|
|
764
|
+
const containsValue = (item: unknown, filter: string): boolean => {
|
|
765
|
+
if (item === null || item === undefined) return false;
|
|
766
|
+
|
|
767
|
+
if (typeof item !== 'object') {
|
|
768
|
+
return String(item).toLowerCase().includes(filter);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
return Object.values(item).some((value) => containsValue(value, filter));
|
|
772
|
+
};
|
|
773
|
+
|
|
774
|
+
onMessage(async (message) => {
|
|
775
|
+
const items = await collection;
|
|
776
|
+
const filter = message.payload.trim().toLowerCase();
|
|
777
|
+
|
|
778
|
+
const result = items.filter((item, index) => {
|
|
779
|
+
message.reportProgress(((index + 1) / items.length) * 100);
|
|
780
|
+
|
|
781
|
+
return containsValue(item, filter);
|
|
782
|
+
});
|
|
384
783
|
|
|
385
784
|
message.resolve(result);
|
|
386
785
|
});
|
|
387
|
-
}, {
|
|
388
|
-
// We will now scale up to four workers if necessary.
|
|
389
|
-
maxWorkers: 4
|
|
390
786
|
});
|
|
391
787
|
```
|
|
392
788
|
|
|
393
|
-
|
|
789
|
+
Usage:
|
|
790
|
+
|
|
791
|
+
```ts
|
|
792
|
+
const filtered = await worker
|
|
793
|
+
.send('johnny')
|
|
794
|
+
.onProgress((percentage) => console.log(percentage));
|
|
795
|
+
|
|
796
|
+
console.log(filtered);
|
|
797
|
+
```
|
|
394
798
|
|
|
395
|
-
|
|
799
|
+
### ๐ฅ Latest Request Wins with `override()`
|
|
396
800
|
|
|
397
|
-
|
|
801
|
+
Useful for search, preview generation, parsing, or any workflow where an older queued result becomes irrelevant.
|
|
398
802
|
|
|
399
803
|
```ts
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
worker.send(payload2),
|
|
405
|
-
]);
|
|
804
|
+
const result = await worker.override(
|
|
805
|
+
latestPayload,
|
|
806
|
+
'Superseded by a newer request'
|
|
807
|
+
);
|
|
406
808
|
```
|
|
407
809
|
|
|
408
|
-
|
|
810
|
+
`override()` cancels the current queued work and sends the new message after cancellation completes.
|
|
409
811
|
|
|
410
|
-
|
|
812
|
+
For immediate cancellation/reboot behavior:
|
|
411
813
|
|
|
412
|
-
|
|
814
|
+
```ts
|
|
815
|
+
await worker.override(latestPayload, 'Superseded', { force: true });
|
|
816
|
+
```
|
|
413
817
|
|
|
414
|
-
|
|
415
|
-
interface FilterSource {
|
|
416
|
-
filter: string,
|
|
417
|
-
collection: any[],
|
|
418
|
-
reportProgress: boolean,
|
|
419
|
-
}
|
|
818
|
+
> `force: true` reboots the Worker. A Worker created directly from an existing native `Worker` instance cannot be rebooted by the library.
|
|
420
819
|
|
|
421
|
-
|
|
422
|
-
const containsValue = (item: any, filter: string): boolean => {
|
|
423
|
-
const itemKeys = Object.keys(item);
|
|
820
|
+
### ๐ฅ Keep the Current Task, Replace the Rest
|
|
424
821
|
|
|
425
|
-
|
|
426
|
-
const prop = item[key] || null;
|
|
822
|
+
`overrideAfterCurrent()` allows the currently executing message to finish, cancels the other queued messages, then sends the replacement.
|
|
427
823
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
824
|
+
```ts
|
|
825
|
+
const result = await worker.overrideAfterCurrent(
|
|
826
|
+
latestPayload,
|
|
827
|
+
'Queue replaced'
|
|
828
|
+
);
|
|
829
|
+
```
|
|
431
830
|
|
|
432
|
-
|
|
433
|
-
});
|
|
434
|
-
};
|
|
831
|
+
This is useful when interrupting the current operation would be expensive or unsafe, but stale queued work should still be discarded.
|
|
435
832
|
|
|
436
|
-
|
|
437
|
-
const { payload } = message;
|
|
438
|
-
const { collection, filter = '', reportProgress: countProgress } = payload;
|
|
439
|
-
const { length: collectionLength } = collection;
|
|
440
|
-
const result = filter === '' ? collection : [];
|
|
441
|
-
const progressPerItem = collectionLength ? 100 / collectionLength : 0;
|
|
833
|
+
### ๐งน Explicit Cleanup
|
|
442
834
|
|
|
443
|
-
|
|
835
|
+
Dispose the worker when the owning feature or application no longer needs it.
|
|
444
836
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
currentProgress += progressPerItem;
|
|
449
|
-
message.reportProgress(currentProgress);
|
|
450
|
-
}
|
|
837
|
+
```ts
|
|
838
|
+
await worker.dispose();
|
|
839
|
+
```
|
|
451
840
|
|
|
452
|
-
|
|
841
|
+
`dispose()` cancels outstanding work, revokes the generated Worker URL when applicable, terminates Worker instances, and clears the pool.
|
|
453
842
|
|
|
454
|
-
|
|
455
|
-
}
|
|
456
|
-
}
|
|
843
|
+
---
|
|
457
844
|
|
|
458
|
-
|
|
459
|
-
|
|
845
|
+
## ๐งฐ API Reference
|
|
846
|
+
|
|
847
|
+
### `createEasyWebWorker(source, config?)`
|
|
848
|
+
|
|
849
|
+
Convenience factory that creates an `EasyWebWorker`.
|
|
850
|
+
|
|
851
|
+
Supported sources:
|
|
852
|
+
|
|
853
|
+
```ts
|
|
854
|
+
EasyWebWorkerBody
|
|
855
|
+
EasyWebWorkerBody[]
|
|
856
|
+
string
|
|
857
|
+
URL
|
|
858
|
+
Worker
|
|
859
|
+
Worker[]
|
|
860
|
+
```
|
|
861
|
+
|
|
862
|
+
Example:
|
|
863
|
+
|
|
864
|
+
```ts
|
|
865
|
+
const worker = createEasyWebWorker<Payload, Result>(workerBody, config);
|
|
866
|
+
```
|
|
867
|
+
|
|
868
|
+
### `EasyWebWorker<TPayload, TResult>`
|
|
869
|
+
|
|
870
|
+
You can also instantiate the class directly:
|
|
871
|
+
|
|
872
|
+
```ts
|
|
873
|
+
import { EasyWebWorker } from 'easy-web-worker';
|
|
874
|
+
|
|
875
|
+
const worker = new EasyWebWorker<Payload, Result>(workerBody, config);
|
|
876
|
+
```
|
|
877
|
+
|
|
878
|
+
### โ๏ธ Worker Configuration
|
|
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` |
|
|
890
|
+
|
|
891
|
+
### ๐ค `send(payload?, transfer?)`
|
|
892
|
+
|
|
893
|
+
Send a message to the default Worker handler.
|
|
894
|
+
|
|
895
|
+
```ts
|
|
896
|
+
const result = await worker.send(payload);
|
|
897
|
+
```
|
|
898
|
+
|
|
899
|
+
With transferables:
|
|
900
|
+
|
|
901
|
+
```ts
|
|
902
|
+
const result = await worker.send(payload, [buffer]);
|
|
903
|
+
```
|
|
904
|
+
|
|
905
|
+
Returns a `CancelablePromise<TResult>`.
|
|
906
|
+
|
|
907
|
+
### ๐ฏ `sendToMethod(method, payload?, transfer?)`
|
|
908
|
+
|
|
909
|
+
Send a message to a named `onMessage(method, callback)` handler.
|
|
910
|
+
|
|
911
|
+
```ts
|
|
912
|
+
const result = await worker.sendToMethod<Result, Payload>('calculate', payload);
|
|
913
|
+
```
|
|
914
|
+
|
|
915
|
+
### ๐ `cancelAll(reason?, config?)`
|
|
916
|
+
|
|
917
|
+
Cancel all currently tracked messages.
|
|
918
|
+
|
|
919
|
+
```ts
|
|
920
|
+
await worker.cancelAll('Canceled by user');
|
|
921
|
+
```
|
|
922
|
+
|
|
923
|
+
Force an immediate Worker reboot:
|
|
924
|
+
|
|
925
|
+
```ts
|
|
926
|
+
await worker.cancelAll('Reset', {
|
|
927
|
+
force: true,
|
|
460
928
|
});
|
|
461
929
|
```
|
|
462
930
|
|
|
463
|
-
|
|
931
|
+
### ๐ฅ `override(payload?, reason?, config?)`
|
|
932
|
+
|
|
933
|
+
Cancel current queued work and send a replacement message.
|
|
464
934
|
|
|
465
|
-
```
|
|
466
|
-
worker.
|
|
467
|
-
collection: [{ name: 'julio perez' }, { name: 'carol starling' }, { name: 'goku' }, { name: { firstname: 'johnny' } }],
|
|
468
|
-
filter: 'johnny',
|
|
469
|
-
reportProgress: true,
|
|
470
|
-
}).onProgress((progressPercentage) => console.log(progressPercentage))
|
|
471
|
-
.then((filtered: any[]) => console.log(filtered));
|
|
935
|
+
```ts
|
|
936
|
+
const result = await worker.override(payload, 'Superseded');
|
|
472
937
|
```
|
|
473
938
|
|
|
474
|
-
|
|
475
|
-
=> 25
|
|
476
|
-
=> 50
|
|
477
|
-
=> 75
|
|
478
|
-
=> 100
|
|
479
|
-
=> [{ name: { firstname: 'johnny' } }]
|
|
939
|
+
### ๐ฅ `overrideAfterCurrent(payload?, reason?, config?)`
|
|
480
940
|
|
|
481
|
-
|
|
941
|
+
Allow the current message to complete, cancel the remaining queue, then send a replacement.
|
|
482
942
|
|
|
483
|
-
|
|
943
|
+
```ts
|
|
944
|
+
const result = await worker.overrideAfterCurrent(payload, 'Queue replaced');
|
|
945
|
+
```
|
|
484
946
|
|
|
485
|
-
### `
|
|
947
|
+
### ๐ `reboot(reason?)`
|
|
486
948
|
|
|
487
|
-
|
|
949
|
+
Terminate the current Worker pool, cancel tracked messages, and initialize the Worker again.
|
|
488
950
|
|
|
489
|
-
|
|
951
|
+
```ts
|
|
952
|
+
worker.reboot('Worker configuration reset');
|
|
953
|
+
```
|
|
490
954
|
|
|
491
|
-
|
|
955
|
+
A Worker created from an existing native `Worker` instance cannot be rebooted by the library.
|
|
492
956
|
|
|
493
|
-
|
|
957
|
+
### ๐งน `dispose()`
|
|
494
958
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
959
|
+
Cancel outstanding messages and release the Worker resources.
|
|
960
|
+
|
|
961
|
+
```ts
|
|
962
|
+
await worker.dispose();
|
|
963
|
+
```
|
|
964
|
+
|
|
965
|
+
---
|
|
966
|
+
|
|
967
|
+
## ๐ฌ `IEasyWebWorkerMessage<TPayload, TResult>`
|
|
968
|
+
|
|
969
|
+
Every `onMessage()` handler receives an `IEasyWebWorkerMessage`.
|
|
970
|
+
|
|
971
|
+
### ๐ฅ `payload`
|
|
972
|
+
|
|
973
|
+
The payload sent from the main thread.
|
|
974
|
+
|
|
975
|
+
```ts
|
|
976
|
+
onMessage((message) => {
|
|
977
|
+
console.log(message.payload);
|
|
978
|
+
});
|
|
979
|
+
```
|
|
980
|
+
|
|
981
|
+
### โ
`resolve(result?, transfer?)`
|
|
982
|
+
|
|
983
|
+
Resolve the main-thread promise.
|
|
984
|
+
|
|
985
|
+
```ts
|
|
986
|
+
message.resolve(result);
|
|
987
|
+
```
|
|
988
|
+
|
|
989
|
+
### โ `reject(reason?, transfer?)`
|
|
990
|
+
|
|
991
|
+
Reject the main-thread promise.
|
|
992
|
+
|
|
993
|
+
```ts
|
|
994
|
+
message.reject(new Error('Something failed'));
|
|
995
|
+
```
|
|
996
|
+
|
|
997
|
+
### ๐ `cancel(reason?, transfer?)`
|
|
998
|
+
|
|
999
|
+
Cancel the request from inside the Worker.
|
|
1000
|
+
|
|
1001
|
+
```ts
|
|
1002
|
+
message.cancel('No longer needed');
|
|
1003
|
+
```
|
|
1004
|
+
|
|
1005
|
+
### ๐ `reportProgress(percentage, payload?, transfer?)`
|
|
1006
|
+
|
|
1007
|
+
Report progress while keeping the request pending.
|
|
1008
|
+
|
|
1009
|
+
```ts
|
|
1010
|
+
message.reportProgress(50, {
|
|
1011
|
+
processed: 500,
|
|
1012
|
+
total: 1000,
|
|
500
1013
|
});
|
|
1014
|
+
```
|
|
501
1015
|
|
|
502
|
-
|
|
1016
|
+
### ๐ฌ Lifecycle Subscriptions
|
|
503
1017
|
|
|
504
|
-
|
|
1018
|
+
```ts
|
|
1019
|
+
message.onResolve(callback);
|
|
1020
|
+
message.onReject(callback);
|
|
1021
|
+
message.onCancel(callback);
|
|
1022
|
+
message.onProgress(callback);
|
|
1023
|
+
message.onFinalize(callback);
|
|
1024
|
+
```
|
|
1025
|
+
|
|
1026
|
+
Each subscription returns an unsubscribe function.
|
|
505
1027
|
|
|
506
|
-
|
|
1028
|
+
### ๐ Status
|
|
1029
|
+
|
|
1030
|
+
```ts
|
|
1031
|
+
message.getStatus();
|
|
1032
|
+
message.isPending();
|
|
507
1033
|
```
|
|
508
1034
|
|
|
509
|
-
|
|
1035
|
+
---
|
|
1036
|
+
|
|
1037
|
+
## ๐จ Worker Source Options
|
|
510
1038
|
|
|
511
|
-
|
|
1039
|
+
| Source | Best for | Worker managed by `easy-web-worker`? |
|
|
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 |
|
|
512
1046
|
|
|
513
|
-
|
|
1047
|
+
All approaches use the same message-oriented API on the main thread.
|
|
514
1048
|
|
|
515
|
-
|
|
1049
|
+
---
|
|
516
1050
|
|
|
517
|
-
|
|
1051
|
+
## ๐ Learning Resources
|
|
518
1052
|
|
|
519
|
-
|
|
1053
|
+
- ๐ [Live example โ text diff](https://johnny-quesada-developer.github.io/easy-web-workers-example/)
|
|
1054
|
+
- ๐ฅ [Introduction video](https://www.youtube.com/watch?v=CK-Uri9lDOE)
|
|
1055
|
+
- ๐งฉ [Example repository](https://github.com/johnny-quesada-developer/easy-web-workers-example)
|
|
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)
|
|
520
1059
|
|
|
521
|
-
|
|
1060
|
+
---
|
|
522
1061
|
|
|
523
|
-
|
|
1062
|
+
## ๐ Framework Compatibility
|
|
524
1063
|
|
|
525
|
-
|
|
1064
|
+
`easy-web-worker` is built around the browser's native Worker API, so it is not tied to a UI framework.
|
|
1065
|
+
|
|
1066
|
+
| Environment | Usage |
|
|
1067
|
+
| ------------------ | --------------------- |
|
|
1068
|
+
| React | โ
|
|
|
1069
|
+
| Angular | โ
|
|
|
1070
|
+
| Vue | โ
|
|
|
1071
|
+
| Svelte | โ
|
|
|
1072
|
+
| Vanilla JavaScript | โ
|
|
|
1073
|
+
| TypeScript | โ
Strongly typed API |
|
|
1074
|
+
|
|
1075
|
+
The important requirement is a runtime with browser Web Worker support.
|
|
1076
|
+
|
|
1077
|
+
---
|
|
1078
|
+
|
|
1079
|
+
## ๐ Why Developers Choose This
|
|
1080
|
+
|
|
1081
|
+
### The Bottom Line
|
|
1082
|
+
|
|
1083
|
+
| What You Get | What You Avoid |
|
|
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 |
|
|
1094
|
+
|
|
1095
|
+
---
|
|
1096
|
+
|
|
1097
|
+
## ๐ค Collaborators
|
|
1098
|
+
|
|
1099
|
+
<div align="center">
|
|
1100
|
+
|
|
1101
|
+
<table>
|
|
1102
|
+
<tr>
|
|
1103
|
+
<td align="center">
|
|
1104
|
+
<a href="https://github.com/johnny-quesada-developer">
|
|
1105
|
+
<img src="https://avatars.githubusercontent.com/u/62082152?v=4&s=150" width="100" alt="Johnny Quesada" />
|
|
1106
|
+
<br />
|
|
1107
|
+
<sub><b>Johnny Quesada</b></sub>
|
|
1108
|
+
</a>
|
|
1109
|
+
</td>
|
|
1110
|
+
<td align="center">
|
|
1111
|
+
<a href="https://github.com/gabrielecirulli">
|
|
1112
|
+
<img src="https://avatars.githubusercontent.com/u/886011?v=4&s=150" width="100" alt="Gabriele Cirulli" />
|
|
1113
|
+
<br />
|
|
1114
|
+
<sub><b>Gabriele Cirulli</b></sub>
|
|
1115
|
+
</a>
|
|
1116
|
+
</td>
|
|
1117
|
+
</tr>
|
|
1118
|
+
</table>
|
|
1119
|
+
|
|
1120
|
+
</div>
|
|
1121
|
+
|
|
1122
|
+
---
|
|
1123
|
+
|
|
1124
|
+
## ๐ Get Started Now
|
|
1125
|
+
|
|
1126
|
+
```bash
|
|
1127
|
+
npm install easy-web-worker
|
|
1128
|
+
```
|
|
1129
|
+
|
|
1130
|
+
Then:
|
|
1131
|
+
|
|
1132
|
+
```ts
|
|
1133
|
+
import { createEasyWebWorker } from 'easy-web-worker';
|
|
1134
|
+
|
|
1135
|
+
const worker = createEasyWebWorker<string, string>(({ onMessage }) => {
|
|
1136
|
+
onMessage((message) => {
|
|
1137
|
+
message.resolve(message.payload.toUpperCase());
|
|
1138
|
+
});
|
|
1139
|
+
});
|
|
1140
|
+
|
|
1141
|
+
console.log(await worker.send('hello worker'));
|
|
1142
|
+
// HELLO WORKER
|
|
1143
|
+
```
|
|
526
1144
|
|
|
527
|
-
|
|
1145
|
+
**That's it. You're running work in a real Web Worker.** ๐
|
|
528
1146
|
|
|
529
|
-
|
|
530
|
-
- `options` - (optional) Additional send options.
|
|
1147
|
+
---
|
|
531
1148
|
|
|
532
|
-
|
|
1149
|
+
<div align="center">
|
|
533
1150
|
|
|
534
|
-
|
|
1151
|
+
### Built with โค๏ธ for developers who want the main thread to stay responsive
|
|
535
1152
|
|
|
536
|
-
[
|
|
537
|
-
[](https://github.com/gabrielecirulli)
|
|
1153
|
+
**[โญ Star on GitHub](https://github.com/johnny-quesada-developer/easy-web-worker)** โข **[๐ Report Issues](https://github.com/johnny-quesada-developer/easy-web-worker/issues)** โข **[๐งฉ Examples](https://github.com/johnny-quesada-developer/easy-web-workers-example)**
|
|
538
1154
|
|
|
539
|
-
|
|
1155
|
+
</div>
|