Preface
In the browser, because the JavaScript engine and the GUI rendering thread are mutually exclusive, when we perform some computationally intensive or high-latency tasks in JavaScript, the page rendering will be blocked or slowed down. In order to solve this problem and improve the user experience, HTML5 brings us the standard of Web Workers.
Overview
As part of the HTML5 standard, Web Workers
defines a set of APIs that allow a JavaScript program to run in a Worker thread other than the main thread. While the main thread is running, the worker thread can run independently in the background to process some computationally intensive or high-latency tasks, and wait for the worker thread to complete the calculation task, and then return the result to the main thread. This ensures that the main thread (usually the UI thread) will not be blocked or slowed down.
There are three main types of common Web Workers:
- Dedicated Workers
- Shared Workers
- Service Workers
Dedicated Workers
That is, dedicated Workers can only be used by the script that generates it, and can only be bound and communicated with one page rendering process, and cannot be shared by multiple tabs. The browser support situation is as follows:
Basic usage of dedicated Workers
1. Create worker thread method:
We call the new command in the main thread JS and then implement the Worker() constructor to create a Worker thread. The code is as follows:
var worker = new Worker("work.js");
Worker()
constructor is a script file, which is the task that the Worker thread needs to perform. It should be noted that because Web Workers have homology restrictions, this script must be read from the network or local server.
2. The main process sends data
Next, we can send a message from the main thread to the child thread, using the worker.postMessage()
method to send a message to the Worker. The code is as follows:
worker.postMessage("Hello LeapFE");
worker.postMessage
method can accept any type of parameters, even binary data.
3. Worker monitor function
There needs to be a listener function inside the Worker thread to listen to the messages sent by the main thread/other child threads. The listening event is message
. The code is as follows:
addEventListener('message', function(e) { postMessage('子线程向主线程发送消息: ' + e.data); close(); // 关闭自身 });`
The child thread receives the data sent by the main process, then performs the corresponding operation, and finally returns the result to the main thread.
4. The main process receives data
The main thread worker.onmessage
, and receives the message sent back by the child thread. The code is as follows:
worker.onmessage = function (event) {
console.log("接收到的消息为: " + event.data);
};
The message sent back by Worker can be obtained from the data property of the event object.
If our Worker thread task is completed, our main thread needs to shut it down, the code is as follows:
worker.terminate();
5. importScripts() method
If other scripts need to be loaded inside the Worker, we can use the importScripts()
method. The code is as follows:
importScripts("a.js");
If you want to load multiple scripts, the code can be written like this:
importScripts('a.js', 'b.js', 'c.js', ....);
6. Error monitoring
The main thread can monitor whether an error occurs in the Worker thread. If an error occurs, the Worker thread will trigger the error
event of the main thread.
worker.onerror = function (e) {
console.log(e);
};
If an error occurs in the Worker throw new Error()
, but this error cannot be retrieved by the main thread. You can only see the "error not caught prompt" error prompt console
console
!
// worker.js内部:
// ... other code
throw new Error("test error");
Shared Workers
That is, shared Workers can be seen as an extension of dedicated Workers. In addition to supporting the functions of dedicated Workers, it can also be accessed by different window pages, iframes, and Workers (of course, subject to homology restrictions) to perform asynchronous communication. The browser support situation is as follows:
Basic usage of shared Workers
1. Creation of Shared Workers
Creating shared Workers can be achieved by using the SharedWorker() constructor. This constructor uses the URL as the first parameter, which is the URL pointing to the JavaScript resource file. The code is as follows:
var worker = new SharedWorker("sharedworker.js");
2. Share Workers to communicate with the main process
The steps of interaction between shared Workers and the main thread are basically the same as those of dedicated Workers, except for one more port:
// 主线程:
const worker = new SharedWorker("worker.js");
const key = Math.random().toString(32).slice(-6);
worker.port.postMessage(key);
worker.port.onmessage = (e) => {
console.log(e.data);
};
// worker.js:
const buffer = [];
onconnect = function (evt) {
const port = evt.ports[0];
port.onmessage = (m) => {
buffer.push(m.data);
port.postMessage("worker receive:" + m.data);
};
};
In the above code, there are two points to note:
onconnect
When other threads create sharedWorker, they actually send a link to sharedWorker, and the worker will receive a connect eventevt.ports[0]
connect event handle evt.ports[0] is a very important object port, used to send messages to the corresponding thread and receive messages from the corresponding thread
Service workers
At the current stage, the main capabilities of Service Workers are focused on network proxies and offline caches. In terms of specific implementation, it can be understood that the Service Worker is a Web Worker that can still run when the web page is closed. The browser support situation is as follows:
PS: Service Workers
involves a lot of function points. Due to limited space, this article will not introduce it for the time being. We will analyze it in detail in a later update.
Application scenarios of dedicated workers and shared workers
As mentioned above, Worker can run independently in the background without blocking the main process. The most common scenario for using Worker is to handle some computationally intensive or high-latency tasks.
Scenario 1: Use a dedicated Worker to solve the time-consuming problem
We have an input box on the page. The user needs to enter a number in the input box, and then click the calculation button next to it to calculate the sum from 1 to a given value in the background. If we do not use Web Workers to solve this problem, as shown in the following demo code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Web Worker</title>
</head>
<body>
<h1>从1到给定数值的求和</h1>
输入数值: <input type="text" id="num" />
<button onclick="calculate()">计算</button>
<script type="text/javascript">
function calculate() {
var num = parseInt(document.getElementById("num").value, 10);
var result = 0;
// 循环计算求和
for (var i = 0; i <= num; i++) {
result += i;
}
alert("总和为:" + result + "。");
}
</script>
</body>
</html>
Like the above code, then we enter 10 billion, and then let the computer calculate it for us. The calculation time should take about 20 seconds, but before this 20 seconds, then our page is in a stuck state, and That is to say, nothing can be done. After the calculation result comes out, we will see the following pop-up window prompting the result, as shown below:
So now we try to use Web Workers to solve the problem, and use Worker to solve these time-consuming operations, then the main thread will not affect the state of page suspended animation, we first change the index.html code as follows:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Web Worker</title>
</head>
<body>
<h1>从1到给定数值的求和</h1>
输入数值: <input type="text" id="num" />
<button id="calculate">计算</button>
<script type="module">
// 创建 worker 实列
var worker = new Worker("./worker1.js");
var calDOM = document.getElementById("calculate");
calDOM.addEventListener("click", calculate);
function calculate() {
var num = parseInt(document.getElementById("num").value, 10);
// 将我们的数据传递给 worker 线程,让我们的 worker 线程去帮我们做这件事
worker.postMessage(num);
}
// 监听 worker 线程的结果
worker.onmessage = function (e) {
alert("总和值为:" + e.data);
};
</script>
</body>
</html>
As we can see when we run the above code, after we click the calculation button, we use the main thread to process this complex time-consuming operation to the child thread. After we click the button, our page can be operated, because the main thread and The Worker thread is two different environments, and the Worker thread does not affect the main thread. So if we need to deal with some time-consuming operations, we can use the Web Workers thread to deal with the problem.
Scenario 2: Use shared Worker to realize cross-page data sharing
Below we give an example: create a shared Worker to share data from multiple Tab pages, and realize the function of a simple web chat room.
First, design a simple chat dialog style index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Shared Worker Example</title>
<style>
ul li {
float: left;
list-style: none;
margin-top: 10px;
width: 100%;
}
ul li > span {
font-size: 10px;
transform: scale(0.8);
display: block;
width: 17%;
}
ul li > p {
background: rgb(140, 222, 247);
border-radius: 4px;
padding: 4px;
margin: 0;
display: inline-block;
}
ul li.right {
float: right;
text-align: right;
}
ul li.right > p {
background: rgb(132, 226, 140);
}
ul li.right > span {
width: 110%;
}
#chatList {
width: 300px;
background: #fff;
height: 400px;
padding: 10px;
border: 4px solid #de8888;
border-radius: 10px;
}
</style>
</head>
<body>
<div class="container">
<section>
<p id="user"></p>
<ul id="chatList" style="width: 300px"></ul>
<input id="input" />
<button id="submitBtn">提交</button>
</section>
</div>
<script src="./main.js"></script>
</body>
</html>
SharedWorker
, we initialize an instance of 0060021db40d04
window.onload = () => {
const worker = new SharedWorker("./shared-worker.js");
const chatList = document.querySelector("#chatList");
let id = null;
worker.port.onmessage = (event) => {
const { data } = event;
switch (data.action) {
case "id": // 接收 Worker 实例化成功之后返回的 id
id = data.value;
document.querySelector("#user").innerHTML = `Client ${id}`;
break;
case "message": // 接收 Worker 返回的来自各个页面的信息
chatList.innerHTML += `<li class="${
data.id === id ? "right" : "left"
}"><span>Client ${data.id}</span><p>${data.value}</p></li>`;
break;
default:
break;
}
};
document.querySelector("#submitBtn").addEventListener("click", () => {
const value = document.querySelector("#input").value;
// 将当前用户 ID 及消息发送给 Worker
worker.port.postMessage({
action: "message",
value: value,
id,
});
});
};
Receive the connection with each page at shared-worker.js
, and forward the message sent from the page at the same time
const connectedClients = new Set();
let connectID = 1;
function sendMessageToClients(payload) {
//将消息分发给各个页面
connectedClients.forEach(({ client }) => {
client.postMessage(payload);
});
}
function setupClient(clientPort) {
//通过 onmessage 监听来自主进程的消息
clientPort.onmessage = (event) => {
const { id, value } = event.data;
sendMessageToClients({
action: "message",
value: value,
id: connectID,
});
};
}
// 通过 onconnect 函数监听,来自不同页面的 Worker 连接
onconnect = (event) => {
const newClient = event.ports[0];
// 保存连接到 Worker 的页面引用
connectedClients.add({
client: newClient,
id: connectID,
});
setupClient(newClient);
// 页面同 Worker 连接成功后, 将当前连接的 ID 返回给页面
newClient.postMessage({
action: "id",
value: connectID,
});
connectID++;
};
In the above shared thread example, a shared thread object is constructed on the main page, that is, each user connection page, and then the information input by the user is sent to the shared thread worker.port.postMessage
connectID
is defined in the implementation code snippet of the shared thread to record the total number of connections to this shared thread. After that, use the onconnect
event handler to receive connections from different users and parse the information passed by them. Finally, a method sendMessageToClients
defined to distribute the message to each user.
to sum up
Web Workers does provide us with new possibilities for optimizing Web applications. By using Web Workers to reasonably schedule JavaScript execution logic, you can ensure that the GUI is still responsive when faced with unpredictable low-end devices and long tasks.
Perhaps in the future, programming with Web Workers may become a standard or best practice for the development of a new generation of Web applications.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。