Jump to content

Introducing the Web Workers API

0
  adfm's Photo
Posted Apr 15 2010 02:06 PM

Web workers offer a potential performance increase and the ability to execute code outside the browser UI. This excerpt from Nicholas C. Zakas' High Performance Javascript introduces you to web workers API and it's practical use.


Since Javascript was introduced, there has been no way to execute code outside of the browser UI thread. The web workers API changes this by introducing an interface through which code can be executed without taking time on the browser UI thread. Originally part of HTML 5, the web workers API has been split out into its own specification (http://www.w3.org/TR/workers/); web workers have already been implemented natively in Firefox 3.5, Chrome 3, and Safari 4.

Web workers represent a potentially huge performance improvement for web applications because each new worker spawns its own thread in which to execute Javascript. That means not only will code executing in a worker not affect the browser UI, but it also won’t affect code executing in other workers.

Worker Environment

Since web workers aren’t bound to the UI thread, it also means that they cannot access a lot of browser resources. Part of the reason that Javascript and UI updates share the same process is because one can affect the other quite frequently, and so executing these tasks out of order results in a bad user experience. Web workers could introduce user interface errors by making changes to the DOM from an outside thread, but each web worker has its own global environment that has only a subset of Javascript features available. The worker environment is made up of the following:

  • A navigator object, which contains only four properties: appName, appVersion, userAgent, and platform

  • A location object (same as on window, except all properties are read-only)

  • A self object that points to the global worker object

  • An importScripts() method that is used to load external Javascript for use in the worker

  • All ECMAScript objects, such as Object, Array, Date, etc.

  • The XMLHttpRequest constructor

  • The setTimeout() and setInterval() methods

  • A close() method that stops the worker immediately

Because web workers have a different global environment, you can’t create one from any Javascript code. In fact, you’ll need to create an entirely separate Javascript file containing just the code for the worker to execute. To create a web worker, you must pass in the URL for the Javascript file:

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

Once this is executed, a new thread with a new worker environment is created for the specified file. This file is downloaded asynchronously, and the worker will not begin until the file has been completely downloaded and executed.

Worker Communication

Communication between a worker and the web page code is established through an event interface. The web page code can pass data to the worker via the postMessage() method, which accepts a single argument indicating the data to pass into the worker. There is also an onmessage event handler that is used to receive information from the worker. For example:

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

worker.onmessage = function(event){

	alert(event.data);

};

worker.postMessage("Nicholas");

The worker receives this data through the firing of a message event. An onmessage event handler is defined, and the event object has a data property containing the data that was passed in. The worker can then pass information back to the web page by using its own postMessage() method:

//inside code.js

self.onmessage = function(event){

	self.postMessage("Hello, " + event.data + "!");

};

The final string ends up in the onmessage event handler for the worker. This messaging system is the only way in which the web page and the worker can communicate.

Only certain types of data can be passed using postMessage(). You can pass primitive values (strings, numbers, Booleans, null, and undefined) as well as instances of Object and Array; you cannot pass any other data types. Valid data is serialized, transmitted to or from the worker, and then deserialized. Even though it seems like the objects are being passed through directly, the instances are completely separate representations of the same data. Attempting to pass an unsupported data type results in a Javascript error.

Note

Safari 4's implementation of workers only allows you to pass strings using postMessage(). The specification was updated after that point to allow serializable data to be passed through, which is how Firefox 3.5 implements workers.

Loading External Files

Loading extra Javascript files into a worker is done via the importScripts() method, which accepts one or more URLs for Javascript files to load. The call to importScripts() is blocking within the worker, so the script won’t continue until all files have been loaded and executed. Since the worker is running outside of the UI thread, there is no concern about UI responsiveness when this blocking occurs. For example:

//inside code.js

importScripts("file1.js", "file2.js");



self.onmessage = function(event){

	self.postMessage("Hello, " + event.data + "!");

};

The first line in this code includes two Javascript files so that they will be available in the context of the worker.

Practical Uses

Web workers are suitable for any long-running scripts that work on pure data and that have no ties to the browser UI. This may seem like a fairly small number of uses, but buried in web applications there are typically some data-handling approaches that would benefit from using a worker instead of timers.

Consider, for example, parsing a large JSON string (JSON parsing is discussed further in Chapter 7, Ajax). Suppose that the data is large enough that parsing takes at least 500 milliseconds. That is clearly too long to allow Javascript to run on the client, as it will interfere with the user experience. This particular task is difficult to break into small chunks with timers, so a worker is the ideal solution. The following code illustrates usage from a web page:

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



//when the data is available, this event handler is called

worker.onmessage = function(event){



	//the JSON structure is passed back

	var jsonData = event.data;



	//the JSON structure is used

	evaluateData(jsonData);

};



//pass in the large JSON string to parse

worker.postMessage(jsonText);

The code for the worker responsible for JSON parsing is as follows:

//inside of jsonparser.js



//this event handler is called when JSON data is available

self.onmessage = function(event){



	//the JSON string comes in as event.data

	var jsonText = event.data;



	//parse the structure

	var jsonData = JSON.parse(jsonText);



	//send back to the results

	self.postMessage(jsonData);

};

Note that even though JSON.parse() is likely to take 500 milliseconds or more, there is no need to write any additional code to split up the processing. This execution takes place on a separate thread, so you can let it run for as long as the parsing takes without interfering with the user experience.

The page passes a JSON string into the worker by using postMessage(). The worker receives the string as event.data in its onmessage event handler and then proceeds to parse it. When complete, the resulting JSON object is passed back to the page using the worker’s postMessage() method. This object is then available as event.data in the page’s onmessage event handler. Keep in mind that this presently works only in Firefox 3.5 and later, as Safari 4 and Chrome 3’s implementations allow strings to be passed only between page and worker.

Parsing a large string is just one of many possible tasks that can benefit from web workers. Some other possibilities are:

  • Encoding/decoding a large string

  • Complex mathematical calculations (including image or video processing)

  • Sorting a large array

Any time a process takes longer than 100 milliseconds to complete, you should consider whether a worker solution is more appropriate than a timer-based one. This, of course, is based on browser capabilities.

Cover of High Performance Javascript
Learn more about this topic from High Performance Javascript. 

If you're like most developers, you rely heavily on Javascript to build interactive and quick-responding web applications. The problem is that all of those lines of Javascript code can slow down your apps. This book reveals techniques and strategies to help you eliminate performance bottlenecks during development. You'll learn optimal ways to load code onto a page, programming tips to help your Javascript run as efficiently and quickly as possible, best practices to build and deploy your files to a production environment, and more.

Learn More Read Now on Safari


Tags:
0 Subscribe


0 Replies