Abstract

This specification defines an API that allows Web application authors to spawn background workers running scripts in parallel to their main page. This allows for thread-like operation with message-passing as the coordination mechanism.

Status of This document

This section describes the status of this document at the time of its publication. Other documents may supersede this document. A list of current W3C publications and the most recently formally published revision of this technical report can be found in the W3C technical reports index at www.w3.org/TR/.

If you wish to make comments regarding this document, you can enter feedback using this form:

Feedback Comments

Please don't use section numbers as these tend to change rapidly and make your feedback harder to understand.

(Note: Your IP address and user agent will be publicly recorded for spam prevention purposes.)

You can also e-mail feedback to public-webapps@w3.org (subscribe, archives), or whatwg@whatwg.org (subscribe, archives). All feedback is welcome.

Implementors should be aware that this specification is not stable. Implementors who are not taking part in the discussions are likely to find the specification changing out from under them in incompatible ways. Vendors interested in implementing this specification before it eventually reaches the Candidate Recommendation stage should join the aforementioned mailing lists and take part in the discussions.

The latest stable version of the editor's draft of this specification is always available on the W3C CVS server and in the WHATWG Subversion repository. The latest editor's working copy (which may contain unfinished text in the process of being prepared) contains the latest draft text of this specification (amongst others). For more details, please see the WHATWG FAQ.

Notifications of changes to this specification are sent along with notifications of changes to related specifications using the following mechanisms:

E-mail notifications of changes
Commit-Watchers mailing list (complete source diffs): lists.whatwg.org/listinfo.cgi/commit-watchers-whatwg.org
Browsable version-control record of all changes:
CVSWeb interface with side-by-side diffs: dev.w3.org/cvsweb/html5/
Annotated summary with unified diffs: html5.org/tools/web-apps-tracker
Raw Subversion interface: svn checkout svn.whatwg.org/webapps/

The W3C Web Applications Working Group is the W3C working group responsible for this specification's progress along the W3C Recommendation track. This specification is the 19 May 2014 Editor's Draft.

This document was produced by a group operating under the 5 February 2004 W3C Patent Policy. W3C maintains a public list of any patent disclosures made in connection with the deliverables of the group; that page also includes instructions for disclosing a patent. An individual who has actual knowledge of a patent which the individual believes contains Essential Claim(s) must disclose the information in accordance with section 6 of the W3C Patent Policy.

Table of Contents

  1. 1 Introduction
    1. 1.1 Scope
    2. 1.2 Examples
      1. 1.2.1 A background number-crunching worker
      2. 1.2.2 Worker used for background I/O
      3. 1.2.3 Shared workers introduction
      4. 1.2.4 Shared state using a shared worker
      5. 1.2.5 Delegation
    3. 1.3 Tutorials
      1. 1.3.1 Creating a dedicated worker
      2. 1.3.2 Communicating with a dedicated worker
      3. 1.3.3 Shared workers
  2. 2 Conformance requirements
    1. 2.1 Dependencies
  3. 3 Terminology
  4. 4 Infrastructure
    1. 4.1 The global scope
      1. 4.1.1 The WorkerGlobalScope common interface
      2. 4.1.2 Dedicated workers and the DedicatedWorkerGlobalScope interface
      3. 4.1.3 Shared workers and the SharedWorkerGlobalScope interface
    2. 4.2 The event loop
    3. 4.3 The worker's lifetime
    4. 4.4 Processing model
    5. 4.5 Runtime script errors
    6. 4.6 Creating workers
      1. 4.6.1 The AbstractWorker abstract interface
      2. 4.6.2 Script settings for workers
      3. 4.6.3 Dedicated workers and the Worker interface
      4. 4.6.4 Shared workers and the SharedWorker interface
  5. 5 APIs available to workers
    1. 5.1 Importing scripts and libraries
    2. 5.2 The WorkerNavigator object
    3. 5.3 Worker locations
  6. References
  7. Acknowledgements

1 Introduction

1.1 Scope

This section is non-normative.

This specification defines an API for running scripts in the background independently of any user interface scripts.

This allows for long-running scripts that are not interrupted by scripts that respond to clicks or other user interactions, and allows long tasks to be executed without yielding to keep the page responsive.

