这是一个很酷的问题.
当你这么做的时候:
verifier(3,4).then(...)
这将返回一个新的promise ,该promise 需要另一个循环返回到事件循环,然后新拒绝的promise 才能运行后面的.catch()
处理程序.这个额外的循环给出了下一个序列:
verifier(5,4).then(...)
有可能在前一行的.catch()
之前运行其.then()
处理程序,因为在第一行的.catch()
处理程序进入队列之前,它已经在队列中,并且项目以FIFO顺序从队列中运行.
请注意,如果您使用.then(f1, f2)
表格代替.then().catch()
表格,它会在您预期的时间运行,因为没有额外的promise ,因此不涉及额外的勾号:
const verifier = (a, b) =>
new Promise((resolve, reject) => (a > b ? resolve(true) : reject(false)));
verifier(3, 4)
.then((response) => console.log("response (3,4): ", response),
(error) => console.log("error (3,4): ", error)
);
verifier(5, 4)
.then((response) => console.log("response (5,4): ", response))
.catch((error) => console.log("error (5,4): ", error));
请注意,我还标记了所有消息,以便您可以看到它们来自哪个verifier()
个呼叫,这使得读取输出更加容易.
ES6 Spec on promise callback ordering and more detailed explanation
ES6规范告诉我们,promise"jobs"(它从.then()
或.catch()
调用回调)是根据它们插入作业(job)队列的时间以FIFO顺序运行的.它没有明确命名FIFO,但它指定在队列末尾插入新作业(job),并从队列开头运行作业(job).它实现了FIFO排序.
PerformPromiseThen(执行.then()
的回调)将导致EnqueueJob,这就是解析或拒绝处理程序计划实际运行的方式.EnqueueJob指定将挂起的作业(job)添加到作业(job)队列的后面.然后,NextJob操作将该项目从队列前面拉出来.这确保了从Promise作业(job)队列服务作业(job)时的FIFO顺序.
所以,在原始问题的例子中,我们得到了verifier(3,4)
promise 和verifier(5,4)
promise 的回调,它们按运行顺序插入到作业(job)队列中,因为这两个原始promise 都完成了.然后,当解释器返回到事件循环时,它首先 Select verifier(3,4)
个作业(job).这个promise 被拒绝了,而且在verifier(3,4).then(...)
年也没有回电.因此,它所做的是拒绝verifier(3,4).then(...)
返回的promise ,并将verifier(3,4).then(...).catch(...)
处理程序插入到jobQueue中.
然后,它返回到事件循环,它从jobQueue中提取的下一个作业(job)是verifier(5, 4)
作业(job).它有一个已解析的promise 和一个解析处理程序,所以它调用该处理程序.这会导致显示response (5,4):
输出.
然后,它返回到事件循环,它从jobQueue中提取的下一个作业(job)是它运行的verifier(3,4).then(...).catch(...)
个作业(job),这会导致显示error (3,4)
个输出.
这是因为第一条链中的.catch()
比第二条链中的.then()
更深一级,这导致了您报告的订单.而且,这是因为promise 链是通过作业(job)队列以FIFO顺序从一个级别传递到下一个级别的,而不是同步的.
General Recommendation About Relying on This Level of Scheduling Detail
仅供参考,一般来说,我会try 编写不依赖于这种详细计时知识的代码.虽然理解它很奇怪,有时也很有用,但它是脆弱的代码,因为对代码进行简单的看似无害的更改可能会导致相对时间的更改.因此,如果计时在这样的两个链之间是至关重要的,那么我宁愿以一种方式来编写代码,强制按照我想要的方式计时,而不是依赖于这种详细的理解.