javascript for web development

Develop Multi-thread JavaScript applications with Web Workers

Imagine that you have a project, site, or a webpage that run a complex, long-running JavaScript task. It causes the page to load very slowly as visitors should wait until the JS code is fully processed. In this tutorial, we show you how to use Web Workers to run a complex, long-running JavaScript task without locking up the UI in the browser. You need to run the JavaScript task in a separate thread, and the way to do this is with the Worker API, otherwise known as Web Workers.

Web Worker Overview

Web Workers are a simple means for web content to run scripts in background threads. The worker thread can perform tasks without interfering with the user interface. In addition, they can perform I/O using XMLHttpRequest (although the responseXML and channel attributes are always null). Once created, a worker can send messages to the JavaScript code that created it by posting messages to an event handler specified by that code (and vice versa). Here are few Web Worker concepts that are good to know:

  • Dedicated Worker
    • A dedicated worker is only accessible by the script that called it.
  • Shared Worker
    • A shared worker is accessible by multiple scripts — even if they are being accessed by different windows, iframes or even workers.
  • Transferring data to and from Workers
    • Data passed between the main page and workers is copied, not shared. Objects are serialized as they’re handed to the worker, and subsequently, de-serialized on the other end. The page and worker do not share the same instance, so the end result is that a duplicate is created on each end. Most browsers implement this feature as structured cloning.
  • Web Worker API
    • A worker is an object created using a constructor (e.g. Worker()) that runs a named JavaScript file — this file contains the code that will run in the worker thread; workers run in another global context that is different from the current window. Thus, using the windowshortcut to get the current global scope (instead of self) within a Worker will return an error.
    • The worker context is represented by a DedicatedWorkerGlobalScope object in the case of dedicated workers (standard workers that are utilized by a single script; shared workers use SharedWorkerGlobalScope). A dedicated worker is only accessible from the script that first spawned it, whereas shared workers can be accessed from multiple scripts.

Step-by-step Implementation

Web Workers create a special environment for JavaScript code to run in that occurs in a separate thread from the main UI of your page. This means that your page’s UI will not be locked up if you have particularly long-running JavaScript code.
To test if the browser supports Web Workers, use the following feature-detect for the
Worker API:
var webworkers_support = !!window.Worker;

Now let’s build a simple Web Workers demo. We’ll initialize a large two-dimensional array with random numbers, a task that may take long enough to cause a noticeable UI delay. Two nested for loops do the trick:

var data = [];
for (var i=0; i<1500; i++) { data[i] = [];
for (var j=0; j<1500; j++) { data[i][j] = Math.random();
}
}

There’s nothing particularly exciting going on here. Such an array, with 2.25 million (1500 × 1500) operations to initialize it, may very well lock up the UI for anywhere from 2 to 30 seconds, depending on browser and device capability.
A more graceful way to handle this, without locking the UI, is to put such an operation into a separate thread—a Web Worker—and simply wait to be notified of it finishing before continuing.
To do this, put the above code into a separate file (called, for instance, init_array.js) and wrap the code in an onmessage event handler:

self.onmessage = function(evt) {
var data = [];
for (var i=0; i<1500; i++) { data[i] = [];
for (var j=0; j<1500; j++) { data[i][j] = Math.random();
}
}

self.postMessage(data);
data = null; // unassign our copy of the data now, to free up memory
};

This is the code for the Web Worker. The code first tells the worker to listen for the message event, which lets the worker know when to start. Once started, the worker performs the long-running computation. Finally, the worker sends back a message (the data array in our example), using postMessage(…), to the main page. Workers can also be started by other workers, and the communication works exactly the same.

In the main page of our UI, we create the worker, pointing it at the appropriate file.   Then we set up a listener for the message event, to receive the message (the initialized array) from the worker when it finishes its job. Finally, we start the worker by sending it an empty message using postMessage():

var worker = new Worker(“init_array.js”);

worker.onmessage = function(evt) {
alert(“Data array initialization finished!”); var data = evt.data;
};

worker.postMessage(); // tell our worker to start its task

Summary

Web Workers are very useful for offloading complex or long-running tasks to another thread, something that JavaScript itself cannot do.
If Web Workers are not supported in a particular browser, you need to just run your code in the main JavaScript thread, and deal with the delays it may cause. In some circumstances, you can break up your long-running code into smaller chunks and run one chunk at a time, pausing briefly in between to let the UI update before resuming. For example:

function doNextChunk() { var done_yet = false;
for (var i=0; i<500; i++) { // do 500 iterations at a time
// do something
// when done, set done_yet = true
}
if (!done_yet) setTimeout(doNextChunk,0); else alert(“All done finally!”);
}

doNextChunk();

Using a setTimeout(…,0) pattern, we do 500 iterations of a long-running loop, pause for a brief moment (just long enough for the UI to update), then resume and do another 500 iterations, and so on. This technique has better performance than letting a long-running piece of code tie up the UI indefinitely, but it is still far less efficient than if Web Workers can be used.
By creating a Web Worker, you are creating a bridge between the main JavaScript in your page and a sandboxed piece of JavaScript running in another thread. The two sides of the bridge communicate with each other by asynchronously sending and receiving messages, using postMessage(…) and listening for the message event.

 

The Web Workers communication interface also allows errors to be sent and received. To signal an error from inside a worker, simply throw a JavaScript error, like so:
self.onmessage = function(evt) { var data = [];
for (var i=0; i<1500; i++) { data[i] = [];
for (var j=0; j<1500; j++) { data[i][j] = Math.random();
if (data[i][j] == 0) {
throw “I don’t like zeros in my array!”;
}
}
}

self.postMessage(data);
data = null; // unassign our copy of the data now, to free up memory
};

To receive an error message from a worker, listen for the error event:

var worker = new Worker(“init_array.js”);

worker.onerror = function(err) {
alert(“An error occurred in the initialization of the array.”); throw err; // optional
};

worker.onmessage = function(evt) {
alert(“Data array initialization finished!”); var data = evt.data;
};

worker.postMessage();

A Web Worker is sandboxed away from the main page, and basically can only communicate with the page using these messages. That means the worker cannot access the DOM to read or modify any information. Also, UI-centric tasks like calling an alert(…) dialog are not allowed.
However, a worker does have several helpful things available to it. For example, it can access the navigator object, to identify the user agent (browser) running it, and it can load scripts into itself using the importScripts(…) command:

if (navigator.userAgent.test(/MSIE/)) { //
importScripts(“ie_helper.js”);
}
self.onmessage = function(evt) {
/* … */
};

A worker may spawn another worker, as we have just seen. The code that created a worker may also terminate it, by calling terminate() on the worker instance.
Finally, workers may use timeouts and intervals, including setTimeout(…), clearTi meout(…), setInterval(…), and clearInterval(…). This would be useful if, for instance, you wanted to have a worker running in the background every so often, notifying the page each time it runs:
self.onmessage = function(evt) {
setInterval(function(){
self.postMessage(Math.random()); // send a random number back
}, 60*60*1000); // execute once per hour
};

 

Here is the list of other related tutorials that are highly recommended:

Here is the list of classes that are highly recommended:

Click here if you wish to learn more about training and career options available in the IT industry.