Workers (as these background scripts are called herein) are relatively heavy-weight, and are not intended to be used in large numbers. For example, it would be inappropriate to launch one worker for each pixel of a four megapixel image. The examples below show some appropriate uses of workers.

Generally, workers are expected to be long-lived, have a high start-up performance cost, and a high per-instance memory cost.

1.2 Examples

This section is non-normative.

There are a variety of uses that workers can be put to. The following subsections show various examples of this use.

1.2.1 A background number-crunching worker

This section is non-normative.

The simplest use of workers is for performing a computationally expensive task without interrupting the user interface.

In this example, the main document spawns a worker to (naïvely) compute prime numbers, and progressively displays the most recently found prime number.

The main page is as follows:

<!DOCTYPE HTML>
<html>
 <head>
  <title>Worker example: One-core computation</title>
 </head>
 <body>
  <p>The highest prime number discovered so far is: <output id="result"></output></p>
  <script>
   var worker = new Worker('worker.js');
   worker.onmessage = function (event) {
     document.getElementById('result').textContent = event.data;
   };
  </script>
 </body>
</html>

The Worker() constructor call creates a worker and returns a Worker object representing that worker, which is used to communicate with the worker. That object's onmessage event handler allows the code to receive messages from the worker.

The worker itself is as follows:

var n = 1;
search: while (true) {
  n += 1;
  for (var i = 2; i <= Math.sqrt(n); i += 1)
    if (n % i == 0)
     continue search;
  // found a prime!
  postMessage(n);
}

The bulk of this code is simply an unoptimised search for a prime number. The postMessage() method is used to send a message back to the page when a prime is found.

View this example online.

1.2.2 Worker used for background I/O

This section is non-normative.

In this example, the main document uses two workers, one for fetching stock updates at regular intervals, and one for performing search queries that the user requests.

The main page is as follows:

<!DOCTYPE HTML>
<html>
 <head>
  <title>Worker example: Stock ticker</title>
  <script>
   // TICKER
   var symbol = 'GOOG'; // default symbol to watch
   var ticker = new Worker('ticker.js');

   // SEARCHER
   var searcher = new Worker('searcher.js');
   function search(query) {
     searcher.postMessage(query);
   }

   // SYMBOL SELECTION UI
   function select(newSymbol) {
     symbol = newSymbol;
     ticker.postMessage(symbol);
   }
  </script>
 </head>
 <body>

  

The two workers use a common library for performing the actual network calls. This library is as follows:

function get(url) {
  try {
    var xhr = new XMLHttpRequest();
    xhr.open('GET', url, false);
    xhr.send();
    return xhr.responseText;
  } catch (e) {
    return ''; // turn all errors into empty results
  }
}

The stock updater worker is as follows:

importScripts('io.js');
var timer;
var symbol;
function update() {
  postMessage(symbol + ' ' + get('stock.cgi?' + symbol));
  timer = setTimeout(update, 10000);
}
onmessage = function (event) {
  if (timer)
    clearTimeout(timer);
  symbol = event.data;
  update();
};

The search query worker is as follows:

importScripts('io.js');
onmessage = function (event) {
  postMessage(get('search.cgi?' + event.data));
};

View this example online.

1.2.3 Shared workers introduction

This section is non-normative.

This section introduces shared workers using a Hello World example. Shared workers use slightly different APIs, since each worker can have multiple connections.

This first example shows how you connect to a worker and how a worker can send a message back to the page when it connects to it. Received messages are displayed in a log.

Here is the HTML page:

<!DOCTYPE HTML>
<title>Shared workers: demo 1</title>
<pre id="log">Log:</pre>
<script>
  var worker = new SharedWorker('test.js');
  var log = document.getElementById('log');
  worker.port.onmessage = function(e) { // note: not worker.onmessage!
    log.textContent += '\n' + e.data;
  }
</script>

Here is the JavaScript worker:

onconnect = function(e) {
  var port = e.ports[0];
  port.postMessage('Hello World!');
}

View this example online.


This second example extends the first one by changing two things: first, messages are received using addEventListener() instead of an event handler IDL attribute, and second, a message is sent to the worker, causing the worker to send another message in return. Received messages are again displayed in a log.

Here is the HTML page:

<!DOCTYPE HTML>
<title>Shared workers: demo 2</title>
<pre id="log">Log:</pre>
<script>
  var worker = new SharedWorker('test.js');
  var log = document.getElementById('log');
  worker.port.addEventListener('message', function(e) {
    log.textContent += '\n' + e.data;
  }, false);
  worker.port.start(); // note: need this when using addEventListener
  worker.port.postMessage('ping');
</script>

Here is the JavaScript worker:

onconnect = function(e) {
  var port = e.ports[0];
  port.postMessage('Hello World!');
  port.onmessage = function(e) {
    port.postMessage('pong'); // not e.ports[0].postMessage!
    // e.target.postMessage('pong'); would work also
  }
}

View this example online.


Finally, the example is extended to show how two pages can connect to the same worker; in this case, the second page is merely in an br on the first page, but the same principle would apply to an entirely separate page in a separate top-level browsing context.

Here is the outer HTML page:

<!DOCTYPE HTML>
<title>Shared workers: demo 3</title>
<pre id="log">Log:</pre>
<script>
  var worker = new SharedWorker('test.js');
  var log = document.getElementById('log');
  worker.port.addEventListener('message', function(e) {
    log.textContent += '\n' + e.data;
  }, false);
  worker.port.start();
  worker.port.postMessage('ping');
</script>
<br src="/img/spacer.gif"> 

  

Here is the inner HTML page:

<!DOCTYPE HTML>
<title>Shared workers: demo 3 inner frame</title>
<pre id=log>Inner log:</pre>
<script>
  var worker = new SharedWorker('test.js');
  var log = document.getElementById('log');
  worker.port.onmessage = function(e) {
   log.textContent += '\n' + e.data;
  }
</script>

Here is the JavaScript worker:

var count = 0;
onconnect = function(e) {
  count += 1;
  var port = e.ports[0];
  port.postMessage('Hello World! You are connection #' + count);
  port.onmessage = function(e) {
    port.postMessage('pong');
  }
}

View this example online.

1.2.4 Shared state using a shared worker

This section is non-normative.

In this example, multiple windows (viewers) can be opened that are all viewing the same map. All the windows share the same map information, with a single worker coordinating all the viewers. Each viewer can move around independently, but if they set any data on the map, all the viewers are updated.

The main page isn't interesting, it merely provides a way to open the viewers:

<!DOCTYPE HTML>
<html>
 <head>
  <title>Workers example: Multiviewer</title>
  <script>
   function openViewer() {
     window.open('viewer.html');
   }
  </script>
 </head>
 <body>
  <p><button type=button>

  

The viewer is more involved:

<!DOCTYPE HTML>
<html>
 <head>
  <title>Workers example: Multiviewer viewer</title>
  <script>
   var worker = new SharedWorker('worker.js', 'core');

   // CONFIGURATION
   function configure(event) {
     if (event.data.substr(0, 4) != 'cfg ') return;
     var name = event.data.substr(4).split(' ', 1)[0];
     // update display to mention our name is name
     document.getElementsByTagName('h1')[0].textContent += ' ' + name;
     // no longer need this listener
     worker.port.removeEventListener('message', configure, false);
   }
   worker.port.addEventListener('message', configure, false);

   // MAP
   function paintMap(event) {
     if (event.data.substr(0, 4) != 'map ') return;
     var data = event.data.substr(4).split(',');
     // display tiles data[0] .. data[8]
     var canvas = document.getElementById('map');
     var context = canvas.getContext('2d');
     for (var y = 0; y < 3; y += 1) {
       for (var x = 0; x < 3; x += 1) {
         var tile = data[y * 3 + x];
         if (tile == '0')
           context.fillStyle = 'green';
         else 
           context.fillStyle = 'maroon';
         context.fillRect(x * 50, y * 50, 50, 50);
       }
     }
   }
   worker.port.addEventListener('message', paintMap, false);

   // PUBLIC CHAT
   function updatePublicChat(event) {
     if (event.data.substr(0, 4) != 'txt ') return;
     var name = event.data.substr(4).split(' ', 1)[0];
     var message = event.data.substr(4 + name.length + 1);
     // display "<name> message" in public chat
     var public = document.getElementById('public');
     var p = document.createElement('p');
     var n = document.createElement('button');
     n.textContent = '<' + name + '> ';
     n.onclick = function () { worker.port.postMessage('msg ' + name); };
     p.appendChild(n);
     var m = document.createElement('span');
     m.textContent = message;
     p.appendChild(m);
     public.appendChild(p);
   }
   worker.port.addEventListener('message', updatePublicChat, false);

   // PRIVATE CHAT
   function startPrivateChat(event) {
     if (event.data.substr(0, 4) != 'msg ') return;
     var name = event.data.substr(4).split(' ', 1)[0];
     var port = event.ports[0];
     // display a private chat UI
     var ul = document.getElementById('private');
     var li = document.createElement('li');
     var h3 = document.createElement('h3');
     h3.textContent = 'Private chat with ' + name;
     li.appendChild(h3);
     var div = document.createElement('div');
     var addMessage = function(name, message) {
       var p = document.createElement('p');
       var n = document.createElement('strong');
       n.textContent = '<' + name + '> ';
       p.appendChild(n);
       var t = document.createElement('span');
       t.textContent = message;
       p.appendChild(t);
       div.appendChild(p);
     };
     port.onmessage = function (event) {
       addMessage(name, event.data);
     };
     li.appendChild(div);
     var form = document.createElement('form');
     var p = document.createElement('p');
     var input = document.createElement('input');
     input.size = 50;
     p.appendChild(input);
     p.appendChild(document.createTextNode(' '));
     var button = document.createElement('button');
     button.textContent = 'Post';
     p.appendChild(button);
     form.onsubmit = function () {
       port.postMessage(input.value);
       addMessage('me', input.value);
       input.value = '';
       return false;
     };
     form.appendChild(p);
     li.appendChild(form);
     ul.appendChild(li);
   }
   worker.port.addEventListener('message', startPrivateChat, false);

   worker.port.start();
  </script>
 </head>
 <body>
  <h1>Viewer</h1>
  <h2>Map</h2>
  <p><canvas id="map"  ></canvas></p>
  <p>
   <button type=button>

  

There are several key things worth noting about the way the viewer is written.

Multiple listeners. Instead of a single message processing function, the code here attaches multiple event listeners, each one performing a quick check to see if it is relevant for the message. In this example it doesn't make much difference, but if multiple authors wanted to collaborate using a single port to communicate with a worker, it would allow for independent code instead of changes having to all be made to a single event handling function.

Registering event listeners in this way also allows you to unregister specific listeners when you are done with them, as is done with the configure() method in this example.

Finally, the worker:

var nextName = 0;
function getNextName() {
  // this could use more friendly names
  // but for now just return a number
  return nextName++;
}

var map = [
 [0, 0, 0, 0, 0, 0, 0],
 [1, 1, 0, 1, 0, 1, 1],
 [0, 1, 0, 1, 0, 0, 0],
 [0, 1, 0, 1, 0, 1, 1],
 [0, 0, 0, 1, 0, 0, 0],
 [1, 0, 0, 1, 1, 1, 1],
 [1, 1, 0, 1, 1, 0, 1],
];

function wrapX(x) {
  if (x < 0) return wrapX(x + map[0].length);
  if (x >= map[0].length) return wrapX(x - map[0].length);
  return x;
}

function wrapY(y) {
  if (y < 0) return wrapY(y + map.length);
  if (y >= map[0].length) return wrapY(y - map.length);
  return y;
}

function wrap(val, min, max) {
  if (val < min)
    return val + (max-min)+1;
  if (val > max)
    return val - (max-min)-1;
  return val;
}

function sendMapData(viewer) {
  var data = '';
  for (var y = viewer.y-1; y <= viewer.y+1; y += 1) {
    for (var x = viewer.x-1; x <= viewer.x+1; x += 1) {
      if (data != '')
        data += ',';
      data += map[wrap(y, 0, map[0].length-1)][wrap(x, 0, map.length-1)];
    }
  }
  viewer.port.postMessage('map ' + data);
}

var viewers = {};
onconnect = function (event) {
  var name = getNextName();
  event.ports[0]._data = { port: event.ports[0], name: name, x: 0, y: 0, };
  viewers[name] = event.ports[0]._data;
  event.ports[0].postMessage('cfg ' + name);
  event.ports[0].onmessage = getMessage;
  sendMapData(event.ports[0]._data);
};

