The Web Workers API allows to run large scripts in the background without interrupting our main page or web app. It was impossible to run multiple JavaScript scripts concurrently prior to Web Workers.
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 won’t be locked up if you have particularly long-running JavaScript code.
This allows us to take scripts that take a long time to run, and require no user interaction, and run them behind the scenes concurrently with any other scripts that do handle user interaction and Web Workers brings us thread-like features. Each "worker" handles its own chunk of script, without interfering with other workers or the rest of the page. To ensure the workers stay in sync with each other, the API defines ways to pass messages from one worker to another.
--Web Workers are supported in the following browsers.
Web Workers are currently unsupported in all versions of IE, iOS, and Android.
To test if the browser supports Web Workers, use the following feature-detect for the Worker API.
var webworkers_support = !!window.Worker;
You can test for Web Worker support by checking whether the object is undefined.
if (typeof Worker != "undefined") { // do the jazzy stuff }
To run a complex, long-running JavaScript task without locking up the UI in the browser create a new worker.
var worker = new Worker('sample_worker.js');
A new worker object is fired up, reads in the sample_worker.js
Unlike normal javascript you cannot call methods inside the worker from your document and data cannot be returned from the worker to your document. To work with a worker, everything must be communicated through posting messages between the worker and your document. The only way to send a information to the worker is using postMessage inside a worker.
worker.postMessage('hello worker!');
The only way to receive information from the worker is using the onmessage
event handler.
worker.onmessage = function (event) { alert('The worker just sent me this: ' + event.data); }
The code inside the worker must also communicate using the postMessage
/onmessage
combo. The only other method available to you via the worker object is terminate method.
Within a Web Worker you don’t have access to such pleasures as the DOM. In fact, if you need to do anything with the DOM, you’re going to have to prepare the work in the worker, then pass it to the parent document to do the actual DOM manipulation.
However there are a number of things you can do in a worker (according to the specifi cation):
postMessage
and listen for inbound messages via onmessageclose
, to end the current workerXMLHttpRequest
, for Ajax requestssetTimeout
, setInterval
, and their clearing counterpartsonmessage = function(event) { if (event.data == "hello worker!") { postMessage("hello there, right back at you"); } else { postMessage("Can’t you see I’m busy, leave me alone"); } };
The this
keyword would refer to the global scope in the normal , the window object where as in the worker, the global scope is the worker instance. It also means that the this keyword inside of setTimeout
and setInterval
.
Workers may use timeouts and intervals, including setTimeout(...)
, clearTimeout(...)
, 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 }
Web Workers is about modularising a block of code or functionality and running it in a stand-alone environment (ie., the worker itself). Web Workers can also load external JavaScript files and libraries via the importScripts method.
The worker can access the navigator object, to identify the user agent (browser) running it. A worker can also load scripts into it, using the importScripts(...)
command.
if (navigator.userAgent.test(/MSIE/)) { // UA sniffing is *bad* practice!! importScripts("ie_helper.js"); } self.onmessage = function(evt) { /* ... */ }
importScripts('xhr.js'); importScripts('main.js', 'block1.js', 'block2.js');
Each script is processed one at a time. The script must be on the same origin as the worker—the same domain, cname, etc. The worker then synchronously loads the JavaScript into itself, returning to continue only once the script has finished processing.
A worker may also be terminated by the code which created the worker, by calling terminate()
on the worker instance.
Web Worker is the SharedWorker
, which is currently supported in Chrome and WebKit (rather than Safari). A shared worker is pretty much like an average Web Worker except that multiple documents can access the same single instance of the worker.
It means that if you have several popups or several iframes, all of those documents can access this single shared worker and this single shared worker will serve all of those documents. This would be useful for applications like Gmail or Facebook, where there may be some client-side data that needs to be maintained, such as the messages for the user, and you have several different windows open.
The shared worker can then maintain all of the changes to the client-side messages database (Web Storage) and then push all of those updates via postMessage to each of the popups, iframes, and so on.
This means that there’s no getting data out of sync or race conditions if each of the popups, iframes, etc. was individually connecting to the server and trying to each manage the client side, since the shared worker is the single point of contact for all of that type of work.
The SharedWorker
works very slightly differently when it comes to communication. For starters there’s the concept of ports—this is an array-like object that contains a reference to each of the communication channels the shared worker has. Also, if you bind to the message event using addEventListener, you have to manually start the worker, which I’ll show you in the following code sample.
In addition, within the worker the connect event fires when the SharedWorker is created, which can be used to keep track of how many connections the worker has to other documents. The documents creating the SharedWorker contain the following code.
var worker = new SharedWorker(‘messages.js’); worker.port.addEventListener(‘message’, function(event) { var messages = JSON.parse(event.data); showNewMessages(messages); }, false); worker.port.start();
In the previous example, you can see we’re accessing the worker via the port property. This is how you interact and, in fact, distinguish between shared and nonshared workers. As the example binds to the message event using addEventListener, the worker must be connected manually using the .start()method.
The code wouldn’t need this if it used onmessage.
Next is the messages.js worker.
importScripts('xhr.js'); importScripts('database.js'); var connections = []; onconnect = function(event) { connections.push(event.ports[0]); } var xhr = new XHR('/get-new-messages'); xhr.oncomplete = function(messages) { database.updateMessages(messages); for (var i = 0; i < connections.length; i++) { connections[i].postMessage(JSON.stringify(messages)); } xhr.send(); // causes us to loop forever }; xhr.send();
When a client document connects to the worker, the connect event is fired, which allows me to capture the connection port. This is collected through the event.ports[0] reference, even though there will never be more than one item inside the ports property. However the worker reference is inside this, so we can use this to post messages and receive messages. As you see in the previous example, when the Ajax complete function runs, I loop through all of the connected ports and send them each a message of the new email messages that have come in. This way the connected clients act as dumb terminals, oblivious to any of the real work going on to store the messages in the client-side database.