JavaScript in the browser is a single-threaded environment, meaning it can run only one script at the same time. If you do heavy calculations and DOM manipulation at the same time, the browser will likely throw you a message about the unresponsive page and offer you to kill the script that takes a long time to finish.
To make the user experience better and escape blocking, we can use web workers. It is a system that spawns required worker to execute script in the background threads without interfering with the user interface.
To spawn a new worker, we need to call a constructor Worker()
and pass URI to the script file, where new worker thread will be created and code executed. A good practice to check if a browser supports workers and handle error:
if (window.Worker) {
const myWorker = new Worker('worker.js');
} else {
console.log('Browser does not support web workers');
}
Worker Scope
A worker will run in a different context from the current window
and trying to access it without keyword self
will throw an error. Using self
will reference the window
context.
In a worker, you can run mostly any JavaScript code including navigotor
object, WebSockets, data storage, XMLHttpRequest
, extensive set of window
methods or load other scripts. But you cannot directly manipulate the DOM.
Communication With a Dedicated Worker
Communication between the main page and worker is happening through method postMessage
and event handler onmessage
.
For example, to send data to a worker, we call a method:
// main js file
const myWorker = new Worker('worker.js');
myWorker.postMessage('Giggity');
Passed data between the main page and workers is copied, not shared, and can be of any type or Javascript object
To handle received data in the worker, we need to declare an event handler. The worker has access to event handler and method, and can be set up directly calling them:
// worker.js file
onmessage = (event) => {
const { data } = event;
const transformedData = doSomeHeavyWork(data);
postMessage(transformedData);
}
Event handler onmessage
runs a function every time it receives a message, with access to Event
object, which holds the sent message in the data
attribute.
To receive the message from the worker, we also declare onmessage
event handler on the main page:
// main js file
const myWorker = new Worker('worker.js');
myWorker.postMessage('Giggity');
myWorker.onmessage((event) => {
const { data } = event;
doSomethingWihtIt(data);
})
And in any case you need to terminate the worker from the main thread, it can be done immediately calling terminate
method:
myWorker.terminate()
Conclusion
That was a basic intro to the Web Workers. You can start using them in your application to enhance user experience.
Any intense computation can be set to run on workers, like processing large data sets, prefetching and cashing, multimedia analyze or real-time text formatting.
Top comments (2)
Nice post, but I don't think I can see the advantages of using worker threads in the frontend where most of the interaction is single-user and quasi "linear".
Can you think of any scenario where this would be useful?
Maybe an event-logger that does heavy processing and is just a fire-and-forget...
It's a good article for getting started. Thanks :)