An overview,

Too many Web workers consume resources

JavaScript is single-threaded, which means that all tasks can only be done on one thread, one thing at a time. The first task is not finished, the next task has to wait. With the enhancement of computer computing ability, especially the emergence of multi-core CPU, single thread brings great inconvenience and can not give full play to the computing ability of the computer.

In the front-end browser, the JavaScript main thread shares the same thread as UI rendering. UI rendering stagnates when JavaScript is executed, and JavaScript stagnates when rendering UI, blocking each other. In order to solve this problem, HTML5 proposes WebWorker API.

The function of Web Worker is to create a multithreaded environment for JavaScript, allowing the main thread to create Worker threads and assign some tasks to the latter to run. While the main thread is running, the Worker thread is running in the background without interfering with each other. Wait until the Worker thread completes the calculation and returns the result to the main thread. The advantage of this is that when computationally intensive or high-latency tasks are taken on by Worker threads, the main thread (usually responsible for UI interactions) will flow smoothly and will not be blocked or slowed down. In addition, they can perform I/O using XMLHttpRequest (although the responseXML and Channel properties are always empty).

Once a Worker thread is created, it is always running and is not interrupted by activity on the main thread, such as a user clicking a button or submitting a form. This facilitates communication that responds to the main thread at any time. However, this also causes the Worker to consume resources, so it should not be overused and should be closed once it is used.

There are several considerations for using Web workers.

  • The same-origin restrictions

The script file assigned to the Worker thread must be of the same origin as the script file of the main thread.

  • DOM limit

Unlike the main thread, the global object where the Worker thread is located cannot read the DOM object of the page where the main thread is located, nor can document, window, parent objects be used. However, Worker threads can have navigator objects and Location objects.

  • A correspondence

The Worker thread and the main thread are not in the same context; they cannot communicate directly and must be done via messages.

  • Script limit

Worker threads cannot execute the Alert () and Confirm () methods, but can make AJAX requests using the XMLHttpRequest object.

  • File limit

