Sharing a load with SharedWorkers

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.

How it Works
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.

snippet
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.

snippet
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.
Related Tutorial