Have you ever needed to run code in the browser that took so long to run your application became unresponsive for a while? With HTML5 web workers, you never need to experience that again.
Web workers allow you to separate long-running code and run it independently of other code running on the page. This keeps your UI responsive, even during complex operations.
What Are Web Workers?
Traditionally, JavaScript is a single-threaded language. That means nothing else can run while one piece of code is running. For example, if you have code trying to animate a DOM element, code trying to change a variable has to wait for the animation to end before it can run.
Web workers are JavaScript files that execute in a separate thread with no direct access to the DOM.
One way to think of web workers is that they’re pieces of code that take a lot of time to run, so you give them to the browser to execute in the background. Since that code is now running in the background, it doesn’t affect the JavaScript responsible for your web page.
As a side effect, it can no longer directly interact with the rest of your code, so web workers have no access to the DOM. However, many other browser APIs are still available, including the WebSocket and Fetch APIs.
Web workers aren’t entirely isolated from the main thread, though. When a worker needs to communicate with the main thread, it can send a message and the main thread can send its own message in response.
Why Web Workers?
Before web workers, the only way to run JavaScript that required a lot of time in the browser was either:
- Accept that the page would be unresponsive for a while.
- Break that code into asynchronous chunks.
Since an unresponsive page is usually a bad user experience, you might opt for the asynchronous option. Writing code this way means dividing it into smaller pieces the browser can run while it’s not handling the user interface. The pieces need to be small enough that if the UI needs updating, the browser can finish executing the current piece and attend to the user interface.
Web workers were added to HTML5 to offer a better solution to this problem. Instead of forcing you to get creative with asynchronous code, they let you cleanly separate a function to run in its own isolated thread.
This made it easier for developers to write such code and improved the user’s experience as well.
Use Cases for Web Workers
Any application which requires a lot of computation on the client side could benefit from web workers.
Say, for example, your application wants to generate a usage report, and it stores all the data on the client out of privacy concerns.
To generate that report, your web application has to retrieve the data, run calculations on it, organize the results, and present them to the user.
If you tried to do that in the main thread, the user would be completely unable to use the application while the application processed the data. Instead, you can move some or all of that code into a web worker. This allows the user to continue using the application while the calculations are being performed.
How to Use Web Workers in JavaScript
The Web Worker API defines how to use web workers. Using this API involves creating a Worker object with the Worker constructor like this:
let newWorker = Worker('worker.js');
The Worker constructor accepts the name of a JavaScript file as its parameter and runs the file in a new thread. It returns a Worker object to allow the main thread to interact with the worker thread.
Workers interact with the main thread by sending messages back and forth. You use the postMessage function to send events between the worker and the main thread. Use the onmessage event listener to listen for messages from the other party.
Here’s a code example. First, a main thread might look like this:
let worker = new Worker('worker.js')
worker.postMessage('Hey!')worker.onmessage = function(e) {
console.log('Worker thread says', e.data)
}
This main thread creates a worker object from worker.js, then sends a message to it with worker.postMessage. It then defines an event listener, similar in concept to a DOM event listener. An event will fire each time the worker sends a message back to the main thread, and the handler logs the worker’s message to the console.
The code inside the worker (worker.js) has one job:
onmessage = function(e) {
let message = e.data;
console.log('Main thread said', message);
postMessage('Hi!')
}
It listens for any messages sent from the main thread, logs the message to the console, and sends a return message back to the main thread.
The messages in this example have all been strings, but that’s not a requirement: you can send almost any type of data as a message.
The kind of workers you’ve seen so far are called dedicated workers. You can only access them from the file you created them in (they’re dedicated to it). Shared workers are the opposite: they can receive messages from, and send messages to, multiple files. Shared workers are conceptually the same as dedicated workers, but you have to use them a little differently.
Let’s look at an example. Instead of using the Worker constructor, each file that wants to use a shared worker has to create a worker object using SharedWorker():
let sharedWorker = new SharedWorker('worker.js')
The differences don’t stop there though. For a file to send or receive a message from a shared worker, it has to do so by accessing a port object, instead of doing so directly. Here’s what that looks like:
sharedWorker.port.postMessage('Hi there!')sharedWorker.port.onMessage = function(e) {
console.log('The shared worker sent', e.data);
}
You have to use the port object inside the worker too:
onconnect = function(e) {
const port = e.ports[0];port.onmessage = function(e) {
console.log('Message recieved', e.data)
port.postMessage('Hello!');
}
}
The onconnect listener fires every time a connection to a port happens (when an onmessage event listener is set up in the main thread).
When that happens, the code gets the port that was just connected to from the connect event and stores it in a variable. Next, the code registers the onmessage listener on the port object. The code then logs the message to the console, and uses the port to send a message back to the main thread.
Web Workers Improve User Experience
Web workers are JavaScript threads that allow you to run complex and long-running pieces of code in the background. This code will then avoid blocking the user interface. Using web workers makes writing this kind of code much easier, and improves the experience for the user of the application. You can create web workers, and interact with them, using the web worker API.