The Worker thread cannot read local files, that is, cannot open the native file system (file://), and the scripts it loads must come from the network.

Two, basic usage

The main thread

The main thread uses the new command to call the Worker() constructor to create a new Worker thread.

var worker = new Worker('work.js');
Copy the code

The argument to the Worker() constructor is a script file that is the task to be performed by the Worker thread. Since the Worker cannot read local files, the script must come from the network. If the download does not succeed (such as a 404 error), the Worker silently fails.

The main thread then calls the worker.postMessage() method to send a message to the worker.

worker.postMessage('Hello World');
worker.postMessage({method: 'echo'.args: ['Work']});
Copy the code

The argument to the worker.postmessage () method is the data that the main thread passes to the worker. It can be a variety of data types, including binary.

The main thread then specifies a listener through worker. onMessage to receive the message from the child thread.

worker.onmessage = function (event) {
  console.log('Received message ' + event.data);
  doSomething();
}

function doSomething() {
  // Execute the task
  worker.postMessage('Work done! ');
}
Copy the code

In the above code, the data attribute of the event object can obtain the data sent by the Worker.

After the Worker completes the task, the main thread can close it.

worker.terminate();
Copy the code

Worker thread

Worker threads need to have a listener function inside that listens for message events.

self.addEventListener('message'.function (e) {
  self.postMessage('You said: ' + e.data);
}, false);
Copy the code

In the code above, self represents the child thread itself, the global object of the child thread. So, it’s the same thing as the following two ways.

/ / write one
this.addEventListener('message'.function (e) {
  this.postMessage('You said: ' + e.data);
}, false);

/ / write two
addEventListener('message'.function (e) {
  postMessage('You said: ' + e.data);
}, false);
Copy the code

In addition to specifying the listener using self.addeventListener (), you can also specify the listener using self.onMessage. The listener takes an event object whose data property contains the data sent by the main thread. The self.postMessage() method is used to send messages to the main thread.

The Worker thread can call different methods depending on the data sent by the main thread. Here is an example.

self.addEventListener('message'.function (e) {
  var data = e.data;
  switch (data.cmd) {
    case 'start':
      self.postMessage('WORKER STARTED: ' + data.msg);
      break;
    case 'stop':
      self.postMessage('WORKER STOPPED: ' + data.msg);
      self.close(); // Terminates the worker.
      break;
    default:
      self.postMessage('Unknown command: ' + data.msg);
  };
}, false);
Copy the code

In the above code, self.close() is used to close itself inside the Worker.

Worker loading script

There is a special method importScripts() inside the Worker to load other scripts.

importScripts('script1.js');
Copy the code

This method can load multiple scripts at the same time.

importScripts('script1.js'.'script2.js');
Copy the code

Error handling

The main thread can listen for Worker errors. If an error occurs, the Worker fires an error event for the main thread.

worker.onerror(function (event) {
  console.log([
    'ERROR: Line ', e.lineno, ' in ', e.filename, ':', e.message
  ].join(' '));
});

/ / or
worker.addEventListener('error'.function (event) {
  // ...
});
Copy the code

The Worker can also listen for error events internally.

Close the Worker

After use, the Worker must be shut down to save system resources.

/ / main thread
worker.terminate();

/ / Worker thread
self.close();
Copy the code

Data communication

As mentioned above, the communication content between the main thread and the Worker can be either text or object. It should be noted that this communication is a copy relationship, that is, value transmission rather than address transmission. Worker’s modification of communication content will not affect the main thread. In fact, the internal operation mechanism of the browser is to serialize the communication content first, and then send the serialized string to the Worker, who then restores it.

Binary data can also be exchanged between the main thread and the Worker, such as File, Blob, ArrayBuffer, etc., and can also be sent between threads. Here’s an example.

/ / main thread
var uInt8Array = new Uint8Array(new ArrayBuffer(10));
for (var i = 0; i < uInt8Array.length; ++i) {
  uInt8Array[i] = i * 2; // [0, 2, 4, 6, 8...]
}
worker.postMessage(uInt8Array);

/ / Worker thread
self.onmessage = function (e) {
  var uInt8Array = e.data;
  postMessage('Inside worker.js: uInt8Array.toString() = ' + uInt8Array.toString());
  postMessage('Inside worker.js: uInt8Array.byteLength = ' + uInt8Array.byteLength);
};
Copy the code

However, sending binary data in copy mode can cause performance problems. For example, if the main thread sends a 500MB file to the Worker, by default the browser will generate a copy of the original file. To solve this problem, JavaScript allows the main thread to transfer binary data directly to child threads, but once it is transferred, the main thread can no longer use the binary data, to prevent the troublesome situation of multiple threads modifying the data at the same time. This method of transferring data is called Transferable Objects. This enables the host thread to quickly deliver data to the Worker, which is very convenient for image processing, sound processing, 3D computing, etc., without performance burden.

If you want to transfer control of the data directly, use the following notation.

// Transferable Objects format
worker.postMessage(arrayBuffer, [arrayBuffer]);

/ / case
var ab = new ArrayBuffer(1);
worker.postMessage(ab, [ab]);
Copy the code

Using Channel Messaging API

Instead of using the built-in postMessage API provided by the Web Worker, we can choose to use the more generic Channel Messaging API to communicate with the Web Worker.

/ / main thread
const worker = new Worker('worker.js')
const messageChannel = new MessageChannel()
messageChannel.port1.addEventListener('message'.event= > {
  console.log(event.data)
})
worker.postMessage(data, [messageChannel.port2])

/ / Woker threads
addEventListener('message'.event= > {
  console.log(event.data);
  
  // Return data
  event.ports[0].postMessage(data);
})
Copy the code

Iv. Web workers on the same page

Typically, workers load a separate JavaScript script file, but they can also load code on the same page as the main thread.

<! DOCTYPEhtml>
  <body>
    <script id="worker" type="app/worker">
      addEventListener('message'.function () {
        postMessage('some message');
      }, false);
    </script>
  </body>
</html>
Copy the code

Note that the type attribute of the

Then, read the script embedded in the page and use the Worker to process it.

var blob = new Blob([document.querySelector('#worker').textContent]);
var url = window.URL.createObjectURL(blob);
var worker = new Worker(url);

worker.onmessage = function (e) {
  // e.data === 'some message'
};
Copy the code

In the above code, the script code embedded in the webpage is first converted into a binary object, and then the URL is generated for the binary object, and then the Worker loads the URL. This is done, the main thread and the Worker code are on the same page.

Example: The Worker thread completes the polling

Sometimes, the browser needs to poll the server state to be the first to know about a state change. This job can be placed in the Worker.

function createWorker(f) {
  var blob = new Blob(['(' + f.toString() +') () ']);
  var url = window.URL.createObjectURL(blob);
  var worker = new Worker(url);
  return worker;
}

var pollingWorker = createWorker(function (e) {
  var cache;

  function compare(new, old) {... };setInterval(function () {
    fetch('/my-api-endpoint').then(function (res) {
      var data = res.json();

      if(! compare(data, cache)) { cache = data; self.postMessage(data); }})},1000)}); pollingWorker.onmessage =function () {
  // render data
}

pollingWorker.postMessage('init');
Copy the code

In the code above, the Worker polls the data every second and compares it to the cache. If it is inconsistent, a new change has occurred on the server and the main thread is notified.

Create a Worker

You can also create Worker threads within Worker threads (currently only supported by Firefox). The following example is to allocate a computationally intensive task to 10 workers.

The main thread code is as follows.

var worker = new Worker('worker.js');
worker.onmessage = function (event) {
  document.getElementById('result').textContent = event.data;
};
Copy the code

The Worker thread code is shown below.

// worker.js

// 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 += event.data;
  pending_workers -= 1;
  if (pending_workers <= 0)
    postMessage(result); // finished!
}
Copy the code

