Repository navigation
Expand file tree
/
Copy pathindex.md
More file actions
806 lines (604 loc) · 36.2 KB
/
Copy pathindex.md
File metadata and controls
806 lines (604 loc) · 36.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
---
title: Using Web Workers
slug: Web/API/Web_Workers_API/Using_web_workers
page-type: guide
spec-urls: https://html.spec.whatwg.org/multipage/#workers
---
{{DefaultAPISidebar("Web Workers API")}}
Web Workers are a simple means for web content to run scripts in background threads. The worker thread can perform tasks without interfering with the user interface. In addition, they can make network requests using the {{domxref("WorkerGlobalScope/fetch", "fetch()")}} or {{domxref("XMLHttpRequest")}} APIs. Once created, a worker can send messages to the JavaScript code that created it by posting messages to an event handler specified by that code (and vice versa).
This article provides a detailed introduction to using web workers.
## Web Workers API
A worker is an object created using a constructor (e.g., {{domxref("Worker.Worker", "Worker()")}}) that runs a named JavaScript file — this file contains the code that will run in the worker thread; workers run in another global context that is different from the current {{domxref("window")}}. Thus, using the {{domxref("window")}} shortcut to get the current global scope (instead of {{domxref("window.self","self")}}) within a {{domxref("Worker")}} will return an error.
The worker context is represented by a {{domxref("DedicatedWorkerGlobalScope")}} object in the case of dedicated workers (standard workers that are utilized by a single script; shared workers use {{domxref("SharedWorkerGlobalScope")}}). A dedicated worker is only accessible from the script that first spawned it, whereas shared workers can be accessed from multiple scripts.
> [!NOTE]
> See [The Web Workers API landing page](/en-US/docs/Web/API/Web_Workers_API) for reference documentation on workers and additional guides.
You can run whatever code you like inside the worker thread, with some exceptions. For example, you can't directly manipulate the DOM from inside a worker, or use some default methods and properties of the {{domxref("window")}} object. But you can use a large number of items available under `window`, including [WebSockets](/en-US/docs/Web/API/WebSockets_API), and data storage mechanisms like [IndexedDB](/en-US/docs/Web/API/IndexedDB_API). See [Functions and classes available to workers](/en-US/docs/Web/API/Web_Workers_API/Functions_and_classes_available_to_workers) for more details.
Data is sent between workers and the main thread via a system of messages — both sides send their messages using the `postMessage()` method, and respond to messages via the `onmessage` event handler (the message is contained within the {{domxref("Worker/message_event", "message")}} event's data attribute). The data is copied rather than shared.
Workers may in turn spawn new workers, as long as those workers are hosted within the same {{glossary("origin")}} as the parent page.
In addition, workers can make network requests using the {{domxref("WorkerGlobalScope/fetch", "fetch()")}} or [`XMLHttpRequest`](/en-US/docs/Web/API/XMLHttpRequest) APIs (although note that the {{domxref("XMLHttpRequest.responseXML", "responseXML")}} attribute of `XMLHttpRequest` will always be `null`).
## Dedicated workers
As mentioned above, a dedicated worker is only accessible by the script that called it. In this section we'll discuss the JavaScript found in our [Basic dedicated worker example](https://github.com/mdn/dom-examples/tree/main/web-workers/simple-web-worker) ([run dedicated worker](https://mdn.github.io/dom-examples/web-workers/simple-web-worker/)): This allows you to enter two numbers to be multiplied together. The numbers are sent to a dedicated worker, multiplied together, and the result is returned to the page and displayed.
This example is rather trivial, but we decided to keep it simple while introducing you to basic worker concepts. More advanced details are covered later on in the article.
### Worker feature detection
For slightly more controlled error handling and backwards compatibility, it is a good idea to wrap your worker accessing code in the following ([main.js](https://github.com/mdn/dom-examples/blob/main/web-workers/simple-web-worker/main.js)):
```js
if (window.Worker) {
// …
}
```
### Spawning a dedicated worker
Creating a new worker is simple. All you need to do is call the {{domxref("Worker.Worker", "Worker()")}} constructor, specifying the URI of a script to execute in the worker thread ([main.js](https://github.com/mdn/dom-examples/blob/main/web-workers/simple-web-worker/main.js)):
```js
const myWorker = new Worker("worker.js");
```
> [!NOTE]
> Bundlers, including [webpack](https://webpack.js.org/guides/web-workers/), [Vite](https://vite.dev/guide/features.html#web-workers), and [Parcel](https://parceljs.org/languages/javascript/#web-workers), recommend passing URLs that are resolved relative to [`import.meta.url`](/en-US/docs/Web/JavaScript/Reference/Operators/import.meta#url) to the `Worker()` constructor. For example:
>
> ```js
> const myWorker = new Worker(new URL("worker.js", import.meta.url));
> ```
>
> This way, the path is relative to the current script instead of the current HTML page, which allows the bundler to safely do optimizations like renaming (because otherwise the `worker.js` URL may point to a file not controlled by the bundler, so it cannot make any assumptions).
### Sending messages to and from a dedicated worker
The magic of workers happens via the {{domxref("Worker.postMessage", "postMessage()")}} method and the {{domxref("Worker.message_event", "onmessage")}} event handler. When you want to send a message to the worker, you post messages to it like this ([main.js](https://github.com/mdn/dom-examples/blob/main/web-workers/simple-web-worker/main.js)):
```js
[first, second].forEach((input) => {
input.onchange = () => {
myWorker.postMessage([first.value, second.value]);
console.log("Message posted to worker");
};
});
```
So here we have two {{htmlelement("input")}} elements represented by the variables `first` and `second`; when the value of either is changed, `myWorker.postMessage([first.value,second.value])` is used to send the value inside both to the worker, as an array. You can send pretty much anything you like in the message.
In the worker, we can respond when the message is received by writing an event handler block like this ([worker.js](https://github.com/mdn/dom-examples/blob/main/web-workers/simple-web-worker/worker.js)):
```js
onmessage = (e) => {
console.log("Message received from main script");
const workerResult = `Result: ${e.data[0] * e.data[1]}`;
console.log("Posting message back to main script");
postMessage(workerResult);
};
```
The `onmessage` handler allows us to run some code whenever a message is received, with the message itself being available in the `message` event's `data` attribute. Here we multiply together the two numbers then use `postMessage()` again, to post the result back to the main thread.
Back in the main thread, we use `onmessage` again, to respond to the message sent back from the worker:
```js
myWorker.onmessage = (e) => {
result.textContent = e.data;
console.log("Message received from worker");
};
```
Here we grab the message event data and set it as the `textContent` of the result paragraph, so the user can see the result of the calculation.
> [!NOTE]
> Notice that `onmessage` and `postMessage()` need to be hung off the `Worker` object when used in the main script thread, but not when used in the worker. This is because, inside the worker, the worker is effectively the global scope.
> [!NOTE]
> When a message is passed between the main thread and worker, it is copied or "transferred" (moved), not shared. Read [Transferring data to and from workers: further details](#transferring_data_to_and_from_workers_further_details) for a much more thorough explanation.
### Terminating a worker
If you need to immediately terminate a running worker from the main thread, you can do so by calling the worker's {{domxref("Worker", "terminate")}} method:
```js
myWorker.terminate();
```
The worker thread is killed immediately.
### Handling errors
When a runtime error occurs in the worker, its `onerror` event handler is called. It receives an event named `error` which implements the `ErrorEvent` interface.
The event doesn't bubble and is cancelable; to prevent the default action from taking place, the worker can call the error event's [`preventDefault()`](/en-US/docs/Web/API/Event/preventDefault) method.
The error event has the following three fields that are of interest:
- `message`
- : A human-readable error message.
- `filename`
- : The name of the script file in which the error occurred.
- `lineno`
- : The line number of the script file on which the error occurred.
### Spawning subworkers
Workers may spawn more workers if they wish. So-called sub-workers must be hosted within the same origin as the parent page. Also, the URIs for subworkers are resolved relative to the parent worker's location rather than that of the owning page. This makes it easier for workers to keep track of where their dependencies are.
### Importing scripts and libraries
Worker threads have access to a global function, `importScripts()`, which lets them import scripts. It accepts zero or more URIs as parameters to resources to import; all the following examples are valid:
```js
importScripts(); /* imports nothing */
importScripts("foo.js"); /* imports just "foo.js" */
importScripts("foo.js", "bar.js"); /* imports two scripts */
importScripts(
"//example.com/hello.js",
); /* You can import scripts from other origins */
```
The browser loads each listed script and executes it. Any global objects from each script may then be used by the worker. If the script can't be loaded, `NETWORK_ERROR` is thrown, and subsequent code will not be executed. Previously executed code (including code deferred using {{domxref("WorkerGlobalScope.setTimeout", "setTimeout()")}}) will still be functional though. Function declarations **after** the `importScripts()` method are also kept, since these are always evaluated before the rest of the code.
> [!NOTE]
> Scripts may be downloaded in any order, but will be executed in the order in which you pass the filenames into `importScripts()`. This is done synchronously; `importScripts()` does not return until all the scripts have been loaded and executed.
## Shared workers
A shared worker is accessible by multiple scripts — even if they are being accessed by different windows, iframes or even workers. In this section we'll discuss the JavaScript found in our [Basic shared worker example](https://github.com/mdn/dom-examples/tree/main/web-workers/simple-shared-worker) ([run shared worker](https://mdn.github.io/dom-examples/web-workers/simple-shared-worker/)): This is very similar to the basic dedicated worker example, except that it has two functions available handled by different script files: _multiplying two numbers_, or _squaring a number_. Both scripts use the same worker to do the actual calculation required.
Here we'll concentrate on the differences between dedicated and shared workers. Note that in this example we have two HTML pages, each with JavaScript applied that uses the same single worker file.
> [!NOTE]
> If SharedWorker can be accessed from several browsing contexts, all those browsing contexts must share the exact same origin (same protocol, host, and port).
> [!NOTE]
> In Firefox, shared workers cannot be shared between documents loaded in private and non-private windows ([Firefox bug 1177621](https://bugzil.la/1177621)).
### Spawning a shared worker
Spawning a new shared worker is pretty much the same as with a dedicated worker, but with a different constructor name (see [index.html](https://github.com/mdn/dom-examples/blob/main/web-workers/simple-shared-worker/index.html) and [index2.html](https://github.com/mdn/dom-examples/blob/main/web-workers/simple-shared-worker/index2.html)) — each one has to spin up the worker using code like the following:
```js
const myWorker = new SharedWorker("worker.js");
```
One big difference is that with a shared worker you have to communicate via a `port` object — an explicit port is opened that the scripts can use to communicate with the worker (this is done implicitly in the case of dedicated workers).
The port connection needs to be started either implicitly by use of the `onmessage` event handler or explicitly with the `start()` method before any messages can be posted. Calling `start()` is only needed if the `message` event is wired up via the `addEventListener()` method.
> [!NOTE]
> When using the `start()` method to open the port connection, it needs to be called from both the parent thread and the worker thread if two-way communication is needed.
### Shared worker lifetime
Shared workers are shut down when they are no longer referenced by any windows, iframes, or workers.
Browsers _may_ keep workers alive between same-origin navigations to avoid the cost of restarting a shared worker used by a site when the user is navigating from page to page within that site.
The [`extendedLifetime`](/en-US/docs/Web/API/SharedWorker/SharedWorker#extendedlifetime) constructor option may also be specified to keep a shared worker alive for a short period after all references to it have closed:
```js
const worker = new SharedWorker("worker.js", { extendedLifetime: true });
```
This allows work to be done after the user navigates away from the page, such as writing state information to storage, or sending analytics data back to servers.
This is more ergonomic than using a service worker for the same purpose.
### Sending messages to and from a shared worker
Now messages can be sent to the worker as before, but the `postMessage()` method has to be invoked through the port object (again, you'll see similar constructs in both [multiply.js](https://github.com/mdn/dom-examples/blob/main/web-workers/simple-shared-worker/multiply.js) and [square.js](https://github.com/mdn/dom-examples/blob/main/web-workers/simple-shared-worker/square.js)):
```js
squareNumber.onchange = () => {
myWorker.port.postMessage([squareNumber.value, squareNumber.value]);
console.log("Message posted to worker");
};
```
Now, on to the worker. There is a bit more complexity here as well ([worker.js](https://github.com/mdn/dom-examples/blob/main/web-workers/simple-shared-worker/worker.js)):
```js
onconnect = (e) => {
const port = e.ports[0];
port.onmessage = (e) => {
const workerResult = `Result: ${e.data[0] * e.data[1]}`;
port.postMessage(workerResult);
};
};
```
First, we use an `onconnect` handler to fire code when a connection to the port happens (i.e., when the `onmessage` event handler in the parent thread is set up, or when the `start()` method is explicitly called in the parent thread).
We use the `ports` attribute of this event object to grab the port and store it in a variable.
Next, we add an `onmessage` handler on the port to do the calculation and return the result to the main thread. Setting up this `onmessage` handler in the worker thread also implicitly opens the port connection back to the parent thread, so the call to `port.start()` is not actually needed, as noted above.
Finally, back in the main script, we deal with the message (again, you'll see similar constructs in both [multiply.js](https://github.com/mdn/dom-examples/blob/main/web-workers/simple-shared-worker/multiply.js) and [square.js](https://github.com/mdn/dom-examples/blob/main/web-workers/simple-shared-worker/square.js)):
```js
myWorker.port.onmessage = (e) => {
result2.textContent = e.data;
console.log("Message received from worker");
};
```
When a message comes back through the port from the worker, we insert the calculation result inside the appropriate result paragraph.
## About thread safety
The {{domxref("Worker")}} interface spawns real OS-level threads, and mindful programmers may be concerned that concurrency can cause "interesting" effects in your code if you aren't careful.
However, since web workers have carefully controlled communication points with other threads, it's actually very hard to cause concurrency problems. There's no access to non-thread-safe components or the DOM. And you have to pass specific data in and out of a thread through serialized objects. So you have to work really hard to cause problems in your code.
## Content security policy
Workers are considered to have their own execution context, distinct from the document that created them. For this reason they are, in general, not governed by the [content security policy](/en-US/docs/Mozilla/Add-ons/WebExtensions/Content_Security_Policy) of the document (or parent worker) that created them. So for example, suppose a document is served with the following header:
```http
Content-Security-Policy: script-src 'self'
```
Among other things, this will prevent any scripts it includes from using [`eval()`](/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval). However, if the script constructs a worker, code running in the worker's context _will_ be allowed to use `eval()`.
To specify a content security policy for the worker, set a [Content-Security-Policy](/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy) response header for the request which delivered the worker script itself.
The exception to this is if the worker script's origin is a globally unique identifier (for example, if its URL has a scheme of data or blob). In this case, the worker does inherit the CSP of the document or worker that created it.
## Transferring data to and from workers: further details
Data passed between the main page and workers is _copied_, not shared (except for certain objects that can be explicitly [shared](#sharing_data)). Objects are serialized as they're handed to the worker, and subsequently, de-serialized on the other end. The page and worker **do not share the same instance**, so the end result is that **a duplicate** is created on each end. Most browsers implement this feature as [structured cloning](/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm).
As you probably know by now, data is exchanged between the two threads via messages using `postMessage()`, and the `message` event's {{domxref("MessageEvent.data", "data")}} attribute contains data passed back from the worker.
**example.html**: (the main page):
```js
const myWorker = new Worker("my_task.js");
myWorker.onmessage = (event) => {
console.log(`Worker said : "${event.data}"`);
};
myWorker.postMessage({ lastUpdate: new Date() });
```
**my_task.js** (the worker):
```js
self.onmessage = (event) => {
postMessage(`Last updated: ${event.data.lastUpdate.toDateString()}`);
};
```
The [structured cloning](/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm) algorithm can accept JSON and a few things that JSON can't — like circular references.
### Passing data examples
#### Example 1: Advanced passing JSON Data and creating a switching system
If you have to pass some complex data and have to call many different functions both on the main page and in the Worker, you can create a system which groups everything together.
First, we create a `QueryableWorker` class that takes the URL of the worker, a default listener, and an error handler, and this class is going to keep track of a list of listeners and help us communicate with the worker:
```js
function QueryableWorker(url, defaultListener, onError) {
const worker = new Worker(url);
const listeners = {};
this.defaultListener = defaultListener ?? (() => {});
if (onError) {
worker.onerror = onError;
}
this.postMessage = (message) => {
worker.postMessage(message);
};
this.terminate = () => {
worker.terminate();
};
}
```
Then we add the methods of adding/removing listeners:
```js
this.addListeners = (name, listener) => {
listeners[name] = listener;
};
this.removeListeners = (name) => {
delete listeners[name];
};
```
Here we let the worker handle two simple operations for illustration: getting the difference of two numbers and making an alert after three seconds. In order to achieve that we first implement a `sendQuery` method which queries if the worker actually has the corresponding methods to do what we want.
```js
// This functions takes at least one argument, the method name we want to query.
// Then we can pass in the arguments that the method needs.
this.sendQuery = (queryMethod, ...queryMethodArguments) => {
if (!queryMethod) {
throw new TypeError(
"QueryableWorker.sendQuery takes at least one argument",
);
}
worker.postMessage({
queryMethod,
queryMethodArguments,
});
};
```
We finish QueryableWorker with the `onmessage` method. If the worker has the corresponding methods we queried, it should return the name of the corresponding listener and the arguments it needs, we just need to find it in `listeners`:
```js
worker.onmessage = (event) => {
if (
event.data instanceof Object &&
Object.hasOwn(event.data, "queryMethodListener") &&
Object.hasOwn(event.data, "queryMethodArguments")
) {
listeners[event.data.queryMethodListener].apply(
this,
event.data.queryMethodArguments,
);
} else {
this.defaultListener(event.data);
}
};
```
Now onto the worker. First we need to have the methods to handle the two simple operations:
```js
const queryableFunctions = {
getDifference(a, b) {
reply("printStuff", a - b);
},
waitSomeTime() {
setTimeout(() => {
reply("doAlert", 3, "seconds");
}, 3000);
},
};
function reply(queryMethodListener, ...queryMethodArguments) {
if (!queryMethodListener) {
throw new TypeError("reply - takes at least one argument");
}
postMessage({
queryMethodListener,
queryMethodArguments,
});
}
// This method is called when main page calls QueryWorker's postMessage
// method directly
function defaultReply(message) {
// do something
}
```
And the `onmessage` method is now trivial:
```js
onmessage = (event) => {
if (
event.data instanceof Object &&
Object.hasOwn(event.data, "queryMethod") &&
Object.hasOwn(event.data, "queryMethodArguments")
) {
queryableFunctions[event.data.queryMethod].apply(
self,
event.data.queryMethodArguments,
);
} else {
defaultReply(event.data);
}
};
```
Here is the full implementation:
**example.html** (the main page):
```html
<ul>
<li>
<button id="first-action">What is the difference between 5 and 3?</button>
</li>
<li>
<button id="second-action">Wait 3 seconds</button>
</li>
<li>
<button id="terminate">terminate() the Worker</button>
</li>
</ul>
```
It needs to execute the following script, either inline or as an external file:
```js
// QueryableWorker instances methods:
// * sendQuery(queryable function name, argument to pass 1, argument to pass 2, etc. etc.): calls a Worker's queryable function
// * postMessage(string or JSON Data): see Worker.prototype.postMessage()
// * terminate(): terminates the Worker
// * addListener(name, function): adds a listener
// * removeListener(name): removes a listener
// QueryableWorker instances properties:
// * defaultListener: the default listener executed only when the Worker calls the postMessage() function directly
function QueryableWorker(url, defaultListener, onError) {
const worker = new Worker(url);
const listeners = {};
this.defaultListener = defaultListener ?? (() => {});
if (onError) {
worker.onerror = onError;
}
this.postMessage = (message) => {
worker.postMessage(message);
};
this.terminate = () => {
worker.terminate();
};
this.addListener = (name, listener) => {
listeners[name] = listener;
};
this.removeListener = (name) => {
delete listeners[name];
};
// This functions takes at least one argument, the method name we want to query.
// Then we can pass in the arguments that the method needs.
this.sendQuery = (queryMethod, ...queryMethodArguments) => {
if (!queryMethod) {
throw new TypeError(
"QueryableWorker.sendQuery takes at least one argument",
);
}
worker.postMessage({
queryMethod,
queryMethodArguments,
});
};
worker.onmessage = (event) => {
if (
event.data instanceof Object &&
Object.hasOwn(event.data, "queryMethodListener") &&
Object.hasOwn(event.data, "queryMethodArguments")
) {
listeners[event.data.queryMethodListener].apply(
this,
event.data.queryMethodArguments,
);
} else {
this.defaultListener(event.data);
}
};
}
// your custom "queryable" worker
const myTask = new QueryableWorker("my_task.js");
// your custom "listeners"
myTask.addListener("printStuff", (result) => {
document
.getElementById("firstLink")
.parentNode.appendChild(
document.createTextNode(`The difference is ${result}!`),
);
});
myTask.addListener("doAlert", (time, unit) => {
alert(`Worker waited for ${time} ${unit} :-)`);
});
document.getElementById("first-action").addEventListener("click", () => {
myTask.sendQuery("getDifference", 5, 3);
});
document.getElementById("second-action").addEventListener("click", () => {
myTask.sendQuery("waitSomeTime");
});
document.getElementById("terminate").addEventListener("click", () => {
myTask.terminate();
});
```
**my_task.js** (the worker):
```js
const queryableFunctions = {
// example #1: get the difference between two numbers:
getDifference(minuend, subtrahend) {
reply("printStuff", minuend - subtrahend);
},
// example #2: wait three seconds
waitSomeTime() {
setTimeout(() => {
reply("doAlert", 3, "seconds");
}, 3000);
},
};
// system functions
function defaultReply(message) {
// your default PUBLIC function executed only when main page calls the queryableWorker.postMessage() method directly
// do something
}
function reply(queryMethodListener, ...queryMethodArguments) {
if (!queryMethodListener) {
throw new TypeError("reply - not enough arguments");
}
postMessage({
queryMethodListener,
queryMethodArguments,
});
}
onmessage = (event) => {
if (
event.data instanceof Object &&
Object.hasOwn(event.data, "queryMethod") &&
Object.hasOwn(event.data, "queryMethodArguments")
) {
queryableFunctions[event.data.queryMethod].apply(
self,
event.data.queryMethodArguments,
);
} else {
defaultReply(event.data);
}
};
```
It is possible to switch the content of each mainpage -> worker and worker -> mainpage message. And the property names "queryMethod", "queryMethodListeners", "queryMethodArguments" can be anything as long as they are consistent in `QueryableWorker` and the `worker`.
### Passing data by transferring ownership (transferable objects)
Modern browsers contain an additional way to pass certain types of objects to or from a worker with high performance. [Transferable objects](/en-US/docs/Web/API/Web_Workers_API/Transferable_objects) are transferred from one context to another with a zero-copy operation, which results in a vast performance improvement when sending large data sets.
For example, when transferring an {{jsxref("ArrayBuffer")}} from your main app to a worker script, the original {{jsxref("ArrayBuffer")}} is cleared and no longer usable. Its content is (quite literally) transferred to the worker context.
```js
// Create a 32MB "file" and fill it with consecutive values from 0 to 255 – 32MB = 1024 * 1024 * 32
const uInt8Array = new Uint8Array(1024 * 1024 * 32).map((v, i) => i);
worker.postMessage(uInt8Array.buffer, [uInt8Array.buffer]);
```
### Sharing data
The {{jsxref("SharedArrayBuffer")}} object allows two threads, such as the worker and the main thread, to simultaneously operate on the same memory span and exchange data without going through the messaging mechanism. Using shared memory does come with significant determinism, security, and performance concerns, some of which are outlined in the [JavaScript execution model](/en-US/docs/Web/JavaScript/Reference/Execution_model#agent_clusters_and_memory_sharing) article.
## Embedded workers
There is not an "official" way to embed the code of a worker within a web page, like {{HTMLElement("script")}} elements do for normal scripts. But a {{HTMLElement("script")}} element that does not have a `src` attribute and has a `type` attribute that does not identify an executable MIME type can be considered a data block element that JavaScript could use. "Data blocks" is a more general feature of HTML that can carry almost any textual data. So, a worker could be embedded in this way:
```html
<!doctype html>
<html lang="en-US">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<title>MDN Example - Embedded worker</title>
<script type="text/js-worker">
// This script WON'T be parsed by JS engines because its MIME type is text/js-worker.
const myVar = "Hello World!";
// Rest of your worker code goes here.
</script>
<script>
// This script WILL be parsed by JS engines because its MIME type is text/javascript.
function pageLog(sMsg) {
// Use a fragment: browser will only render/reflow once.
const frag = document.createDocumentFragment();
frag.appendChild(document.createTextNode(sMsg));
frag.appendChild(document.createElement("br"));
document.querySelector("#logDisplay").appendChild(frag);
}
</script>
<script type="text/js-worker">
// This script WON'T be parsed by JS engines because its MIME type is text/js-worker.
onmessage = (event) => {
postMessage(myVar);
};
// Rest of your worker code goes here.
</script>
<script>
// This script WILL be parsed by JS engines because its MIME type is text/javascript.
// In the past blob builder existed, but now we use Blob
const blob = new Blob(
Array.prototype.map.call(
document.querySelectorAll("script[type='text/js-worker']"),
(script) => script.textContent,
),
{ type: "text/javascript" },
);
// Creating a new global "worker" variable from all our "text/js-worker" scripts.
const worker = new Worker(window.URL.createObjectURL(blob));
worker.onmessage = (event) => {
pageLog(`Received: ${event.data}`);
};
</script>
</head>
<body>
<div id="logDisplay"></div>
<script>
// Start the worker.
worker.postMessage("");
</script>
</body>
</html>
```
The embedded worker is now nested into a new custom `document.worker` property.
It is also worth noting that you can also convert a function into a Blob, then generate an object URL from that blob. For example:
```js
function fn2workerURL(fn) {
const blob = new Blob([`(${fn.toString()})()`], { type: "text/javascript" });
return URL.createObjectURL(blob);
}
```
## Further examples
This section provides further examples of how to use web workers.
### Performing computations in the background
Workers are mainly useful for allowing your code to perform processor-intensive calculations without blocking the user interface thread. In this example, a worker is used to calculate Fibonacci numbers.
#### The JavaScript code
The following JavaScript code is stored in the "fibonacci.js" file referenced by the HTML in the next section.
```js
self.onmessage = (event) => {
const userNum = Number(event.data);
self.postMessage(fibonacci(userNum));
};
function fibonacci(num) {
let a = 1;
let b = 0;
while (num > 0) {
[a, b] = [a + b, a];
num--;
}
return b;
}
```
The worker sets the property `onmessage` to a function which will receive messages sent when the worker object's `postMessage()` is called. This performs the math and eventually returns the result back to the main thread.
#### The HTML code
```html
<form>
<div>
<label for="number">
Enter a number that is a zero-based index position in the fibonacci
sequence to see what number is in that position. For example, enter 6 and
you'll get a result of 8 — the fibonacci number at index position 6 is 8.
</label>
<input type="number" id="number" />
</div>
<div>
<input type="submit" />
</div>
</form>
<p id="result"></p>
```
It needs to execute the following script, either inline or as an external file:
```js
const form = document.querySelector("form");
const input = document.querySelector('input[type="number"]');
const result = document.querySelector("p#result");
const worker = new Worker("fibonacci.js");
worker.onmessage = (event) => {
result.textContent = event.data;
console.log(`Got: ${event.data}`);
};
worker.onerror = (error) => {
console.log(`Worker error: ${error.message}`);
throw error;
};
form.onsubmit = (e) => {
e.preventDefault();
worker.postMessage(input.value);
input.value = "";
};
```
The web page creates a `<p>` element with the ID `result`, which gets used to display the result, then spawns the worker. After spawning the worker, the `onmessage` handler is configured to display the results by setting the contents of the `<p>` element, and the `onerror` handler is set to log the error message to the devtools console.
Finally, a message is sent to the worker to start it.
[Try this example live](https://mdn.github.io/dom-examples/web-workers/fibonacci-worker/).
### Dividing tasks among multiple workers
As multicore computers become increasingly common, it's often useful to divide computationally complex tasks among multiple workers, which may then perform those tasks on multiple-processor cores.
## Other types of workers
In addition to dedicated and shared web workers, there are other types of workers available:
- [ServiceWorkers](/en-US/docs/Web/API/Service_Worker_API) essentially act as proxy servers that sit between web applications, and the browser and network (when available). They are intended to (among other things) enable the creation of effective offline experiences, intercepting network requests and taking appropriate action based on whether the network is available and updated assets reside on the server. They will also allow access to push notifications and background sync APIs.
- [Audio Worklet](/en-US/docs/Web/API/Web_Audio_API#audio_processing_in_javascript) provide the ability for direct scripted audio processing to be done in a worklet (a lightweight version of worker) context.
## Debugging worker threads
Most browsers enable you to debug web workers in their JavaScript debuggers in _exactly the same way_ as debugging the main thread! For example, both Firefox and Chrome list JavaScript source files for both the main thread and active worker threads, and all of these files can be opened to set breakpoints and logpoints.
To learn how to debug web workers, see the documentation for each browser's JavaScript debugger:
- [Chrome Sources panel](https://developer.chrome.com/docs/devtools/sources)
- [Firefox JavaScript Debugger](https://firefox-source-docs.mozilla.org/devtools-user/debugger/)
To open devtools for web workers, you can use the following URLs:
- Edge: `edge://inspect/`
- Chrome: `chrome://inspect/`
- Firefox: `about:debugging#/runtime/this-firefox`
These pages show an overview of all service workers. You need to find the relevant one by the URL and then click _inspect_ to access devtools such as the console and debugger for that worker.
## Functions and interfaces available in workers
You can use most standard JavaScript features inside a web worker, including:
- {{domxref("Navigator")}}
- {{domxref("WorkerGlobalScope.fetch", "fetch()")}}
- {{jsxref("Global_Objects/Array", "Array")}}, {{jsxref("Global_Objects/Date", "Date")}}, {{jsxref("Global_Objects/Math", "Math")}}, and {{jsxref("Global_Objects/String", "String")}}
- {{domxref("WorkerGlobalScope.setTimeout", "setTimeout()")}} and {{domxref("WorkerGlobalScope.setInterval", "setInterval()")}}
The main thing you _can't_ do in a Worker is directly affect the parent page. This includes manipulating the DOM and using that page's objects. You have to do it indirectly, by sending a message back to the main script via {{domxref("DedicatedWorkerGlobalScope.postMessage()")}}, then doing the changes in event handler.
> [!NOTE]
> You can test whether a method or interface is available to workers using the [Worker Playground](https://mdn.github.io/dom-examples/web-workers/worker-playground/).
> [!NOTE]
> For a complete list of functions available to workers, see [Functions and interfaces available to workers](/en-US/docs/Web/API/Web_Workers_API/Functions_and_classes_available_to_workers).
## Specifications
{{Specifications}}
## See also
- {{domxref("Worker")}} interface
- {{domxref("SharedWorker")}} interface
- [Functions available to workers](/en-US/docs/Web/API/Web_Workers_API/Functions_and_classes_available_to_workers)
- {{domxref("OffscreenCanvas")}} interface