Javascript 承诺好奇心

Gus*_*ves 97 javascript node.js

当我调用这个 promise 时,输出与函数调用的顺序不匹配。The.then出现在 之前.catch,即使.then之后调用了 with 的承诺。这是什么原因?

const verifier = (a, b) =>
  new Promise((resolve, reject) => (a > b ? resolve(true) : reject(false)));

verifier(3, 4)
  .then((response) => console.log("response: ", response))
  .catch((error) => console.log("error: ", error));

verifier(5, 4)
  .then((response) => console.log("response: ", response))
  .catch((error) => console.log("error: ", error));
Run Code Online (Sandbox Code Playgroud)

输出

node promises.js
response: true
error: false
Run Code Online (Sandbox Code Playgroud)

jfr*_*d00 137

这是一个很酷的问题,可以深入了解。

当你这样做时:

verifier(3,4).then(...)
Run Code Online (Sandbox Code Playgroud)

返回一个新的承诺,在新拒绝的承诺可以运行.catch()随后的处理程序之前,需要另一个循环回到事件循环。额外的循环给出了下一个序列:

verifier(5,4).then(...)
Run Code Online (Sandbox Code Playgroud)

有机会.then()在前一行之前运行其处理程序,.catch()因为.catch()在第一个处理程序进入队列之前它已经在队列中,并且项目以 FIFO 顺序从队列中运行。


请注意,如果您使用.then(f1, f2)表单代替.then().catch(),它会在您期望的时候运行,因为没有额外的承诺,因此不涉及额外的滴答:

请注意,我还标记了所有消息,以便您可以查看verifier()它们来自哪个调用,从而更容易阅读输出。


ES6 规范关于 promise 回调排序和更详细的解释

ES6 规范告诉我们,promise“作业”(因为它从 a.then()或调用回调.catch())根据它们插入作业队列的时间按 FIFO 顺序运行。它没有专门命名 FIFO,但它指定在队列末尾插入新作业,并从队列开头运行作业。这实现了 FIFO 排序。

PerformPromiseThen(从 执行回调.then())将导致EnqueueJob,这就是解析或拒绝处理程序被安排为实际运行的方式。EnqueueJob 指定挂起的作业添加到作业队列的后面。然后NextJob操作从队列的前面拉出项目。这确保了从 Promise 作业队列服务作业的 FIFO 顺序。

因此,在原始问题的示例中,我们获得了 Promise 的回调,verifier(3,4)以及按照verifier(5,4)运行顺序插入作业队列中的Promise的回调,因为这两个原始 Promise 都已完成。然后,当解释器回到事件循环时,它首先接手verifier(3,4)工作。该承诺被拒绝,并且在verifier(3,4).then(...). 因此,它所做的是拒绝verifier(3,4).then(...)返回的承诺并导致将verifier(3,4).then(...).catch(...)处理程序插入到作业队列中。

然后,它返回到事件循环,它从 jobQueue 中提取的下一个作业就是该verifier(5, 4)作业。它有一个已解决的承诺和一个解决处理程序,因此它调用该处理程序。这会导致显示response (5,4):输出。

然后,它返回到事件循环,它从 jobQueue 中提取的下一个作业是verifier(3,4).then(...).catch(...)它运行的作业,这会导致显示error (3,4)输出。

这是因为.catch()第 1 条链中的 承诺级别比.then()导致您报告的排序的第 2 条链中的要深一个承诺级别。而且,这是因为 Promise 链通过作业队列以 FIFO 顺序从一个级别遍历到下一个级别,而不是同步遍历。


关于依赖此级别的调度详细信息的一般建议

仅供参考,一般来说,我尝试编写不依赖于这种详细时序知识水平的代码。虽然理解起来很有趣,偶尔也很有用,但它是脆弱的代码,因为对代码的简单看似无害的更改可能会导致相对时间的更改。因此,如果像这样的两条链之间的时间安排至关重要,那么我宁愿以一种强制按我想要的方式进行时间安排的方式编写代码,而不是依赖于这种级别的详细理解。

  • @slebetman Promises/A+ 没有指定它。ES6 确实指定了它。(不过,ES11 改变了 `await` 的行为)。 (3认同)

小智 50

Promise.resolve()
  .then(() => console.log('a1'))
  .then(() => console.log('a2'))
  .then(() => console.log('a3'))
Promise.resolve()
  .then(() => console.log('b1'))
  .then(() => console.log('b2'))
  .then(() => console.log('b3'))
Run Code Online (Sandbox Code Playgroud)

由于相同的原因,您将看到 a1、b1、a2、b2、a3、b3,而不是输出 a1、a2、a3、b1、b2、b3 - 然后每一个都返回一个承诺,并进入事件循环的末尾队列。所以我们可以看到这个“承诺竞赛”。当有一些嵌套的承诺时也是如此。