Web Workers
Learn the way to run JavaScript code in the background using Web Workers
- Introduction
- Browser support for Web Workers
- Create a Web Worker
- Communication with a Web Worker
- Web Worker Lifecycle
- Loading libraries in a Web Worker
- APIs available in Web Workers
Introduction
JavaScript is single threaded. Nothing can run in parallel at the same time.
This is great because we don’t need to worry about a whole set of issues that would happen with concurrent programming.
With this limitation, JavaScript code is forced to be efficient from the start, otherwise the user would have a bad experience. Expensive operations should be asynchronous to avoid blocking the thread.
As the needs of JavaScript applications grew, this started to become a problem in some scenarios.
Web Workers introduce the possibility of parallel execution inside the browser.
They have quite a few limitations:
- no access to the DOM: the Window object and the Document object
- they can communicate back with the main JavaScript program using messaging
- they need to be loaded from the same origin (domain, port and protocol)
- they don’t work if you serve the page using the file protocol (
file://
)
The global scope of a Web Worker, instead of Window which is in the main thread, is a WorkerGlobalScope
object.
Browser support for Web Workers
Pretty good!
You can check for Web Workers support using
if (typeof Worker !== 'undefined') {
}
Create a Web Worker
You create a Web Worker by initializing a Worker object, loading a JavaScript file from the same origin:
const worker = new Worker('worker.js')
Communication with a Web Worker
There are two main ways to communicate to a Web Worker:
- the postMessage API offered by the Web Worker object
- the Channel Messaging API
Using postMessage in the Web Worker object
You can send messages using postMessage
on the Worker
object.
Important: a message is transferred, not shared.
main.js
const worker = new Worker('worker.js')
worker.postMessage('hello')
worker.js
onmessage = (event) => {
console.log(event.data)
}
onerror = (event) => {
console.error(event.message)
}
Send back messages
A worker can send back messages to the function that created it. using its global postMessage()
function:
worker.js
onmessage = (event) => {
console.log(event.data)
postMessage('hey')
}
onerror = (event) => {
console.error(event.message)
}
main.js
const worker = new Worker('worker.js')
worker.postMessage('hello')
worker.onmessage = (event) => {
console.log(event.data)
}
Multiple event listeners
If you want to setup multiple listeners for the message
event, instead of using onmessage
create an event listener (applies to the error
event as well):
worker.js
addEventListener(
'message',
(event) => {
console.log(event.data)
postMessage('hey')
},
false,
)
addEventListener(
'message',
(event) => {
console.log(`I'm curious and I'm listening too`)
},
false,
)
addEventListener(
'error',
(event) => {
console.log(event.message)
},
false,
)
main.js
const worker = new Worker('worker.js')
worker.postMessage('hello')
worker.addEventListener(
'message',
(event) => {
console.log(event.data)
},
false,
)
Using the Channel Messaging API
Instead of using the built-in postMessage API offered by Web Workers, we can choose to use the more general-purpose Channel Messaging API to communicate to them.
main.js
const worker = new Worker('worker.js')
const messageChannel = new MessageChannel()
messageChannel.port1.addEventListener('message', (event) => {
console.log(event.data)
})
worker.postMessage(data, [messageChannel.port2])
worker.js
addEventListener('message', (event) => {
console.log(event.data)
})
A Web Worker can send messages back by posting a message to messageChannel.port2
, like this:
addEventListener('message', (event) => {
event.ports[0].postMessage(data)
})
Web Worker Lifecycle
Web Workers are launched and if they do not stay in listening mode for messages through worker.onmessage
or by adding an event listener, they will be shut down as soon as their code is run through completion.
A Web Worker can be stopped using its terminate()
method from the main thread, and inside the worker itself using the global method close()
:
main.js
const worker = new Worker('worker.js')
worker.postMessage('hello')
worker.terminate()
worker.js
worker.onmessage = (event) => {
console.log(event.data)
close()
}
worker.onerror = (event) => {
console.error(event.message)
}
Loading libraries in a Web Worker
Web Workers can use the importScripts()
global function defined in their global scope:
importScripts('../utils/file.js', './something.js')
APIs available in Web Workers
As said before, the DOM is not reachable by a Web Worker, so you cannot interact with the window
and document
objects. Also parent
is unavailable.
You can however use many other APIs, which include:
- the XHR API
- the Fetch API
- the BroadcastChannel API
- the FileReader API
- IndexedDB
- the Notifications API
- Promises
- Service Workers
- the Channel Messaging API
- the Cache API
- the Console API (
console.log()
and friends) - the JavaScript Timers (
setTimeout
,setInterval
…) - the CustomEvents API:
addEventListener()
andremoveEventListener()
- the current URL, which you can access through the
location
property in read mode - WebSockets
- WebGL
- SVG Animations
→ I wrote 17 books to help you become a better developer, download them all at $0 cost by joining my newsletter
→ JOIN MY CODING BOOTCAMP, an amazing cohort course that will be a huge step up in your coding career - covering React, Next.js - next edition February 2025