提问人:Ozan Başkan 提问时间:8/17/2023 更新时间:8/17/2023 访问量:35
Nodejs 事件循环 promise 在 process.tick 之前执行
Nodejs event loop promise executes before process.tick
问:
const axiosTest = async () => {
setImmediate(() => {
console.log('immediate axios');
})
const x = axios.get('https://www.google.com');
x.then((r) => console.log('fetch'));
const x2 = new Promise(resolve => {
resolve('promise axios')
});
x2.then(console.log)
process.nextTick(() => {
console.log('tick axios')
});
console.log('stack axios');
}
const normalTest = async () => {
setImmediate(() => {
console.log('immediate');
})
const x = new Promise(resolve => {
resolve('promise')
});
x.then(console.log)
process.nextTick(() => {
console.log('tick')
});
console.log('stack');
}
normalTest().then(axiosTest);
执行顺序:
stack
tick
promise
stack axios
promise axios
tick axios
immediate
immediate axios
fetch
normalTest()
执行顺序:
stack
tick
promise
immediate
axiosTest()
执行顺序:
stack axios
tick axios
promise axios
immediate axios
fetch
当我刚刚运行 axiosTest 函数时,tick 在 promise resolve 之前执行,但当我在 normalTest 函数 promise 首先解析之后运行它时。为什么?
我希望 promise.tick 在相同范围内解析任何 promise 之前执行,但它没有。
答:
回调安排在当前“阶段”结束时。文档将阶段定义为在事件循环期间处理一个 FIFO 队列:process.nextTick
当 Node.js 启动时,它会初始化事件循环,处理提供的输入脚本(或放入 REPL,本文档未介绍),该脚本可能会进行异步 API 调用、调度计时器或调用 process.nextTick(),然后开始处理事件循环。
下图显示了事件循环操作顺序的简化概述。
┌───────────────────────────┐ ┌─>│ timers │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ pending callbacks │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ idle, prepare │ │ └─────────────┬─────────────┘ ┌───────────────┐ │ ┌─────────────┴─────────────┐ │ incoming: │ │ │ poll │<─────┤ connections, │ │ └─────────────┬─────────────┘ │ data, etc. │ │ ┌─────────────┴─────────────┐ └───────────────┘ │ │ check │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ └──┤ close callbacks │ └───────────────────────────┘
每个框将被称为事件循环的一个“阶段”。
我们可以用这个简化的脚本重现顺序的变化:
const test = async () => {
console.log('------------test-------------');
process.nextTick(() => console.log('tick'));
Promise.resolve().then(() => console.log('promise resolved'))
}
test().then(test)
输出为:
------------test-------------
tick
promise resolved
------------test-------------
promise resolved
tick
不同之处在于,从不同的阶段执行,这意味着不同的顺序。这是一个分步分析,应该澄清这一点:test
当主脚本完成其同步部分(即 已经执行过一次),我们有这样的状态:test
() => console.log('tick')
在当前阶段之后等待执行() => console.log('promise resolved')
正在 promise 作业队列中等待test
正在 promise 作业队列中等待(第二个条目)
当同步部分结束时,引擎准备进入事件循环并处理下一阶段。但是,在过渡到下一阶段之前,将执行即时报价回调。所以这解释了“滴答声”是第一位的。
在事件循环的某个时刻,我们进入了 promise 作业阶段(上面的简化图中没有特别指出):这是第一个“promise resolved”被输出,然后被执行(第二次执行)的地方。test
再次会做类似的事情:test
() => console.log('tick')
在当前阶段之后等待执行() => console.log('promise resolved')
正在 promise 作业队列中等待
但现在我们应该意识到,我们仍然处于承诺作业阶段,在这个阶段,发现承诺作业队列仍然不是空的(我们只是在那里添加了一个新作业),因此没有过渡到下一阶段,因此现在还不是进行“勾选”的时候。
因此,这一次,“promise resolved”首先输出,然后这个阶段结束,因为 promise 作业队列中不再有 promise 作业:现在是进行勾选的时候了,而 'tick' 是输出。
备注
这种即时报价的概念和在当前阶段之后执行的回调不是 ECMA 脚本语言规范的一部分。nextTick
就我个人而言,我会远离打电话。nextTick
评论