In the above code, 10 Worker threads are created inside the Worker thread, and messages are sent to these 10 workers in turn to inform the starting point and end point of calculation. The code for the calculation task script is as follows.

// core.js
var start;
onmessage = getStart;
function getStart(event) {
  start = event.data;
  onmessage = getEnd;
}

var end;
function getEnd(event) {
  end = 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();
}
Copy the code

Seven, API

The main thread

Browsers natively provide the Worker() constructor, which is used by the main thread to generate Worker threads.

var myWorker = new Worker(jsUrl, options);
Copy the code

The Worker() constructor, which takes two arguments. The first parameter is the url of the script (it must comply with the same origin policy), which is required and can only load JS scripts, otherwise an error will be reported. The second parameter is the configuration object, which is optional. One of its functions is to specify the name of the Worker to distinguish between multiple Worker threads.

/ / main thread
var myWorker = new Worker('worker.js', { name : 'myWorker' });

/ / Worker thread
self.name // myWorker
Copy the code

The Worker() constructor returns a Worker thread object that is used by the main thread to manipulate the Worker. The Worker thread object has the following properties and methods.

  • Worker.onerror: Specifies a listener for error events.
  • Worker. onMessage: Specifies the listener function for the message Event. The sent data is in the Event.
  • Worker. onMessageError: Specifies a listener function for messageError events. This event is emitted when the sent data cannot be serialized into a string.
  • Worker.postmessage () : sends a message to the Worker thread.
  • Worker.terminate() : Terminates the Worker thread immediately.

Worker thread

The Web Worker has its own global object, not the window of the main thread, but a global object customized for the Worker. Therefore, not all objects and methods defined on the window can be used.

Worker threads have some global properties and methods of their own.

  • Self. name: The Worker’s name. This property is read-only and is specified by the constructor.
  • Self. onMessage: Specifies a listener for the message event.
  • Self. onMessageError: Specifies a listener for messageError events. This event is emitted when the sent data cannot be serialized into a string.
  • Self.close () : Closes the Worker thread.
  • Self.postmessage () : Sends a message to the thread that generated the Worker.
  • Self.importscripts () : loads the JS script.

Web workers can share data, please refer to JavaScript processing binary ArrayBuffer

reference

  1. Web Worker Tutorial — Ruan Yifeng
  2. Web Workers