【问题标题】:Strange async code behavior [duplicate]奇怪的异步代码行为[重复]
【发布时间】:2019-01-25 20:22:54
【问题描述】:

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

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

f2() 结果符合预期。先打印'start',然后打印'end'。

f1() 结果不符合预期。先打印'end',然后打印'start'。

有人可以向我解释一下下面代码的结果吗?

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

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

【问题讨论】:

  • 我想知道 Promise 构造函数是否正在简化结果。
  • 那么,您真正要问的是setImmediate().then() 处理程序之间的竞争,即立即解决的承诺?答案是因为不同的异步事物(如 setImmediate() 和 Promise)被编码为工作(微任务)的内部机制。如果您关心两者之间的顺序,请为特定顺序编写代码,而不是依赖该级别的内部设计细节。
  • setImmediate 不幸地被命名为 very,它做任何事情但立即运行代码 - 它实际上在 之后运行它> 所有微任务(promises 和 await)和 after nextTick(它不会在下一个滴答时运行,而是一个微任务)

标签: javascript node.js asynchronous promise


【解决方案1】:

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

因为f1 立即解析,所以setImmediate 的回调和已解析的Promise 都同时添加到事件队列中,但在不同的阶段。已解决的 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

【解决方案2】:

因此,在您的f1() 示例中,您在setImmediate() 和立即解决的承诺的.then() 处理程序之间存在竞争,因为在准备好处理下一个事件时两者都将在事件队列中.

当两者都准备好运行时,一个会先于另一个运行,因为不同的异步事物(如 setImmediate() 和 Promise)在其事件循环的 node.js 实现中被编码为工作原理。在 node.js 的事件循环内部,一些不同类型的异步操作有一个顺序或优先级,如果所有异步操作都在等待执行,则某些操作会先于其他操作执行。虽然很难完全理解哪个先于其他,但它非常复杂,而且它主要是一个实现细节,而不是规范中完整记录的东西。

在这种特定情况下,node.js 中的原生 Promise 使用 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

这是最后一篇参考文章的引述:

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


对于您的 f2() 示例,可能只是 fs.readFile() 需要一些有限的时间,因此 f2() 不会立即解析,因此无法在 setImmediate() 的同时运行setImmediate()f2() 解析之前运行。

【讨论】:

  • 我们实际上绝对保证计时器在承诺之后运行。实际上有一个错误的边缘情况现在不会发生 - 但总的来说,这绝对是 Node.js 项目的意图以及浏览器行为。你仍然不应该依赖它。
  • 另外,我不确定承诺“使用微任务队列”是否属实,实际上有两个微任务队列:Node 和 V8。 Promise 使用 V8 微任务队列 (RunMicrotasks),而 nextTick 队列任务在内部提供。
【解决方案3】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-05-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-17
    • 2015-12-29
    • 1970-01-01
    • 2020-07-28
    相关资源
    最近更新 更多