Javascript Web Workers: From Basics to jQuery.Hive

This is long overdue. Also, it should serve as a context for my slides from jQuery Conference, San Francisco 2010 (use the up and down arrow keys to navigate).


Whether or not you’ve read the WHATWG Web Worker spec and are looking for more information about Javascript multi threading OR you already have a rudimentary understanding of Web Workers and simply need more input, I hope to shed some new light on the subject.


I’ll get started with some basic information about Workers:


  • The Workers API is originally based on the now deprecated Gears WorkerPool API
  • Creates an OS level script execution environment that is capable of scaling across multiple cores when present
  • Workers allow web applications to run non-blocking scripts parallel to the main page.
    • Allow long-running processes to execute uninterrupted, while keeping the main page responsive.
    • Replace the need for breaking up execution with recursive function setTimeout hacks.

Myths, misconceptions and clarifications

  • Workers are not intended to be used in large numbers.
    • Creation of a new script execution environment is not free from performance expense. One worker will be hardly noticeable, whereas a large number will cause a loss of response in the main window.
  • Workers cannot access non-thread safe content, this means: the `window` and `document` objects or any part of the DOM directly (elements, et al.)
  • Workers can only pass data in and out of a thread through the postMessage() API and onMessage Event.

Why you don’t get it and Why you don’t care

  • Web developers aren’t used to the concept of multi-threading, because until now, the concept hasn’t applied to us
  • Support is limited to: Firefox 3.5+, Safari 4+ and Chrome 4+

Got all that? Great! Now let’s look at the API


The first thing we need to establish is the new WorkerGlobalScope which is your thread’s script execution environment. It’s kind of like the window, but is actually the “global scope” of the worker. This parallel dimension exists within the file you passed to the new Worker(‘worker-javascript-file.js’) constructor.


The easiest way to contextualize the worker thread is to think of your main window as the initial worker (as it IS a script execution environment) and the Worker API let’s you make “windows” in terms of script execution, in parallel, but with some limitations.

Here’s what’s new in the window object (main window execution):

workers-1.js

/*

  The constructor

  It takes the worker file name as an argument

*/var worker = new Worker('worker-javascript-file.js')

/*

  The postMessage() function sends a message to the worker

*/worker.postMessage(message)

/*

  The onmessage event receives messages from the worker

*/worker.onmessage = function (event) {
  /*

    The message is packaged in the event object

    The property of interest is:
    data

  */  console.log( event.data )
}
/*

  In my slides, I lobby against the use of
  onmessage = function () {} syntax,
  instead encouraging developers to use
  the addEventListener() syntax

*/worker.addEventListener('message', function (event) {

  console.log(event.data);

}, false);



/*

  the onerror event is triggered if an error occurred.

*/worker.onerror = function (event) {
  /*

    The onerror event does not bubble and can be canceled.
    The default action can be prevented with
    event.preventDefault()

    The properties on interest are:
    message, filename, lineno

  */}

/*

  The terminate() function is supposed
  to stop a Worker from executing immediately

*/worker.terminate()

/*

  The Worker object also implements the following:

*/
worker.addEventListener()
worker.removeEventListener()
worker.dispatchEvent()


The new WorkerGlobalScope has it’s own new API as well:

workers-2.js

/*

  The Worker: WorkerGlobalScope

  Inside the worker, the keywords "self" and "this"
  are the same thing and both are the equivalent
  to the global WorkerGlobalScope

*/self = this = [WorkerGlobalScope]

/*

  The postMessage() function sends a message
  from the worker to the main execution thread

*/self.postMessage(message)

/*

  The onmessage event receives messages
  from the main execution thread

*/self.onmessage = function (event) {
  /*

    The message is packaged in the event object

    The property of interest is:
    data

  */}

/*

  As with the example in the main window,
  I'd like to encourage developers to use
  the addEventListener() syntax

*/self.addEventListener('message', function (event) {

  this.postMessage(event.data);

}, false);

/*

  The importScripts() function can be used
  to call in reusable libraries

  But there is a drawback here - all the major
  libraries that exist will throw exceptions
  if the developer tries to import them into the
  WorkerGlobalScope - they all reference either
  "window" or "document" - both of which are
  considered "non thread safe".

  Because of this, developers are left to
  "roll their own" reusable libraries, which
  is both inefficient and counter productive.


*/self.importScripts(urls)

/*

  WorkerGlobalScope also implements
  the following interfaces:

*/
self.addEventListener()
self.removeEventListener()
self.dispatchEvent()
self.setTimeout()
self.clearTimeout()
self.setInterval()
self.clearInterval()

/*

  As well as:

*/
self.location
self.navigator

/*

  And

*/
new XMLHttpRequest
// However, requestXML and channels properties are NULL

The window and the workerglobalscope BOTH have a postMessage() function and an onmessage event.This is how they communicate.


Get these files from GitHub

…and give it try. (Requires a javascript console to see the results)


You’ll notice that I’ve used the progressive approach with addEventListener() instead of onmessage = function () {} – I’ve done so because I don’t believe we should be writing attribute event handlers on worker objects as we once did with elements in the DOM. I also take a strong opposition to Web Worker “tutorials” that use this syntactic approach.

Continued in Part II

EditIn the time since this was originally published, Chrome, Safari & Opera now support complex JSON messages.

Comments

Contact Us

We'd love to hear from you. Get in touch!

Phone

+1 617-283-2807

Mail

P.O. Box 961436
Boston, MA 02196