Web Workers makes it possible to run a script operation in a background thread separate from the main execution thread of a web application.
Introduction
Web Workers makes it possible to run a script operation in a background thread separate from the main execution thread of a web application. The advantage of this is that laborious processing can be performed in a separate thread, allowing the main (usually the UI) thread to run without being blocked/slowed down.
Concepts and usage
A worker is an object created using a constructor (e.g., Worker()) that runs a named JavaScript file — this file contains the code that will run in the worker thread.
In addition to the standard JavaScript set of functions (such as String, Array, Object, JSON, etc.), you can run almost any code you like inside a worker thread. There are some exceptions: for example, you can’t directly manipulate the DOM from inside a worker or use some default methods and properties of the Window object. For information about the code that you can run see supported functions, and supported Web APIs.
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 message event’s data property). The data is copied rather than shared.
Workers may in turn spawn new workers, as long as those workers are hosted within the same origin as the parent page.
In addition, workers can make network requests using the fetch() API.
Worker types
There are a number of different types of workers:
-
Dedicated workers are workers that are utilized by a single script. This context is represented by a
DedicatedWorkerGlobalScopeobject. -
Shared workers are workers that can be utilized by multiple scripts running in different windows, IFrames, etc., as long as they are in the same domain as the worker. They are a little more complex than dedicated workers — scripts must communicate via an active port.
-
Service Workers essentially act as proxy servers that sit between web applications, the browser, and the network (when available). They are intended, among other things, to enable the creation of effective offline experiences, intercept network requests and take appropriate action based on whether the network is available, and update assets residing on the server. They will also allow access to push notifications and background sync APIs.
Worker contexts
While Window is not directly available to workers, many of the same methods are defined in a shared mixin (WindowOrWorkerGlobalScope), and made available to workers through their own WorkerGlobalScope - derived contexts:
DedicatedWorkerGlobalScopefor dedicated workersSharedWorkerGlobalScopefor shared workersServiceWorkerGlobalScopefor service workers
Using Web Workers
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 fetch() API. 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).
Web Workers API
A worker is an object created using a constructor (e.g., 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 window. Thus, using the window shortcut to get the current global scope (instead of self) within a Worker will return an error.
The worker context is represented by a DedicatedWorkerGlobalScope object in the case of dedicated workers (standard workers that are utilized by a single script; shared workers use SharedWorkerGlobalScope). A dedicated worker is only accessible from the script that first spawned it, whereas shared workers can be accessed from multiple scripts.
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 window object. But you can use a large number of items available under window, including WebSockets, and data storage mechanisms like IndexedDB.
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 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 origin as the parent page.
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 Dedicated worker example: 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.
Spawning a dedicated worker
Creating a new worker is simple. All you need to do is call the Worker() constructor, specifying the URI of a script to execute in the worker thread:
const myWorker = new Worker("worker.js");Bundlers, including webpack, Vite, and Parcel, recommend passing URLs that are resolved relative to import.meta.url to the Worker() constructor.
For example:
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 postMessage() method and the onmessage event handler.
When you want to send a message to the worker, you post messages to it like this:
[first, second].forEach((input) => { input.onchange = () => { myWorker.postMessage([first.value, second.value]); console.log("Message posted to worker"); };});So here we have two <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:
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:
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.
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.
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 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 terminate method:
myWorker.terminate();The worker thread is killed immediately.