蓝易云cdn:async方式在浏览器中调用web
通过Async方式在浏览器中调用Web Worker 🧵
Web Worker能将耗时计算放到独立线程中执行,避免阻塞UI渲染。但Worker原生的通信方式基于 postMessage 和 onmessage 事件回调,写起来很不直观。通过Promise封装,我们可以用 async/await 语法优雅地调用Worker,让异步线程通信像调用普通函数一样简洁。

一、原生Worker通信的痛点 😵
先看传统写法:
const worker = new Worker('worker.js');
worker.postMessage({ type: 'calculate', data: 100000 });
worker.onmessage = function(e) {
console.log('结果:', e.data);
};
worker.onerror = function(e) {
console.error('出错了:', e.message);
};
解释:new Worker('worker.js') 创建一个后台线程并加载指定脚本。postMessage 向Worker发送消息,onmessage 监听Worker返回的结果。问题在于——发送和接收是分离的,如果主线程需要连续发起多个不同任务,回调中很难区分哪个响应对应哪个请求,代码会迅速变成难以维护的回调地狱 🌀。
二、用Promise封装Worker通信 ✨
核心思路是:每次调用时生成一个唯一ID,将请求和对应的resolve/reject关联起来,Worker返回结果时根据ID匹配并兑现Promise。
主线程封装代码:
class AsyncWorker {
constructor(scriptUrl) {
this.worker = new Worker(scriptUrl);
this.pendingTasks = new Map();
this.taskId = 0;
this.worker.onmessage = (e) => {
const { id, result, error } = e.data;
const task = this.pendingTasks.get(id);
if (!task) return;
this.pendingTasks.delete(id);
if (error) {
task.reject(new Error(error));
} else {
task.resolve(result);
}
};
this.worker.onerror = (e) => {
// 全局错误,拒绝所有等待中的任务
for (const [id, task] of this.pendingTasks) {
task.reject(new Error(e.message));
}
this.pendingTasks.clear();
};
}
run(type, data) {
return new Promise((resolve, reject) => {
const id = this.taskId++;
this.pendingTasks.set(id, { resolve, reject });
this.worker.postMessage({ id, type, data });
});
}
terminate() {
this.worker.terminate();
this.pendingTasks.clear();
}
}
解释: 这个类是整个方案的核心。构造函数中创建Worker实例,同时初始化一个 Map 用于存放等待中的任务。每个任务由自增的 taskId 唯一标识。run 方法返回一个Promise,调用时将 resolve 和 reject 函数存入Map中,同时通过 postMessage 将任务ID和数据发送给Worker。当Worker返回消息时,onmessage 回调根据返回的 id 从Map中找到对应的Promise并完成兑现。如果Worker返回了 error 字段就走reject,否则走resolve。onerror 处理Worker脚本级别的全局异常,一旦触发,所有挂起的任务统一拒绝 🛡️。
Worker线程代码(worker.js):
self.onmessage = function(e) {
const { id, type, data } = e.data;
try {
let result;
if (type === 'fibonacci') {
result = fibonacci(data);
} else if (type === 'sort') {
result = data.slice().sort((a, b) => a - b);
} else {
throw new Error('未知任务类型:' + type);
}
self.postMessage({ id, result });
} catch (err) {
self.postMessage({ id, error: err.message });
}
};
function fibonacci(n) {
if (n <= 1) return n;
let a = 0, b = 1;
for (let i = 2; i <= n; i++) {
[a, b] = [b, a + b];
}
return b;
}
解释: Worker线程监听消息,从中解构出 id、type、data 三个字段。根据 type 执行对应的计算任务,然后将结果连同原始 id 一起回传。关键点是 id必须原样带回 ——主线程正是靠这个id来匹配哪个Promise应该被resolve。try-catch 捕获运算异常,出错时将错误信息作为 error 字段返回,确保主线程的Promise能正确reject而不是永远挂起 ⚙️。
三、Async/Await调用实战 🚀
封装完毕后,主线程的调用方式变得极其清爽:
const worker = new AsyncWorker('worker.js');
async function main() {
try {
// 串行调用
const fib = await worker.run('fibonacci', 50);
console.log('斐波那契结果:', fib);
// 并行调用多个任务
const [result1, result2] = await Promise.all([
worker.run('fibonacci', 40),
worker.run('sort', [5, 3, 8, 1, 9, 2])
]);
console.log('并行结果:', result1, result2);
} catch (err) {
console.error('任务执行失败:', err.message);
}
}
main();
解释:await worker.run(...) 让异步线程通信看起来像同步函数调用,代码可读性大幅提升。串行场景下直接 await 逐个等待结果;并行场景下用 Promise.all 同时发起多个任务,Worker会依次处理并返回(单Worker是单线程顺序执行,如需真正并行可创建多个Worker实例)。try-catch 统一处理错误,不用再分散写多个错误回调 🎯。
四、进阶:用Blob创建内联Worker 💡
有些场景下不希望额外维护一个worker.js文件,可以直接将Worker代码内联到主线程中:
function createInlineWorker(fn) {
const blob = new Blob(
['self.onmessage = ', fn.toString()],
{ type: 'application