Web Worker usage tutorial
I. Overview
The JavaScript language uses a single-threaded model, that is, all tasks can only be completed on one thread, and only one thing can be done at a time. The previous tasks have not been completed, and the following tasks can only be waited for. With the increase in computing power of computers, especially the emergence of multi-core CPUs, single threading brings great inconvenience and cannot fully utilize the computing power of computers.
The role of Web Worker is to create a multi-threaded 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 runs in the background, and the two do not interfere with each other. Wait until the Worker thread completes the calculation task, and then return the result to the main thread. 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 be smooth and will not be blocked or slowed down.
Once the Worker thread is successfully created, it will always run and will not be interrupted by activities on the main thread (such as user clicking buttons and submitting forms). This is helpful for responding to the communication of the main thread at any time. However, this also causes the Worker to consume more resources and should not be overused, and once it is used up, it should be closed.
Web Worker has the following points of attention.
(1) Homologous restriction
The script file assigned to the Worker thread to run must be of the same source as the script file of the main thread.
(2) DOM restrictions
The global object where the Worker thread is located is different from the main thread. The DOM objects of the webpage where the main thread is located cannot be read
document
,
window
and
parent
these objects
cannot be used
.
However, Worker threads can
navigator
objects and
location
objects.
(3) Correspondence
Worker thread and main thread are not in the same context, they cannot communicate directly, and must be done through messages.
(4) Script restrictions
The Worker thread cannot execute
alert()
methods and
confirm()
methods, but can use the XMLHttpRequest object to make AJAX requests.
(5) Document restrictions
The Worker thread cannot read local files, that is, it cannot open the file system (
file://
) of
the machine.
The script it loads must come from the network.
2. Basic usage
2.1 Main thread
The main thread uses
new
commands, calls the
Worker()
constructor, and creates a new Worker thread.
var worker = new Worker('work.js');
Worker()
The parameter of the constructor is a script file, which is the task to be performed by the Worker thread.
Since Worker cannot read local files, this script must come from the network.
If the download is not successful (such as a 404 error), the Worker will silently fail.
Then, the main thread calls the
worker.postMessage()
method to send a message to the Worker.
worker.postMessage('Hello World'); worker.postMessage({method: 'echo', args: ['Work']});
worker.postMessage()
The parameter of the method is the data passed to the Worker by the main thread.
It can be of various data types, including binary data.
Then, the main thread
worker.onmessage
receives the message sent back by the child
thread by
specifying the monitoring function.
worker.onmessage = function (event) { console.log('Received message ' + event.data); doSomething(); } function doSomething() { // 执行任务 worker.postMessage('Work done!'); }
In the above code, the
data
properties of the
event object
can get the data sent by the Worker.
After the Worker completes the task, the main thread can turn it off.
worker.terminate();
2.2 Worker thread
There needs to be a listener function inside the Worker thread to listen for
message
events.
self.addEventListener('message', function (e) { self.postMessage('You said: ' + e.data); }, false);
In the above code, it
self
represents the child thread itself, that is, the global object of the child thread.
Therefore, it is equivalent to the following two ways of writing.
// 写法一 this.addEventListener('message', function (e) { this.postMessage('You said: ' + e.data); }, false); // 写法二 addEventListener('message', function (e) { postMessage('You said: ' + e.data); }, false);
In addition to using
self.addEventListener()
designated listening functions, you can also use
self.onmessage
designated.
The parameter of the listener function is an event object, and its
data
attributes contain the data sent by the main thread.
self.postMessage()
Methods are used to send messages to the main thread.
According to the data sent by the main thread, the Worker thread can call different methods. The following 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, it is
self.close()
used to close itself inside the Worker.
2.3 Worker loading script
If you want to load other scripts inside the Worker, there is a special method
importScripts()
.
importScripts('script1.js');
This method can load multiple scripts at the same time.
importScripts('script1.js', 'script2.js');
2.4 Error handling
The main thread can monitor the Worker for errors.
If an error occurs, the Worker will trigger an
error
event on the
main thread
.
worker.onerror(function (event) { console.log([ 'ERROR: Line ', e.lineno, ' in ', e.filename, ': ', e.message ].join('')); }); // 或者 worker.addEventListener('error', function (event) { // ... });
You can also listen to
error
events
inside the Worker
.
2.5 Close Worker
After use, in order to save system resources, the Worker must be closed.
// 主线程 worker.terminate(); // Worker 线程 self.close();
Three, data communication
As mentioned earlier, the communication content between the main thread and the Worker can be text or objects. It should be noted that this communication is a copy relationship, that is, transfer by value instead of address, and the modification of the communication content by Worker will not affect the main thread. In fact, the internal operating 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 Worker, such as File, Blob, ArrayBuffer and other types, and can also be sent between threads. Below is an example.
// 主线程 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 线程 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, sending binary data in copy mode will cause performance problems. For example, if the main thread sends a 500MB file to 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 directly to the child threads, but once transferred, the main thread can no longer use the binary data. This is to prevent the troublesome situation of multiple threads modifying data at the same time. This method of transferring data is called Transferable Objects . This allows the main thread to quickly hand over the data to the Worker, which is very convenient for image processing, sound processing, 3D calculations, etc., without any performance burden.
If you want to directly transfer control of the data, you must use the following wording.
// Transferable Objects 格式 worker.postMessage(arrayBuffer, [arrayBuffer]); // 例子 var ab = new ArrayBuffer(1); worker.postMessage(ab, [ab]);
Fourth, the Web Worker on the same page
Normally, Worker loads a separate JavaScript script file, but it can also load code that is on the same web page as the main thread.
<!DOCTYPE html> <body> <script id="worker" type="app/worker"> addEventListener('message', function () { postMessage('some message'); }, false); </script> </body> </html>
The above is a script embedded in a web page. Note that
<script>
the
type
attribute of the
tag
that must be specified
is a value that the browser does not recognize. The above example is
app/worker
.
Then, read the script embedded in the page and process it with Worker.
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, first convert the script code embedded in the web page into a binary object, then generate a URL for the binary object, and then let the Worker load the URL. In this way, the main thread and Worker code are on the same web page.
V. Example: Worker thread completes polling
Sometimes, the browser needs to poll the server status in order to know the status change as soon as possible. This job can be placed in 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, the Worker polls the data every second, and then compares it with the cache. If they are inconsistent, it means that the server has new changes, so the main thread must be notified.
6. Example: Worker Create a new Worker
Worker threads can be created inside Worker threads (currently only supported by Firefox browser). The following example is to assign 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; };
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 newly created inside the Worker thread, and messages are sent to these 10 Workers in turn to inform the starting point and end point of the calculation. The code of 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(); }
Seven, API
7.1 Main thread
The browser
Worker()
natively
provides a
constructor for the main thread to
generate a
Worker thread.
var myWorker = new Worker(jsUrl, options);
Worker()
The constructor can accept two parameters.
The first parameter is the URL of the script (must comply with the same-origin policy). This parameter is required, and only JS scripts can be loaded, 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, which is used to distinguish multiple Worker threads.
// 主线程 var myWorker = new Worker('worker.js', { name : 'myWorker' }); // Worker 线程 self.name // myWorker
Worker()
The constructor returns a Worker thread object for the main thread to operate Worker.
The attributes 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, and the sent data is in the
Event.data
attribute.- Worker.onmessageerror: Specify the listener function for the messageerror event. This event is triggered when the sent data cannot 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 object, not the main thread
window
, but a global object customized specifically for Worker.
Therefore,
window
not all objects and methods
defined
above can be used.
The Worker thread has some of its own global properties and methods.
- self.name: Worker’s name. This attribute is read-only and is specified by the constructor.
- self.onmessage: Specifies
message
the listener function of the event.- self.onmessageerror: Specify the listener function for the messageerror event. This event is triggered when the sent data cannot be serialized into a string.
- self.close(): Close the Worker thread.
- self.postMessage(): Send a message to the thread that produced this Worker.
- self.importScripts(): Load JS scripts.
(over)