June 30, 2016

Web Workers And The Threads That Bind Us

By Jscrambler | 8 min read

web_workers_img

We tend to take for granted our day to day web browsing experience. Our daily life and work might have us using any number of single page web apps that on the surface seem like a web page with some neat UI icing. In reality these apps hide underlying complexity that make them quick and responsive. App responsiveness and performance might be non-issue for the latest Macbook Pro, but our concern lies in running it on a decently powered phone. Developers understand the value of a responsive app and strive to avoid any skipped frames that result in poor performance and even poorer user experience.

Web Workers

Web Workers are especially suited for doing expensive computations and avoid blocking the UI rendering thread entirely. They provide us with some concurrency within Javascript by running otherwise costly tasks in the background. Javascript is for the most part, a single-threaded environment meaning that only one method can be executed at any given time. Let’s say for example, the task of rendering a UI in a web app is considered a process utilizing exactly one CPU thread. When you render a button or event, the thread starts the task of painting that button on the screen and won’t run other tasks during this event. If rendering the page takes too long, the content remains unresponsive to user interaction. The Chrome V8 JavaScript engine is undoubtedly powerful but even it can’t handle running out of memory:

web-kit-user-select

Basic Example

Here’s a simple example of how you’d utilize a web worker. First let’s initiate a web worker with Worker() as in the following main.html:

<h1>Web Workers example</h1>
<div class="controls" tabindex="0"></div>
<form>
    <div>
        <label for="number1">Multiply number 1: </label>
        <input id="number1" type="text" value="0" />
    </div>
    <div>
        <label for="number2">Multiply number 2: </label>
        <input id="number2" type="text" value="0" />
    </div>
</form>
<p class="result">Result: 0</p>
<script type="text/javascript">
    // <![CDATA[
    var first = document.querySelector('#number1');
    var second = document.querySelector('#number2');
    var result = document.querySelector('.result');
    if (window.Worker) { // Check if Browser supports the Worker api. 
      var myWorker = new Worker("worker.js");
//creates a new web worker with the provided file 
      first.onchange = function() {  
         myWorker.postMessage([first.value,second.value]);   
         console.log('Message posted to worker'); 
       } 
//our event listener receives messages from the worker second. 
      onchange = function() {
         myWorker.postMessage([first.value,second.value]);   
         console.log('Message posted to worker'); 
       } 
      myWorker.onmessage = function(e) {   
         result.textContent = e.data;   
         console.log('Message received from worker'); 
       } 
    };
   // ]]>
</script>

Our script logic runs immediately as soon as it’s passed to the worker object. Our onchange event looks for any events that fire. The worker thread receives the message, logs it and posts the appropriate string using myworker.postMessage() method and notifies the UI thread with postMessage(). As you can see web workers are easy to get started but their true potential is in running asynchronously.

As soon as our Main script is called, it begins to run in the background. Our script logic runs immediately as soon as it’s passed to the worker object. Our event listener looks for any events that fire. The worker thread receives the message, logs it and posts the appropriate string using worker.postMessage() method and notifies the UI thread with self.postMessage(). As you can see web workers are easy to get started with but their true potential is running asynchronously.

Try Jscrambler for Free!

Blocking vs. Non Blocking

Because it costs much more to transmit a byte than it does to compute it, we can harness the power of web workers and offload any performance load to client CPUs. Rather than burdening servers and waiting to send clients data to be rendered, workers lets us do some of the computing locally in a non thread blocking manner.

Web Workers can asynchronously reference an external script, download that script and run it as a background process so as to not interfere with the main UI thread. Workers use postMessage to communicate through messages posted to and from each worker. The Web workers API grants us the ability to run scripts in the background with each Web Worker assigned to it’s own thread. As workers run in the background, application logic will not block the render thread.
One web worker uses one thread, enabling you to run application logic across various windows with improved performance. We’ve covered dedicated workers but there’s also a shared worker variation that allows multiple workers access to the same file.

Here are a some other important things to know when using Web Workers:
…they have no control over the DOM( or window object) so they’re unable to update the UI.
…they share no memory with your main process.
…they have access to navigator metadata like useragent and other information.
…they can use Timers (setTimeout, setInterval)
…they also get access to XMLHttpRequest and WebSockets

with-web-workers

Without Workers

You might still be wondering about practical applications for web workers. Next we’ll cover an example with and without workers to visualize the benefits. In the below example without web workers we are generating a series of Fibonacci numbers
up to a number of our choice. Once our program runs, it fills up our results array and generates the series as an unordered list. The major key here is that when the thread begins computing a large Fibonacci number, the loading gif will lock up because UI is blocked by the thread calculating the series.

Our example without workers, worker.html:

	<link rel="stylesheet" type="text/css" />

<style type="text/css">
    <!-- ol {
        background-color: #ccc;
        width: 20%;
    }
    ol li {
        background-color: #fff;
        padding-left: 5px;
        margin: 5px;
    }
    -->
</style>
<div id="container">
    <h1>Fibonacci Web Workers</h1>
    <input id="seriesLength" type="numeric" value="40" />
    <input id="generateButton" type="button" value="Generate" />
    <img alt="" src="http://i.imgur.com/vp8NUmC.gif" />
    <ol id="log"></ol>
