Web Workers allow running scripts in the background without blocking or freezing the user interface. By using Web Workers to run non-UI scripts in the background, your application can take advantage of multiple cores on your machine to execute scripts concurrently.
Workers allows you to spawn background workers running scripts in parallel to your main page. This allows for thread-like operation with message-passing as the coordination mechanism. Web Workers are also described as “Workers”.
This means that a long running computation no longer needs to block your user interface. HTML5 Web Workers typically run on separate threads so you can take advantage of multicore CPUs.
Using Workers effectively, help you avoid the slow-scripts warnings that display with JavaScript loops continue over several seconds.
Although a lot of focus in the specification is around, the architecture and code can be used on the server side too. The node-webworker module aims to implement as much of the HTML5 Web Workers API as is practical and useful in the context of NodeJS.
Web Worker Scenarios
You will want to consider Web Workers:
* Encoding/decoding a large string that you create on the client and wish to keep it there.
* Code syntax highlighting or other real-time text analysis (like spell checking).
* Background I/O (e.g. Polling web services).
* Complex mathematical calculations (prime numbers, encryption, simulated annealing, etc.) – think on a web app that work offline and still need to run…
* Analyzing or processing video or audio data (including face and voice recognition)
- Image processing by using the data extracted from the <canvas> or the <video> elements. You can divide the image into several zones and push them to the different Workers that will work in parallel. You’ll then benefit from the new generation of multi-cores CPUs. The more you have, the faster you’ll go.
- big amount of data retrieved that you need to parse after an XMLHTTPRequest call. If the time needed to process this data is important, you’d better do it in background inside a Web Worker to avoid freezing the UI Thread. You’ll then keep a reactive application.
- background text analysis: as we have potentially more CPU time available when using the Web Workers, we can now think about new scenarios in JavaScript. For instance, we could imagine parsing in real-time what the user is currently typing without impacting the UI experience. Think about an application like Word (of our Office Web Apps suite) leveraging such possibility: background search in dictionaries to help the user while typing, automatic correction, etc.
- concurrent requests against a local database. IndexDB will allow what the Local Storage can’t offer us: a thread-safe storage environment for our Web Workers.
Worker Limitations
Workers are relatively heavy-weight, and are not intended to be used in large numbers. Generally, workers are expected to be long-lived, have a high start-up performance cost, and a high per-instance memory cost.
Workers do not have access to the direct access to the Web page or the DOM API. Although they do not block the UI, they do use CPU cycles and can make the system be less responsive.
Workers are not supported in every browser. The latest versions of browsers (IE 10, Firefox 16 and later, Chrome 23 and later, Safari 5.1 and later, Opera 12.1 and later, and others) and Windows 8 applications are supported.
See CanIUse site for supported browsers.
Support through a polyfill (emulation) does not really make sense. For backward compatibility, you can just call your code inline with the down-side being that you will block the UI thread. Or you can mimic ‘concurrency’ by using techniques like setTimeout()
, setInterval()
, XMLHttpRequest
, and event handlers. Although these features run asynchronously, non-blocking doesn’t necessarily mean concurrency. Asynchronous events are processed after the current executing script has yielded.
Check for Support for Workers
You can check to see if Workers are supported in your browser. A call to typeof(Worker) will return the global Worker property. The property will be undefined if your browser does not support Workers.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
if (typeof(Worker) !== "undefined") { | |
// supports Web Worker | |
} |
Using Web Workers
Let’s start with a simple example. Let’s count from 0 to 10 million by ten-thousands.
The main page looks like this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE HTML> | |
<html> | |
<head> | |
<title>Worker example: One-core computation</title> | |
</head> | |
<body> | |
<p>The biggest number so far is: <output id="result"></output></p> | |
<script> | |
var worker = new Worker('counter.js'); | |
worker.onmessage = function (e) { | |
document.getElementById('result').textContent = e.data; | |
}; | |
</script> | |
</body> | |
</html> |
And in counter.js:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
for (var i = 0; i <= 10000000 ; i += 1) { | |
if (i % 1000 == 0) { | |
postMessage(i); | |
} | |
} |
In this example, the page updates a counter until it reaches 10,000,000.
In lieu of counting, you can imagine retrieving or updating data on a Web service.
Creating Web Workers
You can spawn background scripts that run in parallel with the main page. A new worker object requires a .js file, which is included via an asynchronous request to the server.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var myWorker = new Worker('aWorkerTask.js'); |
The code sets up event listeners and communicates with the script that spawned it.
Communicating with Web Workers
In the context of a worker, both self
and this
reference the global scope for the worker.
Since web workers are in external files, they do not have access to the following JavaScript objects:
- The window object
- The document object
- The parent object
All communication to and from the worker thread is managed through messages. Both the host worker and the worker script can send messages by using postMessage and listen for a response by using the onmessage event. The content of the message is sent as the data property of the event.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var myWorker = new Worker('aWorkerTask.js'); | |
myWorker.onmessage = function(e) { | |
console.log(e.data); | |
}; |
Or you can use addEventListener.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var myWorker = new Worker('aWorkerTask.js'); | |
myWorker.addEventListener('message', function(e) { | |
console.log("Called back by the worker!\n" + e.data); | |
}, false); |
Then inside the Worker, in the case of the example in aWorkerTask.js, use postMessage to send a message back to the originating thread.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// code in myWorkerTask | |
postMessage('My Messages from the Worker'); |
You can also post serialized data or JSON.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
postMessage({'cmd': 'init', 'timestamp': Date.now()}); |
Stopping Web Worker
To terminate a web worker, and free browser/computer resources, use the terminate() method:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
myWorker.terminate(); |
Handling Errors
You will want to handle any errors that are thrown in your web workers. If an error occurs while a worker is executing, an ErrorEvent
occurs.
You get three useful properties for figuring out what went wrong: filename
– the name of the worker script that caused the error, lineno
– the line number where the error occurred, and message
– a meaningful description of the error.
For example:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<output id="error" style="color: red;"></output> | |
<output id="result"></output> | |
<script> | |
function onError(e) { | |
document.getElementById('error').textContent = [ | |
'ERROR: Line ', e.lineno, ' in ', e.filename, ': ', e.message].join(''); | |
} | |
function onMsg(e) { | |
document.getElementById('result').textContent = e.data; | |
} | |
var worker = new Worker('workerWithError.js'); | |
worker.addEventListener('message', onMsg, false); | |
worker.addEventListener('error', onError, false); | |
worker.postMessage(); // Start worker without a message. | |
</script> |
Initializing Worker, Two-way Communication
Messages passed between the main page and workers are copied, not shared. For example, you can pass a parameter inside JSON that is accessible in both locations. It appears that the object is being passed directly to the worker even though it’s running in a separate, dedicated thread.
The object is being serialized as it’s handed to the worker, and then, de-serialized on the other end. The page and worker do not share the same instance, so the end result is a duplicate created on each pass. Most browsers implement this feature by automatically JSON encoding/decoding the value on either end.
To set up two-way communication, both the main page and the worker thread listen for the onmessage event.
For example, when a button is pressed, a message is sent to the Worker that then echos it back. The message is passed in using JSON and then back from the Worker as JSON too.
Here is the HTML.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html xmlns="http://www.w3.org/1999/xhtml"> | |
<head> | |
<title></title> | |
</head> | |
<body> | |
<div> | |
<button onclick="sayHello()">Say HI</button> | |
<button onclick="unknownCmd()">Send unknown command</button> | |
<button onclick="stop()">Stop worker</button> | |
</div> | |
<div> | |
<output id="workermessage"></output> | |
</div> | |
<script> | |
function sayHello() { | |
worker.postMessage({ 'cmd': 'start', 'msg': 'Hello' }); | |
} | |
function stop() { | |
// Calling worker.terminate() from this script would also stop the worker. | |
worker.postMessage({ 'cmd': 'stop', 'msg': 'Bye' }); | |
} | |
function unknownCmd() { | |
worker.postMessage({ 'cmd': 'notsupported', 'msg': '???' }); | |
} | |
var worker = new Worker('3-TwoWay.js'); | |
worker.addEventListener('message', function (e) { | |
document.getElementById('workermessage').textContent = e.data; | |
}, false); | |
</script> | |
</body> | |
</html> |
Here is the JavaScript in a file named 3-TwoWay.js.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
self.addEventListener('message', function (e) { | |
var data = e.data; | |
switch (data.cmd) { | |
case 'start': | |
self.postMessage('My worker has: ' + data.msg); | |
break; | |
case 'stop': | |
self.postMessage('My worker has stopped: ' + data.msg + '. (buttons will no longer work)'); | |
self.close(); // Terminates the worker. | |
break; | |
default: | |
self.postMessage('Huh? ' + data.msg); | |
}; | |
}, false); |
Simple Example
The following example tries to calculate Pi. Here’s the html:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html xmlns="http://www.w3.org/1999/xhtml"> | |
<head> | |
<title>Calculate Pi</title> | |
</head> | |
<body> | |
<p>The longest pi so far is: <output id="result"></output></p> | |
<p>pi = 3.14159265</p> | |
<button onclick="stopWorker()">Stop Worker</button> | |
<script> | |
var work; | |
if (typeof (Worker) !== "undefined") { | |
if (typeof (work) == "undefined") { | |
work = new Worker('2-CalculatePi.js'); | |
work.addEventListener('message', function (e) { | |
document.getElementById('result').textContent = e.data; | |
}, false); | |
work.addEventListener('error', function (e) { | |
document.getElementById('result').textContent = e.message; | |
}, | |
false); | |
} | |
} | |
else { | |
document.getElementById('result').textContent = " not supported."; | |
} | |
function stopWorker() { | |
work.terminate(); | |
} | |
</script> | |
</body> | |
</html> |
Here is the worker.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function calculatePi() { | |
var result = 0; | |
var denominator = 1; | |
var add = true; | |
while (true) { | |
if(add) { | |
result = result + 4/denominator; | |
add = false; | |
} else { | |
result = result – 4/denominator; | |
add = true; | |
} | |
this.postMessage(result); | |
denominator = denominator + 2; | |
} | |
} | |
calculatePi(); |
More Web Worker Features
There are additional ways to use Web Workers. Let your creativity begin.
Inline
You may want to create the Worker inline inside the web page instead of inside a separate file. Or you have may a need to provide the code by some external source as a string. In these cases you can use inline Web Workers.
Here is an example script tag.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script id="worker" type="javascript/worker"> | |
postMessage('worker sent message'); | |
</script> |
The main way to create an inline Web Worker is using the BlobBuilder
object which was added by the HTML5 File API. BlobBuilder
enables you to create a blob object from a given string.
For more information, see Working with Inline Web Workers.
Shared Workers
Shared web workers allow any number of scripts to communicate with a single worker.
To create a shared web worker, you pass a JavaScript file name to a new instance of the SharedWorker object and then you communicated via a port object and attach a message event handler.
See How to Use JavaScript Shared Web Workers in HTML5 for examples.
ShareWorkers not supported in all modern browsers.
Subworkers
Workers may spawn more workers if they wish. So-called subworkers must be hosted within the same origin as the parent page. Also, the URIs for subworkers are resolved relative to the parent worker’s location rather than that of the owning page. This makes it easier for workers to keep track of where their dependencies are.
Subworkers are not currently supported in all browsers.
Importing Scripts
Worker threads have access to a global function, importScripts()
, which lets them import scripts or libraries into their scope. It accepts as parameters zero or more URIs to resources to import, such as:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
importScripts(); /* imports nothing */ | |
importScripts('foo.js'); /* imports just "foo.js" */ | |
importScripts('foo.js', 'bar.js'); /* imports two scripts */ |
Online Examples
For hands-on demonstrations of Web Workers in action, see Web Worker Fountains and Web Worker Harness for test262 on the IE Test Drive.
Mozilla developer page shows Advanced passing JSON Data and creating a switching system. The demos shows how you can pass some complex data and have to call many different functions both in the main page and in the Worker
Sample code is provided in the Web Worker specification for these scenarios:
- A background number-crunching worker. The simplest use of workers is for performing a computationally expensive task without interrupting the user interface.
- Worker used for background I/O. The main document uses two workers, one for fetching stock updates for at regular intervals, and one for fetching performing search queries that the user requests.
- Shared workers introduction. Shared workers use slightly different APIs, since each worker can have multiple connections.
- Shared state using a shared worker. Multiple windows (viewers) can be opened that are all viewing the same map.
- Delegation. A computationally expensive task that is to be performed for every number from 1 to 10,000,000 is farmed out to ten subworkers.
Debugging
IE10 offers you to directly debug the code of your Web Workers inside its script debugger like any other script.
For that, you need to launch the development bar via the F12 key and navigate to the “Script” tab. You shouldn’t see the JS file associated to your worker yet. But right after pressing the “Start debugging” button, it should magically be displayed:
Next step is then to simply debug your worker like you’re used to debug your classic JavaScript code.
References
For more information about Web Workers, see:
- Web Workers specification at W3C
- Using web workers from Mozilla Developer Network
- Web Workers rise up from Dev.Opera
- Web Workers (Windows) on MSDN
- Design of Web Workers for NodeJS
- The Basics of Web Workers from HTML5 Rocks Tutorials
- Debugging Web Workers in IE10
- David Rousset’s Introduction to the HTML5 Web Workers: the JavaScript multithreading approach
Sample Code
Sample code is available in the DevDays GitHub repository. See https://github.com/devdays/html5-tutorials