Web Workers provide a multithreading solution for asynchronous processing in the browser. They are a useful tool to avoid the single-threading blocking and inefficiencies that come as part of JavaScript by design.
In today’s web ecosystem, having a dominant programming or scripting environment that is single-threaded is a bottleneck.
Web Workers
Web workers are an asynchronous system, or protocol, for web pages to execute tasks in the background, independently from the main thread and website UI. It is an isolated environment that is insulated from the window
object, the document
object, direct internet access and is best suited for long-running or demanding computational tasks.
JavaScript is single-threaded. This means that by design, JavaScript engines — originally browsers — have one main thread of execution. To put it simply, process B cannot be executed until process A is finished. A web page’s UI is unresponsive to any other JavaScript processing while it is occupied with executing something — this is known as DOM blocking and it can be terribly inefficient.
Note: Apart from web workers there are other ways to achieve asynchronous processing in JavaScript, such as asynchronous Ajax calls, and event loop.
Web Workers & Multithreading
As Mozilla’s JavaScript reference website explains, web workers are a “means for web content to run scripts in background threads.”
We use them in the following way: we check for the availability of the Worker()
constructor in the browser, and if it is available, we instantiate a worker object, with the script URL as the argument. This script will be executed on a separate thread.
The script must be served from the same host or domain for security reasons, and that is also the reason that web workers won’t work if we open the file locally with a file://
scheme.
if (typeof(Worker) !== "undefined") {
worker = new Worker("worker.js");
}
Now we define this code in the worker.js
file:
i = 0;
while (i < 200000) {
postMessage("Web Worker Counter: " + i);
i++;
}
If you want to write high-quality JavaScript web worker files, check out our book, JavaScript: Best Practice.
The Separation of Threads
An important thing to note here is the separation of the window
and document
scope of execution in the main browser window thread, and the worker
scope.
In order to make use of the worker
thread, these two scopes need to be able to communicate. To achieve this, we use the postMessage()
function within the worker.js
file — to send messages to the main browser thread — and the worker.onmessage
listener in the main thread to listen to worker
messages.
We can also send messages from the main browser thread to the worker
thread or function. The only difference is that we reverse things, and call worker.postMessage()
on the main thread, and onmessage
on the worker thread. To quote Mozilla’s developer reference:
Notice that
onmessage
andpostMessage()
need to be hung off theWorker
object when used in the main script thread, but not when used in the worker. This is because, inside the worker, the worker is effectively the global scope.
We can use the terminate()
method in the same way, to end our worker’s execution.
With all this in mind, we come to this example:
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Web Workers Example</title>
<style type="text/css">
body {padding-top:28px;}
.output-cont {margin-left:12%; margin-top:28px;}
.output-cont h3 {width:200px; height:100%;}
.output-cont button {padding:4px 8px; font-size:1.1rem; font-family:sans-serif; }
</style>
</head>
<body>
<div class="output-cont"><button onclick="testWorker()">start worker</button><h3 id="workerOutput"></h3><button onclick="terminateWorker()">terminate worker</button></div>
<br/>
<div class="output-cont"><button onclick="testMainThread()">start blocking thread</button><h3 id="mainThreadOutput"></h3></div>
<br/>
<div class="output-cont"><button onclick="alert('browser responsive!')">test browser responsiveness</button></div>
<script>
var worker;
function testWorker() {
if (typeof(Worker) !== "undefined") {
if (typeof(worker) == "undefined") {
worker = new Worker("worker.js");
}
worker.onmessage = function(event) {
document.getElementById("workerOutput").innerHTML = event.data;
};
} else {
document.getElementById("workerOutput").innerHTML = "Web Workers are not supported in your browser";
}
}
function terminateWorker() {
worker.terminate();
worker = undefined;
}
function testMainThread() {
for (var i = 0; i < 200000; i++) {
document.getElementById("mainThreadOutput").innerHTML = "Main Thread Counter: " + i;
}
}
</script>
</body>
</html>
and worker.js:
i = 0;
while (i < 200000) {
postMessage("Web Worker Counter: " + i);
i++;
}
This gives us the opportunity to test out the effects of main-thread execution on page behavior and performance versus the web worker’s effects.
In this tutorial, we used http-server
to serve the files locally.
Now we can see that the worker thread does not block the interactivity of the main browser process, and looping through 200,000 numbers does not affect the main thread. The numbers in the #workerOutput
element are updated on every iteration.
The blocking thread, or main thread, when engaged in a loop, blocks all interactivity (we have set the number of iterations to 200,000 here, but it will be even more obvious if we increase it to 2,000,000).
One more thing that points us to a blocked main thread is that the worker process updates the page on every iteration, and the loop in the main thread (the one defined in index.html
) only updates the #mainThreadOutput
element on the last iteration.
This is because the browser is too consumed with counting (for
loop) to be able to redraw the DOM, so it does it only once its business with the for
loop is fully done (at the end of the loop).
Conclusion
In this article, we introduced web workers, a technology that helps the web industry keep up with more and more demanding web apps. This is done by providing a way for web apps to leverage multi-processor and multi-threaded devices by bestowing some multi-threaded superpowers to JavaScript.
Web workers turn the mobile and desktop browser environments into application platforms, providing them with a strict execution environment. This strictness may force us to provide for the copying of objects between multiple threads, and to plan our applications with these constraints in mind.
Do you have any tips regarding web workers, and the web as a programming platform? Let us know in the comments!