NCZOnline

Experimenting with web workers

Posted at August 18, 2009 09:00 am by Nicholas C. Zakas

Tags: JavaScript, Web Workers

In the past couple of months, there’s been some good information floating around about web workers. I have no desire to add yet another introduction to the topic into the blogosphere, but I would like to share some information about my experiences with web workers. What follows are some notes based on my playing around with the API.

Worker global scope

The interesting thing about workers is that they have their own global scope that is separate from the global scope that we all know and dread in JavaScript. Workers don’t share the browser UI thread that in-page JavaScript uses for execution, and therefore isn’t allowed access to the DOM or most of the BOM. In effect, a worker is a sandboxed environment in which JavaScript can be run completely separate from the page. This is why worker code must exist in a separate file rather than in the same location as the page code. Typical creation looks like this:

var worker = new Worker("worker.js")

The file worker.js contains all of the code to be executed within the worker. That code executes in the worker’s global scope. The global scope of a worker contains a limited set of functionality, including:

  • The XMLHttpRequest constructor.
  • A self object, which is the global object representing the worker in this scope.
  • All ECMAScript constructors.
  • A navigator object containing only appName, appVersion, userAgent, and platform properties.
  • A location object that is the same as window.location except that all properties are read-only.
  • setTimeout() and setInterval().
  • An importScripts() method, which is used to load external JavaScript files into the worker’s scope.

As in other ECMAScript environments, global variables become properties on self. Most of the worker examples show a really bad pattern that confuses what’s going on:

//inside worker.js
onmessage = function(event){
    //do something in response to the event
};

I looked over this code repeatedly trying to figure out exactly what was going on. Why is there a global variable being assigned to a function? Then I discovered that self is a reference to the worker’s global scope, and decided I’d write code like this instead:

//inside worker.js
self.onmessage = function(event){
    //do something in response to the event
};

This small addition makes the example code much more readable as this pattern is very common in JavaScript. I strongly recommend that anyone writing code with web workers stick with the convention of assigning properties and calling methods directly on the self object to avoid any confusion. It’s also worth mentioning that this points to self when accessed in the global worker scope.

Worker messaging

Worker can’t affect change in a page directly, instead they rely on a messaging system to pass data back and forth. The postMessage() method is used to send data into a worker, when called on the Worker instance in the page, and to send data out of the worker when called on the worker global object. Example:

//on page
var worker = new Worker("worker.js");

//receive message from the worker
worker.onmessage = function(event){
    alert(event.data);
};

//send message to the worker
worker.postMessage("Nicholas");

//in worker.js
//receive message from the page
self.onmessage = function(event){

    //send message back to the page
    this.postMessage("Hello, " + event.data);

};

The API on both sides of the communication is exactly the same. Calling postMessage() causes a message event to be fired asynchronously on the receiver. The event handler must be assigned using the old DOM 0 style of setting onmessage equal to a function. The event object has a property called data that contains the information from the supplier.

Perhaps the most interesting thing about this messaging system is the way in which the data is transferred. You can pass any primitive data type (string, number, Boolean, null, or undefined) as well as any instance of Object or Array that isn’t part of the DOM or the BOM. The tricky part is that the values appears to be passed directly through, such as:

//on page
var worker = new Worker("worker.js");

//receive message from the worker
worker.onmessage = function(event){
    alert(event.data.name);   //"Nicholas"
};

//send object to the worker
worker.postMessage({ name: "Nicholas" });

//in worker.js
//receive message from the page
self.onmessage = function(event){

    //send message back to the page
    var name = event.data.name;   //comes through fine!
    this.postMessage(event.data);

};

This code passes an object back and forth between the page and a web worker. You’ll note that the name property is accessible in both locations. This gives the appearance that the object is being passed directly through to the worker and then back. In reality, this can’t happen because the worker is in its own detached scope. What actually happens is that the value is serialized as it passes through and then deserialized on the other side. The page and the worker cannot share an object instance, only the data represented in the object. Firefox actually implements this by JSON-encoding the value and then JSON-decoding it on the other side. The end result is that a duplicate of the original data is created.

Better messaging

At first glance, the messaging system seems a bit too simple, with postMessage() just accepting a single argument to pass through. Passing a primitive value seems to be a poor way of communicating because there’s no context. I’ve now taken to passing objects all the time so I can provide better context as to the reason. Example:

//on page
var worker = new Worker("worker.js");

//send object to the worker
worker.postMessage({
    type: "first",
    data: "Nicholas"
});

//send object to the worker
worker.postMessage({
    type: "last",
    data: "Zakas"
});

//in worker.js
//receive message from the page
self.onmessage = function(event){

    //determine what to do
    switch(event.data.type){
        case "first":
            //do something
            break;
        case "last":
            //do something else
            break;
        //and so on...
    }
};

By always passing an object with some contextual data, your worker knows what to do with the data it received.

Wrap-up

