A Simple Introduction to Web Workers in JavaScript

How your JavaScript code can do more than one task at a time

Matthew MacDonald
Jul 29 · 9 min read
Image by Serena Wong from Pixabay

Goal: Learn to use the Web Workers API to do work in the background

Prerequisites: Basic JavaScript knowledge (events, DOM)


Way back when JavaScript was first created, no one worried too much about performance. JavaScript was designed to be a straightforward language for running small bits of script in a web page. It was a frill — a simplified scripting language for amateur programmers. It certainly wasn’t meant to run anyone’s business.

Fast forward nearly 25 years, and JavaScript has taken over the web. It has professional responsibilities and — finally — professional features. One example is web workers.

Web workers are designed to let you run big jobs without freezing up the page. For example, imagine you want to do some complex calculations when someone clicks a button. If you start doing the job right away, you’ll tie everything up. The person using the page won’t be able to scroll or click anything. They might even get the dreaded “this page is unresponsive” error message.

In this situation, you need a way to do work quietly in the background, while the person using your page carriers on in the foreground, undisturbed. Technically, we say the background work takes place on another thread.

(Understanding how threads work is a bit more than we’ll talk about today. But the basic idea is that modern operating systems run hundreds of threads at once, and spend most of their time quickly switching from one thread to another. In fact, they switch so quickly and seamlessly that it seems like everything is happening at once.)

Web workers let you do you do any time-consuming job in the background. The process is simple:

  1. You create a web worker.
  2. You tell the web worker what to do. (For example, crunch these numbers!)
  3. You start the web worker.
  4. When the web worker is done, it tells you, and your code goes from there. (For example, show the results on your page.)

Let’s dive in!

Making a time-consuming task

Before you can see the benefits of web workers, you need to write some really slow code. There’s no point in using web workers for short tasks, because that won’t freeze your page.

Consider, for example, the prime number searcher shown below. (It’s in a CodePen, so you can try it out, check out the code, and even make changes.)

This example hunts for prime numbers that fall in a given range. You pick the range using the two text boxes on the page. Pick a relatively narrow range (say, from 1 to 100,000), and the task completes in seconds, without inconveniencing anyone. But launch a broader search (say, from 1 to 1,000,000) and your page could become unresponsive for seconds or minutes. You won’t be able to click, scroll, or interact with anything.

Clearly, this page can be improved with web workers. But before you get to that, you need to take a closer look at the current JavaScript code. Right now, when you click the Search button it triggers the doSearch() function shown here:

This code is thoroughly unremarkable. It does what nearly every bit of basic JavaScript does — first it grabs some information from the page, then it performs a calculation, then it insert the results back into a <div> so you can see them.

The code that actually does the prime number searching is in another function, named findPrimes(). You don’t need to understand how a prime number search works to use this example. We’re just using it because it’s a task that’s simple to code, but computationally difficult, which means it can take some serious number-crunching time. If you’re curious to see the math that makes this page work, edit the CodePen and take a look at findPrimes().

Doing work in the background

The web worker feature revolves around an object called Worker. When you want to run something in the background, you create a new Worker, give it some code, and send it some data.

Here’s an example that creates a new web worker that runs the code in the file named PrimeWorker.js:

var worker = new Worker("PrimeWorker.js");

The code that a worker runs is always stored in a separate JavaScript file. This design discourages newbie programmers from writing web worker code that attempts to use global variables or directly access elements on the page. Neither of these operations is possible. Why? Because bad things can happen if more than one thread attempts to manipulate the same data at the same time. That means there’s no way for the code in PrimeWorker.js to write prime numbers into a <div> element. Instead, your worker code needs to send its data back to JavaScript code on the page, and let the web page code display the results.

Web pages and web workers communicate by exchanging messages. To send data to a worker, you call the worker’s postMessage() method:

worker.postMessage(myData);

The worker then receives an onMessage event that provides a copy of the data. This is when it starts working.

Similarly, when your worker needs to talk back to the web page, it calls its own postMessage() method, along with some data, and the web page receives an onMessage event.

There’s one more wrinkle to consider before you dive in. The postMessage() function allows only a single value. This fact is a stumbling block for the prime number cruncher, because it needs two pieces of data (the two numbers in the range). The solution is to package these two details into an object literal. This code shows one example, which gives the object two properties (the first named from, and the second named to), and assigns values to both of them:

worker.postMessage(
{ from: 1,
to: 20000 }
);

With these details in mind, you can revise the doSearch() function you saw earlier. Instead of performing the prime number search itself, the doSearch() function creates a worker and gets it to do the real job:

Now, the code in the PrimeWorker.js file springs into action. It receives the onMessage event, performs the search, and then posts a new message back to the page, with the prime list:

When the worker calls postMessage(), it fires the onMessage event, which triggers this function in the web page:

Overall, the structure of the code has changed a bit, but the logic is mostly the same. The result, however, is dramatically different. Now, when a long prime number search is under way, the page remains responsive. You can scroll down, type in the text boxes, and select numbers in the list from the previous search. Try it out in the CodePen here:

