Skip to content

HTML5 - Web Workers

Web 浏览器中的 JavaScript 传统上运行在单个线程(通常称为“主线程”)中。该线程负责处理用户界面更新(渲染、响应点击、滚动)以及执行 JavaScript 代码。如果脚本执行计算密集型任务(例如复杂的计算、处理大量数据或密集的网络请求),它可能会阻塞主线程,导致用户界面变得无响应或“卡死”。

考虑这个简化示例:点击“重型任务”按钮模拟一个长时间运行的操作。在它运行时,点击“打招呼”按钮不会立即触发提示框,因为主线程正忙。

<!DOCTYPE html>
<html>
<head>
<title>Blocking Example</title>
<script>
function performHeavyTask() {
console.log('Starting heavy task...');
// Simulate a long-running calculation
const end = Date.now() + 5000; // Run for 5 seconds
while (Date.now() < end) {
// Busy-wait loop
}
console.log('Heavy task completed.');
alert('Heavy task finished!');
}
function sayHello() {
alert('Hello there!');
}
</script>
</head>
<body>
<button onclick="performHeavyTask()">Run Heavy Task (Blocks UI)</button>
<button onclick="sayHello()">Say Hello</button>
</body>
</html>

Web Workers 提供了一种机制,可以在后台线程中运行 JavaScript 代码,与主执行线程分开。这允许执行长时间运行或计算开销大的任务,而不会阻塞主线程,从而保持用户界面的响应性。

Worker 非常适合执行复杂计算、数据处理、后台同步或管理大型数据集等任务。

Worker 有不同类型,包括专用 Worker(Dedicated Workers,最常见的类型,绑定到单个脚本上下文)、共享 Worker(Shared Workers,可被同源的多个脚本访问)和 Service Worker(Service Workers,充当代理服务器,管理网络请求并支持离线功能和推送通知 - 这是一个更高级的主题)。

重要限制: 在 Web Worker 中运行的脚本在单独的全局上下文(DedicatedWorkerGlobalScope、SharedWorkerGlobalScope 等)中操作。它们不直接访问 window 对象、DOM(文档对象模型)或主线程中可用的许多其他 API。Worker 和主线程之间的通信通过消息传递发生。

  1. 创建 Worker: 在你的主脚本中,创建一个新的 Worker 对象,传递包含 Worker 代码的 JavaScript 文件路径。
main.js
const myWorker = new Worker('worker.js');
  1. 定义 Worker 逻辑: 创建 worker.js 文件。该脚本将在后台线程中运行。它通常监听来自主线程的消息,并将结果发回消息。
// worker.js
// Listen for messages from the main thread
self.onmessage = function(event) {
console.log('Message received from main script:', event.data);
if (event.data.command === 'calculate') {
// Perform the calculation (e.g., sum numbers)
const result = event.data.numbers.reduce((a, b) => a + b, 0);
// Send the result back to the main thread
self.postMessage({ type: 'result', payload: result });
}
};
// You can also send messages proactively
// self.postMessage('Worker initialized!');
  1. 通信: 使用 postMessage() 方法在主线程和 Worker 之间发送数据(字符串、对象、数组、ArrayBuffers - 任何可以结构化克隆的数据)。使用 onmessage 事件处理器监听消息。
// main.js
const myWorker = new Worker('worker.js');
// Send data to the worker
const dataToSend = { command: 'calculate', numbers: [1, 2, 3, 4, 5] };
myWorker.postMessage(dataToSend);
// Listen for messages from the worker
myWorker.onmessage = function(event) {
console.log('Message received from worker:', event.data);
if (event.data.type === 'result') {
alert(`Calculation result: ${event.data.payload}`);
}
};
  1. 导入脚本(可选): 如果你的 Worker 逻辑分散在多个文件中,你可以在 Worker 脚本中使用 importScripts() 函数加载它们。
worker.js
importScripts('helper.js', 'anotherHelper.js');
// Now functions/variables from helper.js and anotherHelper.js are available
self.onmessage = function(event) {
// ... use imported functions ...
};

让我们使用 Web Worker 重写最初的阻塞示例。

index.html(主页面):

<!DOCTYPE html>
<html>
<head>
<title>Web Worker Example</title>
<script>
let worker;
function startHeavyTaskWorker() {
if (window.Worker) { // Check for browser support
if (!worker) {
worker = new Worker('heavy_task_worker.js');
worker.onmessage = function(event) {
console.log('Message received from worker:', event.data);
alert('Worker finished the heavy task!');
// Optionally terminate the worker if no longer needed
// worker.terminate();
// worker = null;
};
worker.onerror = function(error) {
console.error('Error in worker:', error.message, error);
alert('An error occurred in the worker.');
};
}
console.log('Sending task command to worker...');
worker.postMessage({ command: 'startTask' });
} else {
alert('Your browser does not support Web Workers.');
}
}
function sayHello() {
alert('Hello there!');
}
</script>
</head>
<body>
<button onclick="startHeavyTaskWorker()">Run Heavy Task (Worker)</button>
<button onclick="sayHello()">Say Hello</button>
</body>
</html>

heavy_task_worker.js(Worker 脚本):

// heavy_task_worker.js
self.onmessage = function(event) {
if (event.data.command === 'startTask') {
console.log('Worker starting heavy task...');
// Simulate a long-running calculation
const end = Date.now() + 5000; // Run for 5 seconds
while (Date.now() < end) {
// Busy-wait loop
}
console.log('Worker heavy task completed.');
self.postMessage({ status: 'completed' });
}
};

现在,当你点击“运行重型任务 (Worker)”时,长时间运行的操作将在后台运行。你可以立即点击“打招呼”,提示框会立即出现,因为主线程没有被阻塞。

Web Worker 会持续运行,直到被显式终止。在主线程中,你可以使用 Worker 对象的 terminate() 方法停止一个 Worker:

main.js
if (worker) {
worker.terminate();
worker = null; // Clear the reference
console.log('Worker terminated.');
}

Worker 也可以通过在其脚本中调用 self.close() 来终止自身。

一个已终止的 Worker 不能重新启动;如果需要,必须创建一个新的实例。

Worker 脚本中发生的错误(例如,语法错误、运行时异常)可以通过在主线程中为 Worker 对象附加一个 onerror 事件处理器来捕获。事件对象提供了错误消息、文件名和行号等信息。

// main.js
myWorker.onerror = function(error) {
console.error(`Error in worker: ${error.message} at ${error.filename}:${error.lineno}`);
// Handle the error appropriately, e.g., notify the user
};

适当地处理错误,例如通知用户。

你可以通过检查 window 对象上是否存在 Worker 对象来轻松检查浏览器是否支持 Web Workers:

if (window.Worker) {
// Web Workers are supported
console.log('Web Workers supported!');
// Proceed to create workers...
} else {
// Web Workers are not supported
console.warn('Web Workers not supported in this browser.');
// Provide fallback or notify the user
}