Workers seem like an interesting feature in browsers that may ultimately prove useful. It’s going to take a while for web developers to full grasp the idea of data-centric JavaScript that can be done in a worker versus DOM-centric JavaScript that cannot. I’m not completely convinced of worker usefulness in most web applications at this point, however. The majority of examples floating out there seem cool but aren’t things that JavaScript will or should be doing in a web application. I haven’t yet come across an instance where I’ve said, “oh man, if only web workers were widely supported, this would be easy,” and so I wonder if that day will come or if workers will be relegated to the domain of proofs-of-concept without practical applications.

Disclaimer: Any viewpoints and opinions expressed in this article are those of Nicholas C. Zakas and do not, in any way, reflect those of my employer, my colleagues, Wrox Publishing, O'Reilly Publishing, or anyone else. I speak only for myself, not for them.

Both comments and pings are currently closed.

Related Posts

  • Web workers: errors and debugging
  • My ECMAScript 7 wishlist
  • Creating type-safe properties with ECMAScript 6 proxies
  • Creating defensive objects with ES6 proxies
  • Announcing Understanding ECMAScript 6

Further reading

  • spacer
  • spacer
  • spacer

10 Comments

Interesting post, it seems workers are pretty easy to… er… work with. I agree there doesn’t seem to be an obvious need yet, but I’m bookmarking this page in case that changes.

Thanks for clarifying the “self” / scope issue, that does make the examples on other sites a bit clearer.

HB on August 18th, 2009 at 2:06 pm

Nice one Nicholas. I really wish there was more literature/examples of PRACTICAL uses of web workers. I know Mozilla had a really complex one with a video running, but would love to something that could be actually used in an app/site.

Joe McCann on August 18th, 2009 at 4:43 pm

HB/Joe – I’m continuing to dig in and see if I can find a practical use for web workers. One possible thing I’ve come up with is to wrap Comet-type mechanisms in a worker, and then just expose incoming data through the postMessage() method. That would be far more compelling with a WebSocket implementation. The best use I can think of is a background syncing feature, such as Google Reader, where it may pull in more data from the server in a worker without interfering with your normal browsing. I’m sure there’s a lot of possibilities, but I too am trying to figure out the uber-practical “can’t live without it” use case.

Nicholas C. Zakas on August 18th, 2009 at 9:00 pm

This is just another step to help make the js environment a serious toolkit for building RIAs. Just like MVC separates your concerns when designing software, web workers can fulfill the ‘controller’ and/or ‘model’ parts of your app while keeping the ‘view’ logic on the dom-side, where we’ve been jamming all of our code for so long. It’s not a gee-whiz feature, but we should use it to build more maintainable applications.

Jonathan Julian on August 18th, 2009 at 11:18 pm

Hi Nicholas,

I too have been wondering if there are any actual examples where this stuff would be useful on the web – unless we start converting all those good ‘ol Flash and Java based games on Facebook into JS driven ones spacer

Or maybe some sort of data syncing app? Typically outside the web I use threads when loading lots of data into memory, mainly to make sure that good ‘ol progress bar…well…er…shows progress.

Anton Babushkin on August 19th, 2009 at 5:33 am

In contemporary webapplications, there doesn’t seem to be an obvious use for the Web Worker concept. However, regarding the design of the upcoming Chrome OS, I guess more and more applications will start moving towards the browser environment. When this happens with resource-heavy programs, you’ll probably be screaming for the Workers.

However, I think it still has to work its way upwards… good practices on application design with Workers doesn’t really seem to be around yet.

Joren on August 24th, 2009 at 9:22 am

Hi Nicholas,

I just want to comment that the clever messaging system works only on Firefox.
Safari and Chrome assume that the message is a string and not an object.
The spec seems to allow passing any object.

Tali Garsiel on September 24th, 2009 at 6:28 am

[...] Workers, which provides threading support for JavaScript. I’ve written about web workers in a previous post and noted that I don’t see any practical use for them right now. But in a world where [...]

Moving the Web forward | NCZOnline on September 24th, 2009 at 9:02 am

I’m experimenting with Webworkers too. It is nice but I don’t get why postMessage and onMessage are only able to send data as a (JSON) string. If for example I want to do some calculating over lets say an array of 100.000 integers, the browser has to first convert this to a string which I have to convert back to an array before I can use it. Waste of valuable CPU time isn’t it?

I guess the problem here is that when you allow just any object to be posted you could also send the document or window object which is just what you don’t want to keep the GUI responsive… hmm…

Almer on May 9th, 2010 at 4:58 pm

@Almer – That’s only in Safari/Chrome. Firefox allows you to pass in any serializable data (this is according to the spec). Safari/Chrome will eventually be updated to behave the same way.

Nicholas C. Zakas on May 11th, 2010 at 1:59 pm

Comments are automatically closed after 14 days.

Content copyright © 2004-2011 Nicholas C. Zakas. All Rights Reserved. All code examples on all pages, unless otherwise indicated, are BSD licensed. Blog Feed

gipoco.com is neither affiliated with the authors of this page nor responsible for its contents. This is a safe-cache copy of the original web site.