Handling worker errors

The postMessage() method is the key to communicating with web workers. However, there’s one more way that a web worker can notify your web page — with the onerror event that signals an error:

worker.onerror = workerError;

Now, if some dodgy script or invalid data causes an error in your background code, the error details are packaged up and sent back to the page. Here’s some web page code that simply displays the text of the error message:

function workerError(error) {
statusDisplay.innerHTML = error.message;
}

Along with the message property, the error object also includes a lineno and filename property, which report the line number and file name where the error occurred.

Canceling a background task

Now that you’ve built a basic web worker example, you can add some improvements. First up is cancellation support, which lets your page shut down a worker while it’s still working.

There are two ways to stop a worker. First, a worker can stop itself by calling close(). More commonly, the page that created the worker will shut it down by calling the worker’s terminate() method. For example, here’s the code you can use to power a straightforward Cancel button:

function cancelSearch() {
worker.terminate();
statusDisplay.innerHTML = "";
searchButton.disabled = false;
}

Click this button to stop the current search and re-enable the search button. Just remember that once a worker is stopped in this way, you can’t send any more messages, and it can’t be used to do any more operations. To perform a new search, you need to create a new worker object. (The CodePen example does this already. Try it out!)

Passing more complex messages

The last trick you’ll learn to do with a web worker is return progress information. This is a more advanced trick, because you need to make the web worker keep talking to the web page. However, it’s a good skill to master, because you’ll use this sort of communication in more advanced web worker examples.

As you already learned, web workers have just one way to talk to the web page—with the postMessage() method. So to create this example, the web worker needs to send two types of messages: progress notifications (while the work is under way) and the prime number list (when the work is finished). The trick is making the difference between these two messages clear, so the onMessage event handler in the page can tell the difference between the two types of messages.

The best approach is to add a bit of extra information to the message. For example, when the web worker sends progress information, it can slap the text label “Progress” on its message. And when the web worker sends the prime number list, it can add the label “PrimeList.”

To bundle all the information you want together in one message, you need to create an object literal. This is the same technique the web page used to send the number range data to the web worker. The extra piece of information is the text that describes the type of message, which is placed in a property called messageType in this example. The actual data goes in a second property, named data.

Here’s how you would rewrite the web worker code to add a message type to the prime number list:

onmessage = function(event) {
// Perform the prime number search.
var primes = findPrimes(event.data.from, event.data.to);
// Send back the results.
postMessage(
{messageType: "PrimeList", data: primes}
);
};

The code in the findPrimes() function also uses the postMessage() method to send a message back to the web page. It uses the same two properties — messageType and data. But now the messageType indicates that the message is a progress notification, and data holds the progress percentage:

function findPrimes(fromNumber, toNumber) {
...
// Calculate the progress percentage.
var progress = Math.round(i/list.length*100);
// Only send a progress update if the progress has changed
// at least 1%.
if (progress != previousProgress) {
postMessage(
{messageType: "Progress", data: progress}
);
previousProgress = progress;
}
...
}

When the page receives a message, it needs to start by checking the messageType property to determine what sort of message it has just received. If it’s a prime list, then the results are shown in the page. If it’s a progress notification, then the progress text is updated:

function receivedWorkerMessage(event) {
var message = event.data;
if (message.messageType == "PrimeList") {
var primes = message.data;
// Display the prime list. This code is the same as before.
...
}
else if (message.messageType == "Progress") {
// Report the current progress.
statusDisplay.innerHTML = message.data + "% done …";
}
}

This kind of message passing can get complicated. But the extra work is worth it, because it keeps your code safe. There’s no way your web page thread and your web worker thread can collide — something that’s not guaranteed when you use threads in other programming languages.

The progress feature is already in the previous CodePen example. To see all the code together in its proper place, edit the CodePen and take a look!

Final words

Right now, the prime number search users web workers in the most straightforward way possible — to perform one well-defined task. Your pages don’t need to be this simple. Here are a few examples of how you can get more advanced:

  • Create multiple web workers. Your page doesn’t need to stick to one worker. For example, imagine you want to let a visitor launch several prime number searches at a time. You could create a new web worker for each search, and keep track of all your workers in an array.
  • Create web workers inside a web worker. A web worker can start its own web workers, send them messages, and receive their messages back. This technique is useful for complex computational tasks that require recursion, like calculating the Fibonacci sequence.
  • Do periodic tasks with a web worker. Web workers can use the setTimeout() and setInterval() functions, just like ordinary web pages. For example, you might create a web worker that checks a website for new data every minute.

Web workers are one of the most significant ways that JavaScript has closed the gap between the browser world and the world of desktop applications. They’ve only been around for a few years, but they’re already indispensable for serious JavaScript work.

Young Coder

Code, science, and tech for kids and complete beginners. Silly hype not included.

Matthew MacDonald

Written by

Tech writer, teacher, coder. Author of dozens of heavy books. Now out to inspire kids in coding and science. http://prosetech.com

Young Coder

Code, science, and tech for kids and complete beginners. Silly hype not included.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade