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.
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
navigatorobject, which contains only four properties:appName,appVersion,userAgent, andplatformA
locationobject (same as onwindow, except all properties are read-only)A
selfobject that points to the global worker objectAn
importScripts()method that is used to load external Javascript for use in the workerAll ECMAScript objects, such as
Object,Array,Date, etc.The
XMLHttpRequestconstructorThe
setTimeout()andsetInterval()methodsA
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.
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.
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.
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.
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.




Help