function getMessage(event) {
  switch (event.data.substr(0, 4)) {
    case 'mov ':
      var direction = event.data.substr(4);
      var dx = 0;
      var dy = 0;
      switch (direction) {
        case 'up': dy = -1; break;
        case 'down': dy = 1; break;
        case 'left': dx = -1; break;
        case 'right': dx = 1; break;
      }
      event.target._data.x = wrapX(event.target._data.x + dx);
      event.target._data.y = wrapY(event.target._data.y + dy);
      sendMapData(event.target._data);
      break;
    case 'set ':
      var value = event.data.substr(4);
      map[event.target._data.y][event.target._data.x] = value;
      for (var viewer in viewers)
        sendMapData(viewers[viewer]);
      break;
    case 'txt ':
      var name = event.target._data.name;
      var message = event.data.substr(4);
      for (var viewer in viewers)
        viewers[viewer].port.postMessage('txt ' + name + ' ' + message);
      break;
    case 'msg ':
      var party1 = event.target._data;
      var party2 = viewers[event.data.substr(4).split(' ', 1)[0]];
      if (party2) {
        var channel = new MessageChannel();
        party1.port.postMessage('msg ' + party2.name, [channel.port1]);
        party2.port.postMessage('msg ' + party1.name, [channel.port2]);
      }
      break;
  }
}

Connecting to multiple pages. The script uses the onconnect event listener to listen for multiple connections.

Direct channels. When the worker receives a "msg" message from one viewer naming another viewer, it sets up a direct connection between the two, so that the two viewers can communicate directly without the worker having to proxy all the messages.

View this example online.

1.2.5 Delegation

This section is non-normative.

With multicore CPUs becoming prevalent, one way to obtain better performance is to split computationally expensive tasks amongst multiple workers. In this example, a computationally expensive task that is to be performed for every number from 1 to 10,000,000 is farmed out to ten subworkers.

The main page is as follows, it just reports the result:

<!DOCTYPE HTML>
<html>
 <head>
  <title>Worker example: Multicore computation</title>
 </head>
 <body>
  <p>Result: <output id="result"></output></p>
  <script>
   var worker = new Worker('worker.js');
   worker.onmessage = function (event) {
     document.getElementById('result').textContent = event.data;
   };
  </script>
 </body>
</html>

The worker itself is as follows:

// settings
var num_workers = 10;
var items_per_worker = 1000000;

// start the workers
var result = 0;
var pending_workers = num_workers;
for (var i = 0; i < num_workers; i += 1) {
  var worker = new Worker('core.js');
  worker.postMessage(i * items_per_worker);
  worker.postMessage((i+1) * items_per_worker);
  worker.onmessage = storeResult;
}

// handle the results
function storeResult(event) {
  result += 1*event.data;
  pending_workers -= 1;
  if (pending_workers <= 0)
    postMessage(result); // finished!
}

It consists of a loop to start the subworkers, and then a handler that waits for all the subworkers to respond.

The subworkers are implemented as follows:

var start;
onmessage = getStart;
function getStart(event) {
  start = 1*event.data;
  onmessage = getEnd;
}

var end;
function getEnd(event) {
  end = 1*event.data;
  onmessage = null;
  work();
}

function work() {
  var result = 0;
  for (var i = start; i < end; i += 1) {
    // perform some complex calculation here
    result += 1;
  }
  postMessage(result);
  close();
}

They receive two numbers in two events, perform the computation for the range of numbers thus specified, and then report the result back to the parent.

View this example online.

1.3 Tutorials

1.3.1 Creating a dedicated worker

This section is non-normative.

Creating a worker requires a URL to a JavaScript file. The Worker() constructor is invoked with the URL to that file as its only argument; a worker is then created and returned:

var worker = new Worker('helper.js');

1.3.2 Communicating with a dedicated worker

This section is non-normative.

Dedicated workers use MessagePort objects behind the scenes, and thus support all the same features, such as sending structured data, transferring binary data, and transferring other ports.

To receive messages from a dedicated worker, use the onmessage event handler IDL attribute on the Worker object:

worker.onmessage = function (event) { ... };

You can also use the addEventListener() method.

The implicit MessagePort used by dedicated workers has its port message queue implicitly enabled when it is created, so there is no equivalent to the MessagePort interface's start() method on the Worker interface.

To send data to a worker, use the postMessage() method. Structured data can be sent over this communication channel. To send ArrayBuffer objects efficiently (by transferring them rather than cloning them), list them in an array in the second argument.

worker.postMessage({
  operat


		
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.