Promise全攻略(五.在Event Loop中执行顺序)

1,101 阅读7分钟

Promise是异步任务同步化的解决方案?本文讲通过以下几方面来介绍Promise:

  1. Promise的出现为了解决什么问题
  2. Promise内部对任务的处理
  3. Promise的标准规范
  4. Promise的方法使用
  5. Promise在eventLoop中执行顺序
  6. 源码地址

Promise知识分五个章节来阐述,这篇我们讨论:Promise在Event Loop中执行顺序

浅谈Event Loop

由于javaScript是单线程语言,所以我们要用到一个运行机制Event Loop,如果对Event Loop不了解,我会尽快出一篇文章,在我的”浏览器“的专栏中,希望关注阅读。
简单了解下EventLoop中的几个名词:

  • 任务队列(task queue)
  • 执行栈(调用栈)(execution context stack)
  • 同步任务(synchronous)
  • 异步任务(asynchronous)
  • 宏任务(macrotask)
  • 微任务(microtask)
  • 宏任务队列(macrotask queue)
  • 微任务队列(microtask queue) 做些预备工作,简单的说说Event Loop
  • Js解释器会先把代码浏览一遍,把同步任务放到执行栈中执行
  • 异步任务宏任务放到宏任务队列(这里的宏任务中可能包含微任务)中
  • 微任务放入微任务队列
  • 执行栈清空后,Js解释器先找微任务队列中的微任务,再放到执行栈中,
  • 执行栈又被清空且微任务队列没任务时,会查找宏任务队列,再执行宏任务中的同步任务, 循环往复下去,直至所有任务执行完毕。一次循环为一个tick。(至于执行栈是怎么工作,宏任务队列放到哪里挂载,宏任务微任务分别都有哪些 我会专门写一篇文章)。

Promise在Evnet Loop中的角色

我们这次主要针对Promise进行讲解,Promise设计初就是为了异步任务同步化,所以Promise内部执行是同步调用的,会立即执行executor函数,then()也是同步调用。而.then()中的参数,是回调函数,是异步任务异步任务中包含微任务.then()中的回调就是微任务的一员。
由于过于抽象, 我会多举些例子,以便固化总结其中规律,任务队列是先进先出,类似数组,我就当成一个数组来表示,更好理解。
明确了这一点,我们就可以从几个方面下手,看看每一种情况,执行到底是怎样的顺序。

1.基础执行顺序

1.Promise的executor函数是怎么执行的。
console.log('script start');
new Promise((resolve, reject) => {
    console.log('Promise1');
    resolve()
})
console.log('script end');

// script start Promise1 script end

我们可以看出executor函数在执行时,是同步调用的。没有进入任务队列中。

2.then()是怎样调用的。
console.log('script start');
new Promise((resolve, reject) => {
    console.log('Promise1');
    resolve()
}).then(() =>console.log('then1'))
console.log('script end');

// script start Promise1 script end then1

第一轮
current task:script startpromise1script end
micro task queue: [promise1的第一个then]
第二轮
执行 micro task queue第一个任务的同步任务
micro task queue: [promise1的第一个then]
current task:then1
可以看出then()是一个异步任务的但属于微任务。接下来加大难度。

3.链式调用时的执行顺序
console.log('script start');
new Promise((resolve, reject) => {
    console.log('Promise1');
    resolve();
    console.log('状态是resolved');
})
.then(() =>console.log('then1'))
.then(() =>console.log('then2'))
console.log('script end');

// script start Promise1 状态是resolved script end then1 then2

第一轮
current task:script startpromise1状态是resolvedscript end
micro task queue: [promise1的then1]
第二轮
执行 promise1的第一个then
current task:then1
micro task queue: [promise1的then2]
第三轮
执行 promise1的第二个then
current task:then2
micro task queue: []
链式调用时 then()每次生成一个新的promise,然后执行then(),在执行的过程中不断向微任务队列中压入新的任务,因此直至微任务队列清空后才会执行下一波的微任务,继续加大难度。

4.如果分别调用呢
console.log('script start');
let p = new Promise((resolve, reject) => {
    console.log('Promise1');
    resolve()
})
p.then(() =>console.log('then1'))
p.then(() =>console.log('then2'))
console.log('script end');

// script start Promise1 script end then1 then2

其实执行顺序跟第三个是一样的,但是有个小知识点,涉及到源码。其实分别调用时.then中的函数回调是push在一个数组中的。链式调用每个then()返回一个新的Promise,每个then()中的回调都有属于自己的微任务。分别调用时是批量执行的,不会被EventLoop其他函数插队。

5.executor中执行宏任务
console.log('script start');
let p = new Promise((resolve, reject) => {
    setTimeout(()=>{
        console.log('Promise1');
    },1000)
    resolve()
}).then(() =>console.log('then1'))
console.log('script end');

// script start script end  then1 Promise1  

第一轮
current task:script start script end
micro task queue: [promise1的then1]
macro task queue: [promise1的setTimeout]
第二轮
current task:then1
micro task queue: []
macro task queue: [promise1的setTimeout]
第三轮
current task:Promise1
micro task queue: []
macro task queue: []
先执行微任务队列,当队列为空时,再执从宏任务队列找。

2.多次嵌套执行顺序

1.then()函数中执行Promise
console.log('script start');
new Promise((resolve,reject)=>{
    console.log("promise1")
    resolve()
}).then(()=>{
    console.log("then1-1")
    new Promise((resolve,reject)=>{
        console.log("promise2")
        resolve()
    }).then(()=>{
        console.log("then2-1")
    }).then(()=>{
        console.log("then2-2")
    })
}).then(()=>{
    console.log("then1-2")
})
console.log('script end');