</div>
<script type="text/javascript">
    // <![CDATA[
    var results = []; //create the results array
    var log;
    //generates a log list with my Fibonacci series
    $(function() {
        log = $("#log");
        $("#generateButton").click(function() {
            log.html("");
            var seriesLength = parseInt($("#seriesLength").val());
            generateFib(seriesLength);
            //recursviely generate my series of Fibonacci numbers
            $.each(results, function() {
                logMsg(this);
                //iterate my series and log them as an unordered list
            });
        });
    });

    function calculateNextFibVal(n) {
        var s = 0;
        var returnValue;
        if (n == 0) {
            return (s);
        }
        if (n == 1) {
            s += 1;
            return (s);
        } else {
            return (calculateNextFibVal(n - 1) + calculateNextFibVal(n - 2));
        }
    }

    function generateFib(n) {
        results.length = 0;
        for (var i = 0; i < n - 1; i++) {
            results.push(calculateNextFibVal(i));
        }
    }

    function logMsg(msg) {
            log.append("

                    < li > " + msg + " < /li > ")

                }
                // ]]>
</script>

Try Jscrambler For Free

With Workers

The structure for our example with workers is generally the same. The only difference is that we move the logic to it’s own worker script.

Worker.html:

<link rel="stylesheet" type="text/css" />

<style type="text/css">
    <!-- ol {
        background-color: #ccc;
        width: 20%;
    }
    ol li {
        background-color: #fff;
        padding-left: 5px;
        margin: 5px;
    }
    -->
</style>
<div id="container">
    <h1>Fibonacci Web Workers</h1>
    <input id="seriesLength" type="numeric" value="40" />
    <input id="generateButton" type="button" value="Generate" />
    <img id="loadImg" alt="" src="http://i.imgur.com/vp8NUmC.gif" />
    <ol id="log"></ol>
</div>
<script type="text/javascript">
    // <![CDATA[
    var log;
    var loadImg;
    var worker;
    //generates a log list with my Fibonacci series
    $(function() {
                log = $("#log");
                loadImg = $("#loadImg");
                loadImg.hide();

                $("#generateButton").click(function() {

                    var seriesLength = parseInt($("#seriesLength").val());

                    log.html("");
                    loadImg.show();

                    worker = new Worker("worker.js");
                    worker.onmessage = messageHandler;
                    worker.postMessage(seriesLength);
                });

                function messageHandler(e) {
                    var results = e.data;
                    $.each(results, function() {
                        logMsg(this);
                    });
                }

                function logMsg(msg) {
                    log.append("

                        < li > " + msg + " < /li>

                        ")}
                    });
                // ]]>
</script>

We are creating a new instance of worker with the worker when we initialize the worker() variable. We then process the onMessage() message handler and post the message to the worker. The worker gets its commands by posting messages to the worker and then back up to the window. We get the values of my array from the worker while it loops through the results and creates an ordered list. The difference is that we’re not accessing from a local array the values that are passed in from the worker.

Worker.js:

var results = [];

function messageHandler(e) {
    if (e.data > 0) {
        generateFib(e.data);
    }
}

function calculateNextFibVal(n) {
    var s = 0;
    var returnValue;

    if (n == 0) {
        return (s);
    }

    if (n == 1) {
        s += 1;
        return (s);
    } else {
        return (calculateNextFibVal(n - 1) + calculateNextFibVal(n - 2));
    }
}

function generateFib(n) {
    results.length = 0;
    for (var i = 0; i < n - 1; i++) {
        results.push(calculateNextFibVal(i));
    }
    postMessage(results)
}
addEventListener("message", messageHandler, true);

The worker is in a separate javascript file that is assigned to the worker thread. When we call
postMessage() our message event will fire and show up in the event arguments for our results data. Compared to our version that doesn’t use workers, this worker assisted version will calculate a larger series without blocking the UI thread all while allowing me to interact with the UI. Try generating a series of 40 or above to see a notable difference in performance. You can continue to highlight items, press the button or even generate more numbers while it calculates a new series and we won’t see any unresponsive script errors.

It can be easy to take responsive web app experiences for granted.

fibonacci-web-workers

The modern web makes it so that we can enjoy memory intensive apps and games requiring heavy duty computational resources, typically from mobile device. Tasks like parsing large JSON data sets, visualizing analytics, or sound and image processing can slow down an already overloaded client, negatively affecting user experience. Unless of course we can leverage our resources effectively with tools like Web Workers.

Finally, don't forget to pay special attention if you're developing commercial JavaScript apps that contain sensitive logic. You can protect them against code theft, tampering, and reverse engineering by starting your free Jscrambler trial.

Author
JscramblerThe leader in client-side Web security. With Jscrambler, JavaScript applications become self-defensive and capable of detecting and blocking client-side attacks like Magecart.
View All Posts

Subscribe to our weekly newsletter

Learn more about new security threats and technologies.

I agree to receive these emails and accept the Privacy Policy.