🔥 Get 650+ Tech Books and Courses for $3/m for 3 months

JavaScript Web Workers: A Beginner’s Guide

    Tonino Jankov
    Share

    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 and postMessage() need to be hung off the Worker 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.

    JavaScript web workers testing

    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!