为何Web Worker的onmessage会在宏任务(setTimeout)之后执行?
为何Web Worker的onmessage会在宏任务(setTimeout)之后执行?
哈哈,这个问题我当初啃事件循环的时候也踩过一模一样的坑!先给你拆解清楚为啥会出现这种反直觉的情况~
首先得纠正一个小误解:Worker的消息回调其实也属于主线程的宏任务队列,但它的触发时机和setTimeout的宏任务之间,有个关键的跨线程时间差,这就是问题的核心:
1. Worker的“启动+消息处理”是跨线程的异步流程
当你在主线程执行new Worker('worker.js').postMessage('')时,实际发生的步骤是:
- 主线程发起Worker脚本的加载、解析,同时启动一个独立的Worker线程
- 立刻把消息发送给Worker线程,但这时候Worker线程可能还没完成初始化,甚至还没注册好
onmessage回调函数 - 只有等Worker线程完全启动、注册好事件监听,处理完收到的消息,再调用
postMessage把结果发回主线程时,主线程的宏任务队列才会新增一个“Worker消息回调”的任务
而setTimeout(() => {}, 0)就简单多了:它在主线程同步代码执行完后,会直接被加入主线程的宏任务队列,不需要等待任何跨线程的初始化流程,只要事件循环走到宏任务阶段,就会执行它的回调。
2. 哪怕加了阻塞,Worker的消息还是会后到
你测试里给setTimeout加了1秒的阻塞循环,结果Worker的消息还是在后面,这是因为:
- 主线程的阻塞只会卡住自己的事件循环,但Worker线程在后台正常处理消息,处理完后会把结果发回主线程,但这个消息会暂时存在主线程的宏任务队列里排队
- 只有当主线程的阻塞循环结束,事件循环继续运行时,才会去处理这个Worker消息的宏任务,而这时候
setTimeout的回调已经执行完了(毕竟阻塞是在setTimeout的回调内部,回调执行完才会回到事件循环处理下一个宏任务)
完整的执行顺序梳理
我再给你把整个流程串一遍,你就能彻底明白:
- 主线程同步执行代码:
- 创建Worker实例,发起Worker脚本的加载与线程初始化
- 调用
worker.postMessage(''),将消息发向Worker线程 - 调用
setTimeout(0),把回调函数加入主线程的宏任务队列
- 主线程同步代码执行完毕,调用栈清空
- 处理微任务队列(这里没有微任务,直接跳过)
- 处理主线程宏任务队列的第一个任务:
setTimeout的回调执行(哪怕你加了1秒阻塞,也是在这里执行) - 此时Worker线程已经完成初始化,处理了收到的消息,把结果发回主线程,这个消息被加入主线程的宏任务队列
- 主线程事件循环继续,处理下一个宏任务:Worker的
onmessage回调执行,输出日志
这样是不是就清晰多了?核心就是Worker的消息回调需要等待跨线程的初始化和消息传递流程,而setTimeout的宏任务是直接在主线程队列里排队,所以会先一步执行。
备注:内容来源于stack exchange,提问作者Biel Santo
相关产品推荐
相关产品推荐

