文档首页> Linux命令> 蓝易云cdn:async方式在浏览器中调用web

蓝易云cdn:async方式在浏览器中调用web

发布时间:2026-03-23 00:18       

通过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线程监听消息,从中解构出 idtypedata 三个字段。根据 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