Edition for Web Developers — Last Updated 16 January 2024
Support in all current engines.
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.
There are a variety of uses that workers can be put to. The following subsections show various examples of this use.
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 lang = "en" >
< head >
< meta charset = "utf-8" >
< 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 unoptimized search for a prime number. The postMessage()
method is used to send a
message back to the page when a prime is found.
All of our examples so far show workers that run classic
scripts. Workers can instead be instantiated using module
scripts, which have the usual benefits: the ability to use the JavaScript
import
statement to import other modules; strict mode by default; and
top-level declarations not polluting the worker's global scope.
As the import
statement is available, the importScripts()
method will automatically fail
inside module workers.
In this example, the main document uses a worker to do off-main-thread image manipulation. It imports the filters used from another module.
The main page is as follows:
<!DOCTYPE html>
< html lang = "en" >
< meta charset = "utf-8" >
< title > Worker example: image decoding</ title >
< p >
< label >
Type an image URL to decode
< input type = "url" id = "image-url" list = "image-list" >
< datalist id = "image-list" >
< option value = "https://html.spec.whatwg.org/images/drawImage.png" >
< option value = "https://html.spec.whatwg.org/images/robots.jpeg" >
< option value = "https://html.spec.whatwg.org/images/arcTo2.png" >
</ datalist >
</ label >
</ p >
< p >
< label >
Choose a filter to apply
< select id = "filter" >
< option value = "none" > none</ option >
< option value = "grayscale" > grayscale</ option >
< option value = "brighten" > brighten by 20%</ option >
</ select >
</ label >
</ p >
< div id = "output" ></ div >
< script type = "module" >
const worker = new Worker( "worker.js" , { type: "module" });
worker. onmessage = receiveFromWorker;
const url = document. querySelector( "#image-url" );
const filter = document. querySelector( "#filter" );
const output = document. querySelector( "#output" );
url. oninput = updateImage;
filter. oninput = sendToWorker;
let imageData, context;
function updateImage() {
const img = new Image();
img. src = url. value;
img. onload = () => {
const canvas = document. createElement( "canvas" );
canvas. width = img. width;
canvas. height = img. height;
context = canvas. getContext( "2d" );
context. drawImage( img, 0 , 0 );
imageData = context. getImageData( 0 , 0 , canvas. width, canvas. height);
sendToWorker();
output. replaceChildren( canvas);
};
}
function sendToWorker() {
worker. postMessage({ imageData, filter: filter. value });
}
function receiveFromWorker( e) {
context. putImageData( e. data, 0 , 0 );
}
</ script >
The worker file is then:
import * as filters from "./filters.js" ;
self. onmessage = e => {
const { imageData, filter } = e. data;
filters[ filter]( imageData);
self. postMessage( imageData, [ imageData. data. buffer]);
};
Which imports the file filters.js
:
export function none() {}
export function grayscale({ data: d }) {
for ( let i = 0 ; i < d. length; i += 4 ) {
const [ r, g, b] = [ d[ i], d[ i + 1 ], d[ i + 2 ]];
// CIE luminance for the RGB
// The human eye is bad at seeing red and blue, so we de-emphasize them.
d[ i] = d[ i + 1 ] = d[ i + 2 ] = 0.2126 * r + 0.7152 * g + 0.0722 * b;
}
};
export function brighten({ data: d }) {
for ( let i = 0 ; i < d. length; ++ i) {
d[ i] *= 1.2 ;
}
};
Support in all current engines.
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>
< html lang = "en" >
< meta charset = "utf-8" >
< 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!' );
}
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>
< html lang = "en" >
< meta charset = "utf-8" >
< 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
}
}
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 iframe
on the first page, but the same
principle would apply to an entirely separate page in a separate top-level
traversable.
Here is the outer HTML page:
<!DOCTYPE HTML>
< html lang = "en" >
< meta charset = "utf-8" >
< 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 >
< iframe src = "inner.html" ></ iframe >
Here is the inner HTML page:
<!DOCTYPE HTML>
< html lang = "en" >
< meta charset = "utf-8" >
< 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' );
}
}
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 lang = "en" >
< head >
< meta charset = "utf-8" >
< title > Workers example: Multiviewer</ title >
< script >
function openViewer() {
window. open( 'viewer.html' );
}
</ script >
</ head >
< body >
< p >< button type = button onclick = "openViewer()" > Open a new
viewer</ button ></ p >
< p > Each viewer opens in a new window. You can have as many viewers
as you like, they all view the same data.</ p >
</ body >
</ html >
The viewer is more involved:
<!DOCTYPE HTML>
< html lang = "en" >
< head >
< meta charset = "utf-8" >
< 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" height = 150 width = 150 ></ canvas ></ p >
< p >
< button type = button onclick = "worker.port.postMessage('mov left')" > Left</ button >
< button type = button onclick = "worker.port.postMessage('mov up')" > Up</ button >
< button type = button onclick = "worker.port.postMessage('mov down')" > Down</ button >
< button type = button onclick = "worker.port.postMessage('mov right')" > Right</ button >
< button type = button onclick = "worker.port.postMessage('set 0')" > Set 0</ button >
< button type = button onclick = "worker.port.postMessage('set 1')" > Set 1</ button >
</ p >
< h2 > Public Chat</ h2 >
< div id = "public" ></ div >
< form onsubmit = "worker.port.postMessage('txt ' + message.value); message.value = ''; return false;" >
< p >
< input type = "text" name = "message" size = "50" >
< button > Post</ button >
</ p >
</ form >
< h2 > Private Chat</ h2 >
< ul id = "private" ></ ul >
</ body >
</ html >
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.
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 lang = "en" >
< head >
< meta charset = "utf-8" >
< 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.
Suppose that a cryptography library is made available that provides three tasks:
The library itself is as follows:
function handleMessage( e) {
if ( e. data == "genkeys" )
genkeys( e. ports[ 0 ]);
else if ( e. data == "encrypt" )
encrypt( e. ports[ 0 ]);
else if ( e. data == "decrypt" )
decrypt( e. ports[ 0 ]);
}
function genkeys( p) {
var keys = _generateKeyPair();
p. postMessage( keys[ 0 ]);
p. postMessage( keys[ 1 ]);
}
function encrypt( p) {
var key, state = 0 ;
p. onmessage = function ( e) {
if ( state == 0 ) {
key = e. data;
state = 1 ;
} else {
p. postMessage( _encrypt( key, e. data));
}
};
}
function decrypt( p) {
var key, state = 0 ;
p. onmessage = function ( e) {
if ( state == 0 ) {
key = e. data;
state = 1 ;
} else {
p. postMessage( _decrypt( key, e. data));
}
};
}
// support being used as a shared worker as well as a dedicated worker
if ( 'onmessage' in this ) // dedicated worker
onmessage = handleMessage;
else // shared worker
onconnect = function ( e) { e. port. onmessage = handleMessage; }
// the "crypto" functions:
function _generateKeyPair() {
return [ Math. random(), Math. random()];
}
function _encrypt( k, s) {
return 'encrypted-' + k + ' ' + s;
}
function _decrypt( k, s) {
return s. substr( s. indexOf( ' ' ) + 1 );
}
Note that the crypto functions here are just stubs and don't do real cryptography.
This library could be used as follows:
<!DOCTYPE HTML>
< html lang = "en" >
< head >
< meta charset = "utf-8" >
< title > Worker example: Crypto library</ title >
< script >
const cryptoLib = new Worker( 'libcrypto-v1.js' ); // or could use 'libcrypto-v2.js'
function startConversation( source, message) {
const messageChannel = new MessageChannel();
source. postMessage( message, [ messageChannel. port2]);
return messageChannel. port1;
}
function getKeys() {
let state = 0 ;
startConversation( cryptoLib, "genkeys" ). onmessage = function ( e) {
if ( state === 0 )
document. getElementById( 'public' ). value = e. data;
else if ( state === 1 )
document. getElementById( 'private' ). value = e. data;
state += 1 ;
};
}
function enc() {
const port = startConversation( cryptoLib, "encrypt" );
port. postMessage( document. getElementById( 'public' ). value);
port. postMessage( document. getElementById( 'input' ). value);
port. onmessage = function ( e) {
document. getElementById( 'input' ). value = e. data;
port. close();
};
}
function dec() {
const port = startConversation( cryptoLib, "decrypt" );
port. postMessage( document. getElementById( 'private' ). value);
port. postMessage( document. getElementById( 'input' ). value);
port. onmessage = function ( e) {
document. getElementById( 'input' ). value = e. data;
port. close();
};
}
</ script >
< style >
textarea { display : block ; }
</ style >
</ head >
< body onload = "getKeys()" >
< fieldset >
< legend > Keys</ legend >
< p >< label > Public Key: < textarea id = "public" ></ textarea ></ label ></ p >
< p >< label > Private Key: < textarea id = "private" ></ textarea ></ label ></ p >
</ fieldset >
< p >< label > Input: < textarea id = "input" ></ textarea ></ label ></ p >
< p >< button onclick = "enc()" > Encrypt</ button > < button onclick = "dec()" > Decrypt</ button ></ p >
</ body >
</ html >
A later version of the API, though, might want to offload all the crypto work onto subworkers. This could be done as follows:
function handleMessage( e) {
if ( e. data == "genkeys" )
genkeys( e. ports[ 0 ]);
else if ( e. data == "encrypt" )
encrypt( e. ports[ 0 ]);
else if ( e. data == "decrypt" )
decrypt( e. ports[ 0 ]);
}
function genkeys( p) {
var generator = new Worker( 'libcrypto-v2-generator.js' );
generator. postMessage( '' , [ p]);
}
function encrypt( p) {
p. onmessage = function ( e) {
var key = e. data;
var encryptor = new Worker( 'libcrypto-v2-encryptor.js' );
encryptor. postMessage( key, [ p]);
};
}
function encrypt( p) {
p. onmessage = function ( e) {
var key = e. data;
var decryptor = new Worker( 'libcrypto-v2-decryptor.js' );
decryptor. postMessage( key, [ p]);
};
}
// support being used as a shared worker as well as a dedicated worker
if ( 'onmessage' in this ) // dedicated worker
onmessage = handleMessage;
else // shared worker
onconnect = function ( e) { e. ports[ 0 ]. onmessage = handleMessage };
The little subworkers would then be as follows.
For generating key pairs:
onmessage = function ( e) {
var k = _generateKeyPair();
e. ports[ 0 ]. postMessage( k[ 0 ]);
e. ports[ 0 ]. postMessage( k[ 1 ]);
close();
}
function _generateKeyPair() {
return [ Math. random(), Math. random()];
}
For encrypting:
onmessage = function ( e) {
var key = e. data;
e. ports[ 0 ]. onmessage = function ( e) {
var s = e. data;
postMessage( _encrypt( key, s));
}
}
function _encrypt( k, s) {
return 'encrypted-' + k + ' ' + s;
}
For decrypting:
onmessage = function ( e) {
var key = e. data;
e. ports[ 0 ]. onmessage = function ( e) {
var s = e. data;
postMessage( _decrypt( key, s));
}
}
function _decrypt( k, s) {
return s. substr( s. indexOf( ' ' ) + 1 );
}
Notice how the users of the API don't have to even know that this is happening — the API hasn't changed; the library can delegate to subworkers without changing its API, even though it is accepting data using message channels.
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' );
If you want your worker script to be interpreted as a module script instead of the default classic script, you need to use a slightly different signature:
var worker = new Worker( 'helper.mjs' , { type: "module" });
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({
operation: 'find-edges' ,
input: buffer, // an ArrayBuffer object
threshold: 0.6 ,
}, [ buffer]);
To receive a message inside the worker, the onmessage
event handler IDL attribute is used.
onmessage = function ( event) { ... };
You can again also use the addEventListener()
method.
In either case, the data is provided in the event object's data
attribute.
To send messages back, you again use postMessage()
. It supports the
structured data in the same manner.
postMessage( event. data. input, [ event. data. input]); // transfer the buffer back
Support in all current engines.
Shared workers are identified by the URL of the script used to create it, optionally with an explicit name. The name allows multiple instances of a particular shared worker to be started.
Shared workers are scoped by origin. Two different sites using the same names will not collide. However, if a page tries to use the same shared worker name as another page on the same site, but with a different script URL, it will fail.
Creating shared workers is done using the SharedWorker()
constructor. This constructor takes the URL to the script to use for its first argument, and the
name of the worker, if any, as the second argument.
var worker = new SharedWorker( 'service.js' );
Communicating with shared workers is done with explicit MessagePort
objects. The
object returned by the SharedWorker()
constructor holds a
reference to the port on its port
attribute.
worker. port. onmessage = function ( event) { ... };
worker. port. postMessage( 'some message' );
worker. port. postMessage({ foo: 'structured' , bar: [ 'data' , 'also' , 'possible' ]});
Inside the shared worker, new clients of the worker are announced using the connect
event. The port for the new client is
given by the event object's source
attribute.
onconnect = function ( event) {
var newPort = event. source;
// set up a listener
newPort. onmessage = function ( event) { ... };
// send a message back to the port
newPort. postMessage( 'ready!' ); // can also send structured data, of course
};
This standard defines two kinds of workers: dedicated workers, and shared workers. Dedicated workers, once created, are linked to their creator, but message ports can be used to communicate from a dedicated worker to multiple other browsing contexts or workers. Shared workers, on the other hand, are named, and once created any script running in the same origin can obtain a reference to that worker and communicate with it. Service Workers defines a third kind. [SW]
The global scope is the "inside" of a worker.
WorkerGlobalScope
common interfaceSupport in all current engines.
WorkerGlobalScope
serves as the base class for specific types of worker global
scope objects, including DedicatedWorkerGlobalScope
,
SharedWorkerGlobalScope
, and ServiceWorkerGlobalScope
.
workerGlobal.self
workerGlobal.location
WorkerLocation
object.workerGlobal.
WorkerNavigator
object.workerGlobal.importScripts(...urls)
The following are the event handlers (and their corresponding event handler event types) supported,
as event handler IDL attributes, by objects implementing the
WorkerGlobalScope
interface:
Event handler | Event handler event type |
---|---|
onerror Support in all current engines. Firefox3.5+Safari4+Chrome4+ Opera11.5+Edge79+ Edge (Legacy)12+Internet Explorer10+ Firefox Android?Safari iOS5+Chrome Android?WebView Android?Samsung Internet?Opera Android? | error
|
onlanguagechange WorkerGlobalScope/languagechange_event Support in all current engines. Firefox74+Safari4+Chrome4+ Opera11.5+Edge79+ Edge (Legacy)?Internet ExplorerNo Firefox Android?Safari iOS5+Chrome Android?WebView Android37+Samsung Internet?Opera Android? | languagechange
|
onoffline WorkerGlobalScope/offline_event Firefox29+Safari8+ChromeNo Opera?EdgeNo Edge (Legacy)?Internet ExplorerNo Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android? | offline
|
ononline WorkerGlobalScope/online_event Firefox29+Safari8+ChromeNo Opera?EdgeNo Edge (Legacy)?Internet ExplorerNo Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android? | online
|
onrejectionhandled | rejectionhandled
|
onunhandledrejection | unhandledrejection
|
DedicatedWorkerGlobalScope
interfaceSupport in all current engines.
DedicatedWorkerGlobalScope
objects act as if they had an implicit
MessagePort
associated with them. This port is part of a channel that is set up when
the worker is created, but it is not exposed.
dedicatedWorkerGlobal.name
Returns dedicatedWorkerGlobal's name, i.e. the value given to the
Worker
constructor. Primarily useful for debugging.
dedicatedWorkerGlobal.postMessage(message [, transfer ])
dedicatedWorkerGlobal.postMessage(message [, { transfer } ])
Clones message and transmits it to the Worker
object associated
with dedicatedWorkerGlobal. transfer can be passed as a list of objects
that are to be transferred rather than cloned.
dedicatedWorkerGlobal.close()
Aborts dedicatedWorkerGlobal.
The following are the event handlers (and their corresponding event handler event types) supported,
as event handler IDL attributes, by objects implementing the
DedicatedWorkerGlobalScope
interface:
Event handler | Event handler event type |
---|---|
onmessage DedicatedWorkerGlobalScope/message_event Support in all current engines. Firefox3.5+Safari4+Chrome4+ Opera10.6+Edge79+ Edge (Legacy)12+Internet Explorer10+ Firefox Android?Safari iOS5+Chrome Android?WebView Android37+Samsung Internet?Opera Android11.5+ | message
|
onmessageerror DedicatedWorkerGlobalScope/messageerror_event Support in all current engines. Firefox57+Safari16.4+Chrome60+ Opera?Edge79+ Edge (Legacy)18Internet ExplorerNo Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android47+ | messageerror
|
SharedWorkerGlobalScope
interfaceSupport in all current engines.
Shared workers receive message ports through connect
events on their SharedWorkerGlobalScope
object for each
connection.
sharedWorkerGlobal.
Returns sharedWorkerGlobal's name, i.e. the value given to the
SharedWorker
constructor. Multiple SharedWorker
objects can correspond
to the same shared worker (and SharedWorkerGlobalScope
), by reusing the same
name.
sharedWorkerGlobal.
()Aborts sharedWorkerGlobal.
The following are the event handlers (and their corresponding event handler event types) supported,
as event handler IDL attributes, by objects implementing the
SharedWorkerGlobalScope
interface:
Event handler | Event handler event type |
---|---|
onconnect SharedWorkerGlobalScope/connect_event Support in all current engines. Firefox29+Safari16+Chrome4+ Opera10.6+Edge79+ Edge (Legacy)?Internet ExplorerNo Firefox Android?Safari iOS16+Chrome Android?WebView Android37+Samsung Internet?Opera Android11+ | connect
|
A worker event loop's task queues only have events, callbacks, and networking activity as tasks.
Each WorkerGlobalScope
object has a closing flag, initially
false, but which can get set to true when the worker is requested to close.
Once the WorkerGlobalScope
's closing flag is set to true, the event
loop's task queues discard any
further tasks that would be added to them (tasks already on the
queue are unaffected except where otherwise specified). Effectively, once the closing flag is true, timers stop firing,
notifications for all pending background operations are dropped, etc.
Whenever an uncaught runtime script error occurs in one of the worker's scripts, if the error
did not occur while handling a previous script error, the user agent
will report the error for that script, with the position (line number and column number) where the
error occurred, using the WorkerGlobalScope
object as the target.
For shared workers, if the error is still not handled afterwards, the error may be reported to a developer console.
For dedicated workers, if the error is still not
handled afterwards, the error report propagates to the Worker
object,
then to any containing workers in the same fashion, eventually reaching the Window
if
it was never handled along the way.
Error reports propagate up to
the chain of dedicated workers up to the original Document
, even if some of the
workers along this chain have been terminated and garbage collected.
Worker
and SharedWorker
The following are the event handlers (and their corresponding event handler event types) supported,
as event handler IDL attributes, by Worker
and
SharedWorker
objects:
Event handler | Event handler event type |
---|---|
onerror Support in all current engines. Firefox44+Safari11.1+Chrome40+ Opera?Edge79+ Edge (Legacy)17+Internet ExplorerNo Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android? Support in all current engines. Firefox29+Safari16+Chrome5+ Opera10.6+Edge79+ Edge (Legacy)?Internet ExplorerNo Firefox Android33+Safari iOS16+Chrome AndroidNoWebView Android?Samsung Internet4.0–5.0Opera Android11–14 Support in all current engines. Firefox3.5+Safari4+Chrome4+ Opera10.6+Edge79+ Edge (Legacy)12+Internet Explorer10+ Firefox Android?Safari iOS5+Chrome Android?WebView Android?Samsung Internet?Opera Android11+ | error
|
Worker
interfaceSupport in all current engines.
worker = new Worker(scriptURL [, options ])
Returns a new Worker
object. scriptURL will be fetched and
executed in the background, creating a new global environment for which worker
represents the communication channel. options can be used to define the name of that global environment via the name
option, primarily for debugging purposes. It can also ensure this new
global environment supports JavaScript modules (specify type: "module"
),
and if that is specified, can also be used to specify how scriptURL is fetched through
the credentials
option.
worker.terminate()
worker.postMessage(message [, transfer ])
worker.postMessage(message [, { transfer } ])
Clones message and transmits it to worker's global environment. transfer can be passed as a list of objects that are to be transferred rather than cloned.
The postMessage()
method's first argument can be structured data:
worker. postMessage({ opcode: 'activate' , device: 1938 , parameters: [ 23 , 102 ]});
The following are the event handlers (and their corresponding event handler event types) supported,
as event handler IDL attributes, by objects implementing the Worker
interface:
Event handler | Event handler event type |
---|---|
onmessage | message
|
onmessageerror | messageerror
|
SharedWorker
interfaceSupport in all current engines.
sharedWorker = new
(scriptURL [, name ])Returns a new SharedWorker
object. scriptURL will be fetched and
executed in the background, creating a new global environment for which sharedWorker
represents the communication channel. name can be used to define the name of that global environment.
sharedWorker = new SharedWorker(scriptURL [, options ])
Returns a new SharedWorker
object. scriptURL will be fetched and
executed in the background, creating a new global environment for which sharedWorker
represents the communication channel. options can be used to define the name of that global environment via the name
option. It can also ensure this new global environment supports JavaScript
modules (specify type: "module"
), and if that is specified, can also be
used to specify how scriptURL is fetched through the credentials
option. Note that attempting to construct a shared worker with
options whose type
or credentials
values
mismatch an existing shared worker will cause the returned sharedWorker to fire an
error event and not connect to the existing shared worker.
sharedWorker.
Returns sharedWorker's MessagePort
object which can be used to
communicate with the global environment.
self.navigator.
Returns the number of logical processors potentially available to the user agent.
WorkerNavigator
interfaceSupport in all current engines.
The WorkerNavigator
interface implements a subset of the Navigator
interface, consisting of the following APIs:
WorkerLocation
interfaceSupport in all current engines.
Support in all current engines.
The WorkerLocation
interface is like the Location
interface,
but lacks the assign()
, replace()
, reload()
,
and ancestorOrigins
members.