You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Node.js与浏览器事件循环差异:为何这段异步代码输出不同?

Node.js与浏览器事件循环差异解析(针对给定异步代码)

先把咱们要分析的代码贴出来,方便对照:

async function async1() { console.log("a"); await async2(); console.log("b"); }
async function async2() { console.log( 'c'); }
console.log("d");
setTimeout(function () { console.log("e"); },0);
async1();
new Promise(function (resolve) { console.log("f"); resolve(); }).then(function () { console.log("g"); });
console.log('h');

先直接看两个环境的输出差异:

  • Node.js 运行时输出:d a c f h b g e
  • 谷歌浏览器运行时输出:d a c f h g b e

差异点很明显:bg的输出顺序反过来了。咱们一步步拆解,看看问题出在哪。


第一步:同步代码执行阶段(两个环境完全一致)

不管是Node还是浏览器,同步代码都是按从上到下的顺序执行,这部分输出没有区别:

  1. 首先执行console.log("d") → 输出d
  2. 调用setTimeout,它的回调会被放进宏任务队列(宏任务优先级最低,最后才会执行)
  3. 调用async1()
    • 先执行console.log("a") → 输出a
    • 接着调用async2(),里面的console.log("c")同步执行 → 输出c;因为async2是async函数,默认返回一个已经resolved的Promise
    • 遇到awaitasync1会暂停执行,后续的console.log("b")会被包装成一个回调,等着被放进微任务队列
  4. 执行new Promise的构造函数:
    • 同步执行console.log("f") → 输出f
    • 调用resolve()后,then里的console.log("g")会被放进微任务队列
  5. 最后执行console.log('h') → 输出h

到这里同步代码就跑完了,接下来就是两个环境差异的核心:微任务队列的处理逻辑不一样


第二步:微任务处理的核心差异

Node.js环境(以v10及旧版本为例,新版本优先级逻辑未变)

Node.js把微任务分成了两个优先级不同的队列:

  • process.nextTick队列:优先级最高,只要队列里有任务,就会先把它执行完
  • Promise队列:也就是then/catch/finally的回调队列,优先级比前者低

在这段代码里,await后续的console.log("b")会被放到process.nextTick队列,而new Promisethen回调console.log("g")会放到Promise队列。所以微任务的执行顺序是:

  1. 先把process.nextTick队列的任务跑完 → 输出b
  2. 再执行Promise队列的任务 → 输出g

浏览器环境

浏览器的微任务队列不分优先级,完全按照先进先出的顺序执行。

这里的关键是:await的后续回调是在new Promisethen回调之后才加入队列的。为啥?因为当await遇到已经resolved的Promise时,浏览器会先把当前所有同步代码执行完,再把await的后续回调塞进微任务队列;而new Promisethen回调是在调用resolve()的瞬间就被加入队列了。

所以微任务队列的顺序是:

  1. 先执行先加入的console.log("g") → 输出g
  2. 再执行后加入的console.log("b") → 输出b

第三步:宏任务收尾

等所有微任务都执行完,才会轮到宏任务队列里的setTimeout回调,执行后输出e

内容的提问来源于stack exchange,提问作者hui

火山引擎 最新活动