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()
回调)中去排队使命。