1. Overview

JavaScript uses the single-threaded model, which means that all tasks can only be done on one thread, and only one thing at a time.The latter tasks can only be waiting if the previous tasks have not been finished. With the increase of computing power, and especially the emergence of multi-core CPUs, single-threading brings great inconvenience and makes it impossible to fully utilize the computing power of computers.

The role of Web Workers is to create a multi-threaded environment for JavaScript, allowing the main thread to create worker threads and assigning some tasks to the latter. While the main thread is running, the Worker thread runs in the background, and they don't interfere with each other. The result will be returned to the main thread after the Worker thread has completed the calculation task. The advantage of this is that some computationally intensive or high-latency tasks are burdened by the Worker thread, and the main thread (usually responsible for UI interaction) will become smooth and won't be blocked or slowed down.

Once the Worker thread is created successfully, it will always run and won't be interrupted by activities on the main thread (such as the user clicking the button and submitting the form). It's good for responding to the communication of the main thread at any time. However, it also causes Workers to be more resource intensive, so it shouldn't be overused, and should be closed once they are used.

There are a few important points when using the Web Worker.

(1) homology restriction

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

(2) DOM restriction

The Worker thread's global object is different from the main thread's. The former can't read the DOM objects of the web page where the main thread is located, nor can it use the objects of document , window , and parent . However, the Worker thread can use the objects of navigator and location .

(3) Communication

Worker thread and main thread are not in the same context, so they can't communicate directly and must be done through messages.

(4) Script restriction

Worker thread can't execute the alert() method and the confirm() method, but can use the XMLHttpRequest object to make AJAX requests.

(5) File restriction

Worker thread cannot read the native file, that is, it can't open the native file system ( file:// ) . The script loaded must come from the network.

2. Basic Usage

2.1 main thread

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

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

The argument of the Worker() constructor is a script file, which is the task that the Worker thread is to perform. Since Worker can't read the local file, the script must come from the network. If the download is not successful (such as a 404 error), the Worker will fail silently.

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']});

The argument of 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 data.

Then, the main thread specifies the listener function through worker.onmessage , and receives the message sent back by the child thread.

worker.onmessage = function (event) { console.log('Received message ' + event.data); doSomething(); } function doSomething() { // doing task worker.postMessage('Work done!'); }

In the above code, the data property of the event object can get the data sent by the Worker.

After the Worker completes the task, the main thread turns it off.

worker.terminate();

2.2 Worker thread

A listener function is needed to listen for the message event inside the Worker thread.

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

In the above code, self represents the child thread itself, which is the global object of the child thread. Therefore, the following two ways of coding are equivalent.

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

In addition to using the self.addEventListener() to specify the listener function, you can also use self.onmessage to do so. The argument of the listener function is 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.

Worker thread will call different methods according to 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);

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

2.3 Load script

If you want to load other scripts inside the Worker, there is a method called importScripts() .

importScripts('script1.js');

The method can load multiple scripts at the same time.

importScripts('script1.js', 'script2.js');

2.4 error handling

The main thread can listen to the Worker for errors. If an error occurs, the Worker will trigger the error event of 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) { // ... });

Workers can also listen for error events internally.

2.5 Close Worker

After use, you must close the Worker to save system resources.

// main thread worker.terminate(); // Worker thread self.close();

3. Data Communication

As mentioned earlier, the communication between the main thread and the Worker can be either text or an object. It should be noted that the communication here is a copy data--we pass the value instead of the address, and the modification of the communication content by the Worker won't affect the main thread. In fact, the internal mechanism of the browser is to serialize the communication content, and then send the serialized string to the Worker, which then restores it.

Binary data, such as File, Blob, ArrayBuffer, etc., can also be exchanged between the main thread and the Worker, and it can also be sent between threads. Here is 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); };

However, copying binary data may cause performance problems. For example, the main thread sends a 500MB file to the Worker. The browser will generate a copy of the original file by default. To solve this problem, JavaScript allows the main thread to transfer binary data to the child thread directly. But once it has been transferred, in order to prevent multiple threads modifying the data at the same time, the main thread can no longer use the binary data. This method of transferring data is called Transferable Objects. It makes the main thread hand over data to the Worker quickly, which is very convenient for image processing, sound processing, 3D operations, etc., without a performance burden.

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

// Transferable Objects worker.postMessage(arrayBuffer, [arrayBuffer]); // example var ab = new ArrayBuffer(1); worker.postMessage(ab, [ab]);

4. Web Worker in The Same Page

Generally, Worker loads a separate JavaScript script file, but it can also load code that is on the same page as the main thread.

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

This is a script embedded in the web page. Note that the type property of the <script> tag must be specified as a value that the browser doesn't recognize. And the value in the above example is app/worker .

Then use Worker to read the embedded script.

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' };

In the above code, we convert the embedded script into a binary object first, then generate a URL for the binary object, and then let the Worker load the data from the URL. After that, the main thread's and the Worker's code are on the same page.

5. Example: Worker Thread Polling

Sometimes the browser needs to poll the server status to get to know the status change at the very first time. The 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');

In the above code, Worker polls the data every second and then compares it with the cache. If they are inconsistent, it means that there happens a new change in the server, so it is necessary to notify the main thread.

6. Example: Create a New Worker in The Worker

A new Worker thread can be created inside the Worker thread (currently only supported by the Firefox browser). We'll assign a computationally intensive task to 10 Workers in the following example.

The main thread code is as follows.

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

And the Worker thread code is as follows.

// 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! }

In the above code, 10 Worker threads are created inside the Worker thread, and messages are sent to the 10 Workers in turn telling the start and end points of the 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(); }

7. API

7.1 main thread

Natively, the browser provides a Worker() constructor for the main thread to generate a Worker thread.

var myWorker = new Worker(jsUrl, options);

The Worker() constructor takes two arguments. The first parameter is the URL of the script (must follow the same-origin policy). This parameter is required, and only JS scripts can be loaded, otherwise an error will be thrown. The second parameter is the configuration object, which is optional. One of its jobs 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

The Worker() constructor returns a Worker thread object that is used by the main thread to operate the Worker. The properties and methods of the Worker thread object are as follows.

Worker.onerror: Specify the listener function for the error event.

Worker.onmessage: Specify the listener function for the message event. The data sent is in the Event.data property.

Worker.onmessageerror: Specify the listener function for the messageerror event. The event will be triggered when the sent data can't be serialized into a string.

Worker.postMessage(): Send a message to the Worker thread.

Worker.terminate(): Terminate the Worker thread immediately.

7.2 Worker thread

Web Worker has its own global objects. It's not the main thread's window , but a global object that is specifically tailored for Workers. Therefore, not all objects and methods defined in the window can be used.

Worker thread has some of its own global properties and methods.