Edition for Web Developers — Last Updated 29 October 2025
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.selfworkerGlobal.locationWorkerLocation 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 | 
|---|---|
| onerrorSupport 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 | 
| onlanguagechangeWorkerGlobalScope/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 | 
| onofflineWorkerGlobalScope/offline_event Firefox29+Safari8+ChromeNo Opera?EdgeNo Edge (Legacy)?Internet ExplorerNo Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android? | offline | 
| ononlineWorkerGlobalScope/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 have an associated inside port (a
  MessagePort). This port is part of a channel that is set up when the worker is
  created, but it is not exposed.
dedicatedWorkerGlobal.nameReturns 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.
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 | 
|---|---|
| onconnectSharedWorkerGlobalScope/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 it for the worker's WorkerGlobalScope object.
Worker and SharedWorkerThe 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 | 
|---|---|
| onerrorSupport 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 contain the following values:
name can be used to define the name of that global environment, primarily for
     debugging purposes.
type can be used to load the new
     global environment from scriptURL as a JavaScript module, by setting it to the value
     "module".
credentials can be used to
     specify how scriptURL is fetched, but only if type is set to "module".
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 ]}); 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 contain the following values:
name can be used to define the name of that global environment.
type can be used to load the new global
     environment from scriptURL as a JavaScript module, by setting it to the value "module".
credentials can be used to specify
     how scriptURL is fetched, but only if type is set to "module".
Note that attempting to construct a shared worker with options whose type, or credentials values mismatch those
    of an existing shared worker with the same constructor URL and name, 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.