奇怪的异步代码行为

时间:2018-08-19 13:47:20

标签: javascript node.js asynchronous promise

我有一个无法解决的JS行为,我无法理解。 我正在node v8.4.0上运行此代码。 我两次运行此代码。

第一次使用f1() 第二次与f2()

f2()结果符合预期。首先打印“开始”,然后打印“结束”。

f1()结果与预期不符。先打印“结束”,然后再打印“开始”。

有人可以告诉我下面代码的结果吗?

const fs = require('fs')

function f1() { return new Promise((resolve, reject) => { resolve() }) }

function f2() {
    return new Promise((resolve, reject) => {
        fs.readFile('/Users/adi/Downloads/profile.jpg', resolve)
    })
}

async function main() {

    setImmediate(() => { console.log('start') })
    await f1()
    console.log('end')
} 

main()

//f1 output:
end
start

//f2 output:
start
end

据我所知,结果应该是“开始”,然后是“结束”。 我想念什么?

3 个答案:

答案 0 :(得分:1)

将在带有setImmediate(() => { console.log('start') })的队列之前检查带有已解决的Promises的队列

由于f1会立即解析,因此setImmediate的回调和已解析的Promise会同时添加到事件队列中,但要处于不同的阶段。已解决的承诺的优先级高于使用setImmediate

添加的回调

如果您使用process.nextTick,则将以更高的优先级添加回调,然后setImmediatestart会在end之前被记录

function f1() { return new Promise((resolve, reject) => { resolve() }) }

async function main() {
    process.nextTick(() => { console.log('start') })
    setImmediate(() => { console.log('start') })
    await f1()
    console.log('end')
} 

main()

对于f2,文件的读取将涉及更长的异步任务,因此setImmediat仍将被调用。

答案 1 :(得分:1)

因此,在您的f1()示例中,您在setImmediate().then()处理程序之间进行了立即解决的Promise竞赛,因为两者都将在下一次的事件队列中出现事件已准备好进行处理。

当两者都准备好运行时,由于setImmediate()和promise这样的异步事物在其事件循环的node.js实现中如何工作的内部原因,一个先于另一个运行。在node.js中的事件循环内部,某些不同类型的异步操作具有顺序或优先级,如果所有等待都进行,则某些优先于其他操作。尽管可能很难完全理解其他方法,但是它非常复杂,并且主要是实现细节,而没有被规范完全记录。

在这种特定情况下,node.js中的本机承诺使用microTasks队列(显然有几个单独的microTasks队列),它们在setImmediate(),计时器和I / O事件之类的东西之前运行。

但是,总的来说,最好不要依赖于完全理解所有内容,并且,如果您希望一件事情先于另一件事情发生,则不要让它成为node.js内部两者之间的竞争。只需使用您自己的代码对其进行编码即可强制执行所需的序列。这也使您的代码更加明显,并声明了您希望以什么顺序处理事物。

如果我阅读了您当前的代码,我会认为您故意在f1()setImmediate()之间建立了竞争,并且并不在意哪个先运行,因为该代码不是声明性的且未定义所需的顺序。

有关事件循环中不同类型的异步操作的内部细节的更多信息,您可以阅读以下参考文献:

Promise.resolve().then vs setImmediate vs nextTick

Promises, Next-Ticks and Immediates— NodeJS Event Loop Part 3

Promises wiggle their way between nextTick and setImmediate

这是上一篇参考文章的引文:

  

本机promise处理程序在与nextTick大致相同的微任务队列上执行,因此它们先于其他所有内容运行。纯JavaScript [promise]实现应使用nextTick进行调度。


对于您的f2()示例,可能fs.readFile()花费了有限的时间,因此f2()不能立即解决,因此无法在同一时间运行setImmediate()使得setImmediate()f2()解析之前就开始运行。

答案 2 :(得分:0)

它的工作原理是这样的,因为Promise是微任务。微任务在宏任务之前在调用堆栈的末尾执行。您可以阅读更多here