执行顺序

那最后我们再来深入了解一下关于Promise执行时序的问题,也就是执行的顺序。

那正如我们一开始所介绍到的,即便说我们Promise当中并没有任何的异步操作,那他的回调函数仍然会进入到回调队列当中去排队,也就是我们必须要等待当前所有的同步代码执行完了之后才会执行promise当中的回调。

当然了这句话其实不是特别的严谨,我们接着往下看。

那这里我们可以先来尝试一下我们先来直接打印一个global start然后我们再去使用Promise.resolve方法,快速创建一个一定会成功的promise, 那这个操作肯定是没有异步调用的,那在这个promise的回调当中我们去打印一个promise字符串。然后我们在最后面也就是最外侧再打印一个global end。

那此时按照我们刚刚的说法,这里的promise他即便没有任何的异步操作,他的回调仍然会异步调用,那也就是说我们这里的打印顺序应该是先打印global start然后再是global end最后才是promise。

console.log('global start')
Promise.resolve().then(() => {
    console.log('promise')
})
console.log('global end')

我们运行发现确实是这样。

那如果我们在promise的后面再去使用链式调用的方式去传递多个回调,那这里每个回调也应该是依次执行。

console.log('global start')
Promise.resolve().then(() => {
    console.log('promise')
}).then(() => {
    console.log('promise2')
}).then(() => {
    console.log('promise3')
})
console.log('global end')

可以发现运行的结果和我们设想的是一样的,每一个回调都是依次运行的。

最后我们再来尝试一下在Promise之前我们先去使用setTimeout去创建一个传统的异步调用,而且我们这里给延迟时间设置0。

console.log('global start')
setTimeout(() => {
    console.log('setTimeout')
}, 0)
Promise.resolve().then(() => {
    console.log('promise')
}).then(() => {
    console.log('promise2')
}).then(() => {
    console.log('promise3')
})
console.log('global end')

那按照我们之前对ES执行过程的分析我们应该知道这里我们使用setTimout传入的回调的函数,他会立即进入到回调队列中排队。因为延迟时间是0,那进入到回调队列中就是等待下一轮执行。

那此时呢如果按照之前的分析,我们这里的执行过程应该是setTimeout函数先进的队列,然后是Promise回调进队列,所以说这里的执行顺序应该是先打印setTimeout然后再去打印promise。

可是我们运行之后发现并不是这样,这里竟然先打印的是promise然后才是setTimeout, 那究竟是为什么呢?

其实这个地方的原因是因为Promise他的异步执行时序会有一点特殊,那要搞明白Promise执行时序问题之前呢我们先来看一个生活当中的场景。

假设我这里去银行柜台办理存款业务,那办完存款过后我突然想办一张信用卡,那这时我肯定直接告诉银行柜员我的这个临时的需求,而银行柜员他为了提高效率以及我的体验或者客户的体验他肯定不会让我再重新排队。

一般他能够帮我办理的就会顺便一起帮我办理了,那这种行为并不属于插队,只是我在完成主要任务过后临时多了些小插曲。

那在这个例子当中我们在柜台排队办理业务就像是js当中回调队列等待执行的那些任务一样。

那我们队伍当中的每一个人他都对应着回调当中的一个任务,也有人会把这种任务称之为宏任务,这只是一个说法而已。

而宏任务执行过程当中有可能会临时加上一些额外的需求,那这时候对于这些临时额外的需求呢可以选择作为一个新的宏任务重新进入到回调队列中去排队,就像我们有了一个新的需求过后我们重新上后面叫号排队一样。

例如我们这里所使用的的setTimeout的回调他就会作为宏任务再次到回调队列当中排队,那也可以像我刚刚的选择一样,我们直接作为当前这个任务的微任务,这个微任务也是我们在js这个领域的一个说法。就是直接在我当前这个任务结束过后就立即去执行而不是到整个队伍的末尾再重新排队。

这就是宏任务和微任务之间的一个差异,那Promise的回调就是作为微任务执行的,所以说他会在本轮任务结束的末尾去自动执行。那这也就是我们这里为什么先打印的Promise然后再打印的setTimeout的一个原因。

因为setTimeout它是以宏任务的形式进入到回调队列的末尾。

那微任务的概念实际上是在后来才被引入到js当中的,那他的目的就是为了提高我们应用的响应能力,就像是我们在生活当中如果说我们柜台他只允许我们重新排队不允许我们再办理过程中加一些额外的需求的话,那对于我们来讲的话整个这个效率其实会有一个大大的降低,那放在程序的角度实际上是一样的。

那我们在编程的过程中接触到的大部分异步调用的API都会作为宏任务进入到回调队列,而Promise对象,还有一个叫做MutationObserever的对象,还有在Node当中有一个process.nextTick那他们都会作为微任务直接在本轮调用的末尾就执行了。