//script start promise1 script end then1-1 promise2 then2-1 then1-2 then2-2

第一轮
current task:script startpromise1script end
micro task queue: [promise1中的then1-1]
第二轮
current task:then1-1promise2
micro task queue: [promise2的then2-1,promise1的then1-2]
第三轮
current task:then2-1then1-2
micro task queue: [promise2的then2-2]
第四轮
current task:then2-2
micro task queue: [] 再看一个例子,再总结规律。

2.then()函数中返回一个Promise
console.log('script start');
new Promise((resolve,reject)=>{
    console.log("promise1")
    resolve()
}).then(()=>{
    console.log("then1-1")
    return new Promise((resolve,reject)=>{
        console.log("promise2")
        resolve()
    }).then(()=>{
        console.log("then2-1")
    }).then(()=>{
        console.log("then2-2")
    })
}).then(()=>{
    console.log("then1-2")
})
console.log('script end');

//script start promise1 script end then1-1 promise2 then2-1 then2-2 then1-2

第一轮
current task:script startpromise1script end
micro task queue: [promise1中的then1-1]
第二轮
current task:then1-1promise2
micro task queue: [promise2的then2-1]
第三轮
current task:then2-1
micro task queue: [promise2的then2-2,promise1的then1-2]
第四轮
current task: then2-2then1-2
micro task queue: []
通过这两个例子我们可以先确定:
1.Promise的then无论连续调用几次,总是第一个then进入微任务队列
2.在then中执行Promise,由于第一个规律,只向微任务队列压入当前promise2的第一个then,继续向下执行,压入promise1的第二个then。而then中返回Promise的情况,由于是要整体返回一个内部的Promise,所以执行时,promise2的then必须全部调用完毕,才能交给promise1的第二个then使用。
再加点难度巩固一下。

3.then()函数中创建一个Promise并再new一个Promise
console.log('script start');
new Promise((resolve,reject)=>{
    console.log("promise1")
    resolve()
}).then(()=>{
    console.log("then1-1")
    new Promise((resolve,reject)=>{
        console.log("promise2")
        resolve()
    }).then(()=>{
        console.log("then2-1")
    }).then(()=>{
        console.log("then2-2")
    })
}).then(()=>{
    console.log("then1-2")
})
new Promise((resolve,reject)=>{
    console.log("promise3")
    resolve()
}).then(()=>{
    console.log("then3-1")
})
    
console.log('script end');

//script start promise1 promise3 script end then1-1 promise2 then3-1 then2-1 then1-2 then2-2 

第一轮
current task:script startpromise1promise3script end
micro task queue: [promise1中的then1-1,promise3中的then3-1]
第二轮
current task:then1-1promise2then3-1
micro task queue: [promise2的then2-1,promise1的then1-2]
第三轮
current task:then2-1then1-2
micro task queue: [promise2的then2-2]
第四轮
current task: then2-2
micro task queue: []
最终执行顺序与我们总结规律是一致的,遇到promise,都会向微任务队列推入第一个then,再执行微任务队列,直至清空队列,开启下一轮循环。再来一个变态的,巩固一下。

4.executor加入宏任务结合多种情况的.then
console.log('script start');
new Promise(resolve => {
  setTimeout(()=>{
    console.log('setTimeout');
    new Promise(resolve => {
     resolve();
    }).then(() => console.log('setTimeoutthen'))
  })
  resolve();
}).then(() => {
    //then1-1
    new Promise(resolve => {
      resolve();
    })
    .then(() => console.log('then2-1'))
    .then(() => console.log('then2-2'));
}).then(() => {
    //then1-2
    new Promise(resolve => {
    //promise3
        resolve()
    }).then(() => {
    //then3-1
        new Promise((resolve) => {
            resolve()
        }).then(() => console.log('then4-1'))
    }).then(() => console.log('then3-2'))
}).then(() => console.log('then1-3'));

console.log('script end');
//script start script end then2-1 then2-2 then1-3 then4-1 then3-2 setTimeout setTimeoutthen

别慌,心中有两个任务队列,微任务队列宏任务队列,一点点去分析。
第一轮
current task:script startscript end
micro task queue: [Promise1的then1-1]
macro task queue: [setTimeout]
第二轮
current task:无输出
micro task queue: [Promise2的then2-1,Promise1的then1-2]
macro task queue: [setTimeout]
第三轮
current task:then2-1
micro task queue: [Promise2的then2-2,Promise3的then3-1,Promise1的then1-3]
macro task queue: [setTimeout]
第四轮
current task:then2-2,then1-3
micro task queue: [Promise4的then4-1,Promise3的then3-2]
macro task queue: [setTimeout]
第五轮
current task:then4-1,then3-2
micro task queue: []
macro task queue: [setTimeout]
第六轮
current task:setTimeout
micro task queue: []
macro task queue: [setTimeout中的then]
第七轮
current task:setTimeoutthen
micro task queue: []
macro task queue: []
通过这个例子我们可以看到,无论promise1,then()内部怎么运转,在每次轮询中,都是会顺序的吧then1-1,then1-2,then1-3依次压入微任务队列,then1,2内部的promise2,3,4也会根据then1,2的执行顺序开始。

留在最后 到这里基本Promise在EventLoop中执行的顺序就差不多了,但是还有一个关键的是async/await加上Promise,他们之间的顺序是怎样的?说async/await又离不开generator函数,这可能还要拿出一片文章去探讨。
这次文章引发了两个还需关注的点:
1.浏览器的EventLoop。
2.深入了解async/awaitgenerator
更多知识请关注我,等我更新这两篇文章。


如果此文章对您有帮助或启发,那便是我的荣幸