1.试验

咱们来做个试验。什么实行得更快:当即处理的许诺或当即超时(又称超时0 millisecond)?

Promise.resolve(1).then(function resolve() {
  console.log('Resolved!');
});
setTimeout(function timeout() {
  console.log('Timed out!');
}, 0);
// logs 'Resolved!'
// logs 'Timed out!'

Promise.resolve(1)是一个静态函数,它返回一个当即处理的许诺。setTimeout(callback, 0) ,实行回调,延迟时间为0 millisecond。

翻开演示并查看控制台。你会留意到'Resolved!' 先被记录下来,然后是'Timeout completed!' 。一个当即处理的许诺比一个当即超时的许诺处理得更快。

可能是因为Promise.resolve(true).then(...) 是在setTimeout(..., 0) 之前调用的,所以许诺的处理速度更快? 这个问题很合理。

让咱们略微改动一下试验的条件,先调用setTimeout(..., 0)

setTimeout(function timeout() {
  console.log('Timed out!');
}, 0);
Promise.resolve(1).then(function resolve() {
  console.log('Resolved!');
});
// logs 'Resolved!'
// logs 'Timed out!'

翻开演示,看看控制台。嗯……相同的结果!

试验标明,一个当即处理的许诺会在当即超时之前被处理。最大的问题是…为什么?

2.事情循环

与异步JavaScript有关的问题可以经过研究事情循环来回答。让咱们回顾一下异步JavaScript作业方式的主要组成部分。

留意:假如你对事情循环不熟悉,我建议在进一步阅读之前先看这个视频。

调用仓库是一个LIFO(Last In, First Out)结构,用于存储代码实行过程中创立的实行环境。简略地说,调用仓库实行函数。

网络API是异步操作(获取请求、许诺、定时器)及其回调的地方,等候完成。

使命行列(也叫宏使命)是一个FIFO(先进先出)结构,它保存着预备实行的异步操作的回调。例如,一个超时的setTimeout() 的回调–预备被实行–被排在使命行列中。

使命行列(也被称为微使命)是一个FIFO(先进先出)结构,保存着预备实行的许诺的回调。例如,一个已实行的许诺的解析或拒绝回调被排在作业行列中。

最终,事情循环永久地监视调用栈是否为空。假如调用栈是空的,事情循环会查看作业行列或使命行列,并将任何预备实行的回调排入调用栈。

3.作业行列与使命行列

让咱们再从事情循环的角度看一下这个试验。我将对代码的实行做一个逐步的分析。

A) 调用仓库实行setTimeout(..., 0) ,并安排一个定时器。timeout() 回调被存储在Web APIs中。

setTimeout(function timeout() {
  console.log('Timed out!');
}, 0);
Promise.resolve(1).then(function resolve() {
  console.log('Resolved!');
});

B) 调用仓库实行Promise.resolve(true).then(resolve) ,并安排了一个许诺解析。resolved() 回调存储在Web APIs中。

setTimeout(function timeout() {
  console.log('Timed out!');
}, 0);
Promise.resolve(1).then(function resolve() {
  console.log('Resolved!');
});

C)许诺当即被处理,同时计时器也当即超时。因此,定时器回调timeout()排到使命行列中,许诺回调resolve()排到作业行列中。

D) 现在是有趣的部分:事情循环优先于使命去排队作业。事情循环从作业行列中取消许诺回调resolve() ,并把它放到调用栈中。然后调用仓库实行许诺回调resolve()

setTimeout(function timeout() {
  console.log('Timed out!');
}, 0);
Promise.resolve(1).then(function resolve() {
  console.log('Resolved!');
});

E) 最终,事情循环从使命行列中的定时器回调timeout() ,并放入调用仓库。然后调用仓库实行定时器回调timeout()

setTimeout(function timeout() {
  console.log('Timed out!');
}, 0);
Promise.resolve(1).then(function resolve() {
  console.log('Resolved!');
});

'Timed out!' 被记录到控制台。

调用仓库是空的。脚本的实行已经完成。

4.总结

为什么一个当即处理的许诺比一个当即的定时器处理得快?

因为事情循环优先从作业行列(存储已完成的许诺的回调)中去排队作业,而不是从使命行列(存储已超时的setTimeout() 回调)中去排队使命。