Content:
JavaScript can be used to create amazing effects on your website. It does come with one drawback, though.
JavaScript scripts will run as part of the main browser thread, along with page rendering. While this is fine for small, simple script, anything complex can dramatically impact the page performance.
This affects not only the script itself, but simple tasks such as scrolling the page, or interacting with the browser UI. Clearly, this not desirable.
Fortunately, there is a solution – web workers. Let’s take a look at how to use them.
What is a Web Worker?
In simple terms, a web worker is a JS runner that is contained within its own thread. This splits the code running in the web worker from the main thread, splitting general page rendering from additional code.
This is especially useful when running computationally heavy code – such as drawing to a canvas element.
There are a few caveats, though.
As the worker script is running entirely separately to the main page thread, it has no access to any of the data on the page. Any elements you want to access in the worker script must be explicitly passed in through the use of the internal message queue, which we’ll look at later.
In addition, any window
functions are similarly unavailable.
While running multiple workers is possible, each will be completely oblivious to the other running workers. For complex sites, planning will be required to ensure coordination between workers.
Creating a Web Worker
Creating a basic web worker is simple. You’ll first need a web page, preferably with enough content to require scrolling.
Next, you’ll need to create a separate JS file, containing the code to run in the worker. I’ve called mine script.js
.
function increment(i=0) {
while (i<10000000) {
i++;
}
console.log("Cycle complete");
increment();
}
This doesn’t do anything special. We set i
to 0, increment it until it reaches 10000000, log a console message, then start again. You’d never have this kind of code in a proper application.
What it does do is put a large amount of load on the system, which is the best way to demonstrate the power of the web worker.
Next, create the worker itself in the main web page. Using script tags in the file <head>
will suffice.
const webWorker = new Worker('script.js');
Yep, it really is that simple. We now have a web worker, that will run our script in a separate thread, though it’s a good idea to wrap this in code that will check for browser support.
if (window.worker) {
const webWorker = new Worker('script.js');
} else {
console.log("Web worker API not supported");
}
Open the page in your browser, and check the console. You should see either a stream of numbers being printed. If your browser doesn’t support the API, take a look at caniuse.com to find a browser that does.
You should find the page scrolls freely, and overall performance is good.
The real test, is to run the same code without a worker using the <script>
tag.
<script src="script.js"></script>
Make sure everything you have open in your browser is saved, as it’s not going to like what you’re trying to run.
Worker Messaging
As mentioned previously, a caveat to using web workers is the loss of access to certain JS objects, such as window
, or events, such as onScroll
.
You can overcome this by using the worker messaging feature, which allows information to be passed between the main and worker processes.
webWorker.postMessage(value);
console.log("{value} sent to worker");
In the main process, we can use the worker’s postMessage()
function to pass data across. In the example above, the variable value
is passed to the worker.
In the worker script, an event handler needs to be added, to react to the arrival of the message.
onmessage = function(e) {
const result = "Data received from the main thread: ";
console.log(result + e.data);
}
The value sent across is stored under the data
attribute. This script will print out the value passed across from the main thread.
You can send a message back to the main thread in a similar manner, only this time, the name of the worker is omitted.
onmessage = function(e) {
const result = "Data received from the main thread: ";
console.log(result + e.data);
const return = "Here's what you sent to me: ";
postMessage(return);
}
You can send whatever data you like between the two processes.
An important point to note is that data is only transmitted between processes as instructed. Updating a variable within a worker will not change the variable value in the main thread, until a message is used to pass the value back. This makes the process inherently thread-safe, as the two processes will modify their own copies of the variable.
Responding to Multiple Messages
It’s likely that at some point, you’ll want your worker to be able to respond to different messages.
In your main thread, you can set up as many message handlers as you like – just add the workerName.onMessage()
function where required.
In the worker, there’s only one event handler for messages, so you’ll need to differentiate between them yourself. An easy way to do this is to pass through identifying data.
onmessage = function(e) {
if (e.data.limit) {
limit = e.data.limit;
console.log("Limit changed to {limit}");
}
if (e.data.multiplier) {
multiplier = e.data.multiplier;
console.log("Multiplier changed to {multiplier}");
}
}
In our expanded worker onmessage
listener, we’re now checking for two specific data values – limit
, and multiplier
. We could use limit
to change the maximum value our code will reach before looping, and multiplier
to control the increment speed.
To aid in this, the data sent through to the worker is done in JSON form.
webWorker.postMessage({limit: 1000});
Removing a Web Worker
Once your worker has done its job, it’s a good idea to terminate the process. Of course, this won’t always be applicable – some scripts will need to stay active at all times. But where possible, it will free up the resources used by the worker.
webWorker.terminate();
Simply call the terminate()
function on the worker you want to end.
Conclusion
This has been a very simple introduction to web workers, covering the basics. This should be enough for you to shift simple scripts to web workers.
Check out our other JS tutorials to learn more about web workers, and look at more complex scenarios where they can make a huge difference your code.