ES6中Promise到底是怎么执行的🔥

900 阅读26分钟

在读这篇文章之前可以看一下我之前写的JavaScript到底是怎么执行的🔥。在里面已经有简单的提到Promise在JavaScript的执行情况。

那么我们来点稍微复杂的题目看一下。

new Promise((resolve) => {
    resolve();
})
.then(() => console.log(1))
.then(() => console.log(2))
.then(() => console.log(3))

new Promise((resolve) => {
    resolve();
})
.then(() => console.log(4))
.then(() => console.log(5))
.then(() => console.log(6))
参考答案
1
4
2
5
3
6

在看一个更复杂的题目。

function promise1() {
    console.log('promise1 start');
    const p = promise2();
    return new Promise((resolve) => {
        Promise.resolve().then(() => {
            p.then(resolve)
        })
    }).then(() => {
        console.log('promise1 end')
    });
}

function promise2() {
    console.log('promise2 start');
    return Promise.resolve(console.log('promise2 end'));
}

promise1();

new Promise((resolve) => {
    console.log(1)
    resolve()
}).then(() => {
	console.log(2)
}).then(() => {
	console.log(3)
}).then(() => {
	console.log(4)
})
参考答案
promise1 start
promise2 start
promise2 end
1
2
3
promise1 end
4

还是老样子,如果大家能答对,那可以关掉页面了。

一、Promise的语法

要了解ES6中Promise到底是怎么执行的,首先要对其语法非常熟悉,才能分析其到底是怎么执行的。下面简单介绍一下。

1、Promise对象

Promise对象是一个构造函数,其接受一个函数(resolve , reject) =>{...},来生成Promise实例。

其中resolvereject是两个函数,由JavaScript引擎提供,不用自己部署。

Promise 对象有三个状态,Pending(进行中)、Fulfilled(已成功)、Rejected(已失败)。

使用resolve()从Pending变成Fulfilled,使用reject()从Pending变成Rejected,一旦状态改变了就不可逆转。

2、Promise实例方法

Promise有两个实例方法,Promise.prototype.then()Promise.prototype.catch

then方法的作用是为Promise实例状态改变时添加回调函数,其有两个参数,

第一个参数是状态为已成功的的回调函数,回调函数的参数通过resolve(res)从Promise中传递出来。

第二参数(可选)是状态为已失败的回调函数,回调函数的参数通过reject(err)从Promise中传递出来。

catch方法其实是Promise.prototype.then(null,rejectCallback)的别名,用于状态为已失败时添加回调函数。

因为then方法返回的是一个新的Promise实例,不是原来那个Promise实例。所以可以采用链式调用,前一个then中回调函数的返回值可以作为参数传给后一个then中回调函数。

举个例子,给大家感受一下。

function foo(a){
    return new Promise(
        (resolve,reject) =>{
            if(a>10){
                resolve('符合要求')
            }else{
                reject('不符合要求')
            }
        }
    )
}

foo(10).then(
    (res) =>{
        console.log(res);
        return '我前一个then中回调函数的返回值'
    }
).then(
    (res) =>{
        console.log(res);
        return '我后一个then中回调函数的返回值'
    }
).catch(
    (err) =>{
        console.log(err)
    }
)

3、Promise对象的方法

1、Promise.resolve()

这个方法比较重要,大家需要认真地理解透了。

该方法的作用就是将传入的参数转换成Promise对象,但是参数类型不同,处理结果也不同。

  • 参数是一个Promise实例

    Promise.resolve()不做任何处理,直接返回这个Promise实例

  • 参数是一个thenable对象

    thenable对象是指具有then方法的对象。例:

    let foo={
        then:function(resolve,reject){
            resolve (42);
        }
    }
    

    Promise.resolve()会将这个thenable对象转成Promise对象,然后立即执行then方法。

    let bar = Promise.resolve(foo)
    bar.then(
        (res) =>{
            console.log(res)
        }
    )
    

    Promise.resolve()把foo这个thenable对象转成Promise对象并赋值给bar后,立即执行thenable对象中then方法,thenable对象中then方法执行后,对象bar的状态就变为resolved,从而立即执行后面的then方法指定的已成功的回调函数,输出 42。

  • 参数不是具有 then 方法的对象或根本不是对象

    返回一个新的Promise对象,状态为resolved,回调函数会立即执行。

    let p = Promise.resolve('Hello');
    p.then(
        (res) =>{
            console.log(res)//Hello
        }
    )
    
  • 不带任何参数

    返回一个新的Promise对象,状态为resolved,回调函数会立即执行。

    Promise.resolve().then(() =>{console.log('立即执行')})
    

二、分析第一道题目

Promise语法讲到这里,我们先分析一下第一道题目,运用一下,加深理解。

new Promise((resolve) => {
    resolve();
})
.then(() => console.log(1))
.then(() => console.log(2))
.then(() => console.log(3))

new Promise((resolve) => {
    resolve();
})
.then(() => console.log(4))
.then(() => console.log(5))
.then(() => console.log(6))

为什么会输出1,4,2,5,3,6,而不是1,2,3,4,5,6

如果你理解了上面所讲的Promise实例方法Promise.prototype.then()返回的是一个新的Promise实例,不是原来那个Promise实例。

JavaScript到底是怎么执行的🔥中有讲过,Promise是微任务,其实被添加到微任务事件队列中是Promise实例方法then()里面的回调函数。

我们先上面代码改写一下。在借助一张图来分析为什么会输出1,4,2,5,3,6

new Promise((resolve) => {
    resolve();
}).then(() => {
    console.log(1);
    return Promise.resolve();
}).then(() => {
    console.log(2);
    return Promise.resolve();
}).then(() => {
    console.log(3)
    return Promise.resolve();
})

new Promise((resolve) => {
    resolve();
}).then(() => {
    console.log(4);
    return Promise.resolve();
}).then(() => {
    console.log(5);
    return Promise.resolve();
}).then(() => {
    console.log(6)
    return Promise.resolve();
})

  • 任务开始执行。遇到new Promise,执行结束后,状态为已成功,判断为微任务,故将then方法里面已成功的回调函数console.log(1);return Promise.resolve();添加到微任务事件队列,我们将它记为then1。
  • 遇到另一个new Promise,执行结束后,状态为已成功,判断为微任务,故将then方法里面已成功的回调函数console.log(4);return Promise.resolve();添加到微任务事件队列,我们将它记为then4。
微任务Event Queue
then1
then4
  • 上表是此时微任务Event Queue 的情况。
  • 询问微任务事件队列中有没有任务要执行的,有then1,执行,遇到console.log(1),输出1,遇到return Promise.resolve(),执行结束后,状态为已成功,判断为微任务,故将then方法里面已成功的回调函数console.log(2);return Promise.resolve();添加到微任务事件队列,我们将它记为then2。
微任务Event Queue
then4
then2
  • 上表是此时微任务Event Queue 的情况。此时,已经输出了1
  • 再询问微任务事件队列中有没有任务要执行的,有then4,执行,遇到console.log(4),输出4,遇到return Promise.resolve(),执行结束后,状态为已成功,判断为微任务,故将then方法里面已成功的回调函数console.log(5);return Promise.resolve();添加到微任务事件队列,我们将它记为then5。
微任务Event Queue
then2
then5
  • 上表是此时微任务Event Queue 的情况。此时,已经输出了1,4
  • 再询问微任务事件队列中有没有任务要执行的,有then2,执行,遇到console.log(2),输出2,遇到return Promise.resolve(),执行结束后,状态为已成功,判断为微任务,故将then方法里面已成功的回调函数console.log(3);return Promise.resolve();添加到微任务事件队列,我们将它记为then3。
微任务Event Queue
then5
then3
  • 上表是此时微任务Event Queue 的情况。此时,已经输出了1,4,2
  • 再询问微任务事件队列中有没有任务要执行的,有then5,执行,遇到console.log(5),输出5,遇到return Promise.resolve(),执行结束后,状态为已成功,判断为微任务,故将then方法里面已成功的回调函数console.log(6);return Promise.resolve();添加到微任务事件队列,我们将它记为then6。
微任务Event Queue
then3
then6
  • 上表是此时微任务Event Queue 的情况。此时,已经输出了1,4,2,5
  • 再询问微任务事件队列中有没有任务要执行的,有then3,执行,遇到console.log(3),输出3,遇到return Promise.resolve(),执行结束后,没有微任务。
微任务Event Queue
then6
  • 上表是此时微任务Event Queue 的情况。此时,已经输出了1,4,2,5,3
  • 再询问微任务事件队列中有没有要执行的,有then6,执行,遇到console.log(6),输出6,遇到return Promise.resolve(),执行结束后,没有微任务。
  • 询问宏任务事件队列中有没有任务要执行,没有,任务执行结束了。输出1,4,2,5,3,6

三、变动第一道题再分析

new Promise((resolve) => {
    resolve();
}).then(() => {
    console.log(1);
    setTimeout(() => {console.log('setTimeout1');},0)
}).then(() => {
    console.log(2);
    setTimeout(() => {console.log('setTimeout2');},0)
}).then(() => {
    console.log(3)
    setTimeout(() => {console.log('setTimeout3');},0)
})

new Promise((resolve) => {
    resolve();
}).then(() => {
    console.log(4);
    setTimeout(() => {console.log('setTimeout4');},0)
}).then(() => {
    console.log(5);
    setTimeout(() => {console.log('setTimeout5');},0)
}).then(() => {
    console.log(6)
    setTimeout(() => {console.log('setTimeout6');},0)
})
参考答案
1
4
2
5
3
6
setTimeout1
setTimeout4
setTimeout2
setTimeout5
setTimeout3
setTimeout6

这道题和上面不同的是,增加了setTimeout(() => {)},0)这个定时器在then的回调函数中。如果你理解了微任务和宏任务的执行顺序,这道题就很简单了。下面我们来分析一下。

  • 任务开始执行。遇到new Promise,执行结束后,状态为已成功,判断为微任务,故将then方法里面已成功的回调函数console.log(1);setTimeout(() => {console.log('setTimeout1');},0)添加到微任务事件队列,我们将它记为then1。
  • 遇到另一个new Promise,执行结束后,状态为已成功,判断为微任务,故将then方法里面已成功的回调函数console.log(4);setTimeout(() => {console.log('setTimeout4');},0)添加到微任务事件队列,我们将它记为then4。
微任务Event Queue
then1
then4
  • 上表是此时Event Queue 的情况。
  • 询问微任务事件队列中有没有任务要执行的,有then1,
    • 执行,遇到console.log(1),输出1。
    • 遇到setTimeout(() => {console.log('setTimeout1');},0),执行结束后,判断为宏任务,故将里面的回调函数console.log('setTimeout1');添加到宏任务事件队列,我们将它记为setTimeout1。
    • 因then方法固定返回一个新Promise,执行,状态为已成功,任务判断为微任务,故将then方法里面已成功的回调函数console.log(2);setTimeout(() => {console.log('setTimeout2');},0)添加到微任务事件队列,我们将它记为then2。
微任务Event Queue 宏任务Event Queue
then4 setTimeout1
then2
  • 上表是此时Event Queue 的情况。此时已经输出了1
  • 询问微任务事件队列中有没有任务要执行的,有then4,
    • 执行,遇到console.log(4),输出4。
    • 遇到setTimeout(() => {console.log('setTimeout4');},0),执行结束后,判断为宏任务,故将里面的回调函数console.log('setTimeout4');添加到宏任务事件队列,我们将它记为setTimeout4。
    • 因then方法固定返回一个新Promise,执行,状态为已成功,任务判断为微任务,故将then方法里面已成功的回调函数console.log(5);setTimeout(() => {console.log('setTimeout5');},0)添加到微任务事件队列,我们将它记为then5。
微任务Event Queue 宏任务Event Queue
then2 setTimeout1
then5 setTimeout4
  • 上表是此时Event Queue 的情况。此时已经输出了1,4
  • 询问微任务事件队列中有没有任务要执行的,有then2,
    • 执行,遇到console.log(2),输出2。
    • 遇到setTimeout(() => {console.log('setTimeout2');},0),执行结束后,判断为宏任务,故将里面的回调函数console.log('setTimeout2');添加到宏任务事件队列,我们将它记为setTimeout2。
    • 因then方法固定返回一个新Promise,执行,状态为已成功,任务判断为微任务,故将then方法里面已成功的回调函数console.log(3);setTimeout(() => {console.log('setTimeout3');},0)添加到微任务事件队列,我们将它记为then3。
微任务Event Queue 宏任务Event Queue
then5 setTimeout1
then3 setTimeout4
setTimeout2
  • 上表是此时Event Queue 的情况。此时已经输出了1,4,2
  • 询问微任务事件队列中有没有任务要执行的,有then2,
    • 执行,遇到console.log(5),输出5。
    • 遇到setTimeout(() => {console.log('setTimeout5');},0),执行结束后,判断为宏任务,故将里面的回调函数console.log('setTimeout5');添加到宏任务事件队列,我们将它记为setTimeout5。
    • 因then方法固定返回一个新Promise,执行,状态为已成功,任务判断为微任务,故将then方法里面已成功的回调函数console.log(6);setTimeout(() => {console.log('setTimeout6');},0)添加到微任务事件队列,我们将它记为then6。
微任务Event Queue 宏任务Event Queue
then3 setTimeout1
then6 setTimeout4
setTimeout2
setTimeout5
  • 上表是此时Event Queue 的情况。此时已经输出了1,4,2,5
  • 询问微任务事件队列中有没有任务要执行的,有then3,
    • 执行,遇到console.log(3),输出3。
    • 遇到setTimeout(() => {console.log('setTimeout3');},0),执行结束后,判断为宏任务,故将里面的回调函数console.log('setTimeout3');添加到宏任务事件队列,我们将它记为setTimeout3。
    • 因then方法固定返回一个新Promise,执行,任务判断为微任务,当后续已经没有then方法了,所以不再向微任务事件队列添加事件了。
微任务Event Queue 宏任务Event Queue
then6 setTimeout1
setTimeout4
setTimeout2
setTimeout5
setTimeout3
  • 上表是此时Event Queue 的情况。此时已经输出了1,4,2,5,3
  • 询问微任务事件队列中有没有任务要执行的,有then6,
    • 执行,遇到console.log(6),输出6。
    • 遇到setTimeout(() => {console.log('setTimeout6');},0),执行结束后,判断为宏任务,故将里面的回调函数console.log('setTimeout6');添加到宏任务事件队列,我们将它记为setTimeout6。
    • 因then方法固定返回一个新Promise,执行,任务判断为微任务,当后续已经没有then方法了,所以不再向微任务事件队列添加事件了。
微任务Event Queue 宏任务Event Queue
setTimeout1
setTimeout4
setTimeout2
setTimeout5
setTimeout3
setTimeout6
  • 上表是此时Event Queue 的情况。此时已经输出了1,4,2,5,3,6
  • 询问微任务事件队列中有没有任务要执行的,没有。
  • 询问宏任务事件队列中有没有任务要执行的,有setTimeout1。
    • 执行,遇到console.log(setTimeout1),输出setTimeout1。
  • 询问微任务事件队列中有没有任务要执行的,没有。
  • 询问宏任务事件队列中有没有任务要执行的,有setTimeout4。
    • 执行,遇到console.log(setTimeout4),输出setTimeout4。
  • 询问微任务事件队列中有没有任务要执行的,没有。
  • 询问宏任务事件队列中有没有任务要执行的,有setTimeout2。
    • 执行,遇到console.log(setTimeout2),输出setTimeout2。
  • 询问微任务事件队列中有没有任务要执行的,没有。
  • 询问宏任务事件队列中有没有任务要执行的,有setTimeout5。
    • 执行,遇到console.log(setTimeout5),输出setTimeout5。
  • 询问微任务事件队列中有没有任务要执行的,没有。
  • 询问宏任务事件队列中有没有任务要执行的,有setTimeout3。
    • 执行,遇到console.log(setTimeout3),输出setTimeout3。
  • 询问微任务事件队列中有没有任务要执行的,没有。
  • 询问宏任务事件队列中有没有任务要执行的,有setTimeout6。
    • 执行,遇到console.log(setTimeout6),输出setTimeout6。
  • 最后输出1,4,2,5,3,6,setTimeout1,setTimeout4,setTimeout2,setTimeout5,setTimeout3,setTimeout6

到这里。是不是对宏任务和微任务的执行机制更加清楚。

四、分析第二道题

function promise1() {
    console.log('promise1 start');
    const p = promise2();
    return new Promise((resolve) => {
        Promise.resolve().then(() => {
            p.then(resolve)
        })
    }).then(() => {
        console.log('promise1 end')
    });
}

function promise2() {
    console.log('promise2 start');
    return Promise.resolve(console.log('promise2 end'));
}

promise1();

new Promise((resolve) => {
    console.log(1)
    resolve()
}).then(() => {
	console.log(2)
}).then(() => {
	console.log(3)
}).then(() => {
	console.log(4)
})
  • 任务开始执行。遇到promise1(),执行,
    • 遇到console.log('promise1 start');,执行,输出promise1 start
    • 遇到const p = promise2();,执行,
      • 遇到console.log('promise2 start');,执行,输出promise2 start
      • 遇到return Promise.resolve(console.log('promise2 end')),执行,输出promise2 end
    • 遇到下面代码,执行
      return new Promise((resolve) => {
          Promise.resolve().then(() => {
              p.then(resolve)
          })
      }).then(() => {
          console.log('promise1 end')
      });
      
      • 遇到Promise.resolve(),执行,执行结束后,状态为已成功,任务判断为微任务,故将then方法里面已成功的回调函数p.then(resolve)添加到微任务事件队列,记为then1。
      • 此时,要特别注意了。Promise对象是在其状态变为Fulfilled,也可以叫做已完成时,才会执行then方法中的回调函数,因为Promise中的p.then(resolve)被添加到微任务事件队列中,暂时无法执行。那怎么办,卡在这边了。Js引擎会让其跳出来继续执行其他代码。
  • 跳出promise1()继续执行,遇到new Promise,执行,
    • 遇到console.log(1),执行,输出1。
    • 遇到resolve(),执行,执行结束后,状态为已成功,任务判断为微任务,故将then方法里面已成功的回调函数console.log(2)添加到微任务事件队列,记为then2。
微任务Event Queue
then1
then2
  • 上表是此时Event Queue 的情况。此时已经输出了promise1 start,promise2 start,promise2 end,1
  • 询问微任务事件队列中有没有任务要执行的,有then1,执行
    • 遇到p,p是一个状态为Fulfilled的Promise对象,执行结束,状态为已成功,任务判断为微任务,故将then方法里面已成功的回调函数添加到微任务事件队列中,但是你会发现,这个then方法只有一个参数,没有任何回调函数。那么此时还能添加到微任务事件队列吗。这时你应该记起来then方法返回的是一个新的Promise实例,所有then(resolve)相当then((resolve) =>{return Promise.resolve()})故把return Promise.resolve()添加到微任务事件队列中,记为then3。
微任务Event Queue
then2
then3
  • 上表是此时Event Queue 的情况。此时已经输出了promise1 start,promise2 start,promise2 end,1
  • 询问微任务事件队列中有没有任务要执行的,有then2,执行
    • 遇到console.log(2) 执行,输出2。
    • 因then方法固定返回一个新Promise,执行,执行结束,状态为已成功,任务判断为微任务,故将then方法里面已成功的回调函数console.log(3);添加到微任务事件队列,我们将它记为then4。
微任务Event Queue
then3
then4
  • 上表是此时Event Queue 的情况。此时已经输出了promise1 start,promise2 start,promise2 end,1,2
  • 询问微任务事件队列中有没有任务要执行的,有then3,执行
    • 遇到return Promise.resolve() 执行。
    • 此时下面代码中的(resolve) => { Promise.resolve().then(() => { p.then(resolve) }) } 已经都执行完毕了,状态为已成功,任务判断为微任务,故将then方法里面已成功的回调函数console.log('promise1 end');添加到微任务事件队列,我们将它记为then5。
      return new Promise((resolve) => {
          Promise.resolve().then(() => {
              p.then(resolve)
          })
      }).then(() => {
          console.log('promise1 end')
      });
      
微任务Event Queue
then4
then5
  • 上表是此时Event Queue 的情况。此时已经输出了promise1 start,promise2 start,promise2 end,1,2
  • 询问微任务事件队列中有没有任务要执行的,有then4,执行
    • 遇到console.log(3) 执行,输出3。
    • 因then方法固定返回一个新Promise,执行,执行结束,状态为已成功,任务判断为微任务,故将then方法里面已成功的回调函数console.log(4);添加到微任务事件队列,我们将它记为then6。
微任务Event Queue
then5
then6
  • 上表是此时Event Queue 的情况。此时已经输出了promise1 start,promise2 start,promise2 end,1,2,3
  • 询问微任务事件队列中有没有任务要执行的,有then5,执行
    • 遇到console.log('promise1 end'); 执行,输出promise1 end。
    • 因then方法固定返回一个新Promise,执行,任务判断为微任务,但后续没then方法了,所有没有新的微任务添加了。
微任务Event Queue
then6
  • 上表是此时Event Queue 的情况。此时已经输出了promise1 start,promise2 start,promise2 end,1,2,3,promise1 end
  • 询问微任务事件队列中有没有任务要执行的,有then6,执行
    • 遇到console.log(4); 执行,输出4。
    • 因then方法固定返回一个新Promise,执行,任务判断为微任务,但后续没then方法了,所有没有新的微任务添加了。
  • 任务都执行结束,最后输出promise1 start,promise2 start,promise2 end,1,2,3,promise1 end,4

五、小结

以上内容虽然很繁琐,如果你耐心看完,就会发现:

  • 要理解Promise到底是怎么执行的,首先要深刻理解Promise的语法。
    • then方法的作用是什么?
    • 比如Promise怎么向then方法中的回调函数传参?
    • 比如then方法的参数是什么,then方法的返回值是什么?
    • Promise.resolve()方法的作用,返回值是什么。
  • 在配合下图。一步一步分析就可以得出正确答案。

在看完上面几道题的分析,是不是感觉对微任务和宏任务的执行机制更加理解了。

六、Promise中奇怪的thenable对象

上文提到过,thenable对象是指带有then方法的对象。那么在Promise中遇到thenable对象,是怎么执行的。下面用两种情况来阐述。

1、Promise.resolve(thenable)

let foo = {
    then: function(resolve, reject) {
        console.log(1)
    	resolve(2);
    }
}
let bar = Promise.resolve(foo)
bar.then((res) => {
    console.log(res)
})
new Promise((resolve) => {
    console.log(3)
    resolve()
}).then(() => {
    console.log(4)
}).then(() => {
    console.log(5)
})
参考答案
3
1
4
2
5
  • 任务开始执行。遇到let bar = Promise.resolve(foo),执行结束后,立即执行foo中then方法,执行结束,状态为已成功,任务判断为微任务,故将then方法里面已成功的回调函数console.log(1) resolve(2);添加到微任务事件队列,我们将它记为then1。
  • 遇到另一个new Promise,执行
    • 遇到console.log(3),执行,输出3。
    • 遇到resolve()结束后,状态为已成功,任务判断为微任务,故将then方法里面已成功的回调函数console.log(4)添加到微任务事件队列,我们将它记为then2。
微任务Event Queue
then1
then2
  • 上表是此时Event Queue 的情况。此时已经输出了3
  • 询问微任务事件队列中有没有任务要执行的,有then1,执行
    • 遇到console.log(1); 执行,输出1。
    • 遇到resolve(2),执行结束,状态为已成功,任务判断为微任务,故将then方法里面已成功的回调函数console.log(res)添加到微任务事件队列,我们将它记为then3。
微任务Event Queue
then2
then3
  • 上表是此时Event Queue 的情况。此时已经输出了3,1
  • 询问微任务事件队列中有没有任务要执行的,有then2,执行
    • 遇到console.log(4); 执行,输出4。
    • 因then方法固定返回一个新Promise,执行,执行结束,状态为已成功,任务判断为微任务,故将then方法里面已成功的回调函数console.log(5);添加到微任务事件队列,我们将它记为then4。
微任务Event Queue
then3
then4
  • 上表是此时Event Queue 的情况。此时已经输出了3,1,4
  • 询问微任务事件队列中有没有任务要执行的,有then3,执行
    • 遇到console.log(res); 执行,其中res是由resolve(2)传递过来,故输出2。
    • 因then方法固定返回一个新Promise,执行,任务判断为微任务,但后续没then方法了,所有没有新的微任务添加了。
微任务Event Queue
then4
  • 上表是此时Event Queue 的情况。此时已经输出了3,1,4,2
  • 询问微任务事件队列中有没有任务要执行的,有then3,执行
    • 遇到console.log(5); 执行,输出5。
    • 因then方法固定返回一个新Promise,执行,任务判断为微任务,但后续没then方法了,所有没有新的微任务添加了。
  • 任务都执行结束,最后输出3,1,4,2,5

以上可以得出。Promise.resolve(thenable).then()执行时,要先执行thenable里面的then方法,将其添加到微任务队列中。

2、resolve(thenable)

let foo = {
    then: function(resolve, reject) {
        console.log(1)
        resolve(2);
    }
}
new Promise((resolve) => {
    console.log('3');
    resolve(foo);
}).then(() => {
    console.log(4)
})
new Promise((resolve) => {
    console.log(5)
    resolve()
}).then(() => {
    console.log(6)
}).then(() => {
    console.log(7)
})
参考答案
3
5
1
6
4
7
  • 任务开始执行。遇到new Promise,执行,
    • 遇到console.log('3');执行,输出3。
    • 遇到resolve(foo);执行,因为foo是thenable对象,故先转成Promise对象后执行,遇到then方法console.log(1) resolve(2);,判断为微任务,将其添加到微任务事件队列中,记为then1。
    • 此时,resolve(foo);执行被挂起,new Promise记为Promise1的状态没有变成已成功,故先跳出,执行其他。
  • 遇到另一个new Promise,执行
    • 遇到console.log(5),执行,输出5。
    • 因then方法固定返回一个新Promise,执行,执行结束,状态为已成功,任务判断为微任务,故将then方法里面已成功的回调函数console.log(6);添加到微任务事件队列,我们将它记为then2。
微任务Event Queue
then1
then2
  • 上表是此时Event Queue 的情况。此时已经输出了3,5
  • 询问微任务事件队列中有没有任务要执行的,有then1,执行
    • 遇到console.log(1),执行,输出1。
    • 遇到resolve(2),执行,执行结束。此时Promise1的状态变成已成功,判断其为微任务,故将then方法里面的回调函数console.log(4);添加到微任务事件队列,我们将它记为then3。
微任务Event Queue
then2
then3
  • 上表是此时Event Queue 的情况。此时已经输出了3,5,1
  • 询问微任务事件队列中有没有任务要执行的,有then2,执行-
    • 遇到console.log(6),执行,输出6。
    • 因then方法固定返回一个新Promise,执行,执行结束,状态为已成功,任务判断为微任务,故将then方法里面已成功的回调函数console.log(7);添加到微任务事件队列,我们将它记为then4。
微任务Event Queue
then3
then4
  • 上表是此时Event Queue 的情况。此时已经输出了3,5,1,6
  • 询问微任务事件队列中有没有任务要执行的,有then3,执行
    • 遇到console.log(4),执行,输出4。
    • 因then方法固定返回一个新Promise,执行,任务判断为微任务,但后续没then方法了,所有没有新的微任务添加了。
微任务Event Queue
then4
  • 上表是此时Event Queue 的情况。此时已经输出了3,5,1,6,4

  • 询问微任务事件队列中有没有任务要执行的,有then4,执行

    • 遇到console.log(7),执行,输出7。
    • 因then方法固定返回一个新Promise,执行,任务判断为微任务,但后续没then方法了,所有没有新的微任务添加了。
  • 任务都执行结束,最后输出3,5,1,6,4,7

以上可以得出。resolve(thenable)执行时,要先把thenable转成Promise对象,然后立即执行thenable里面的then方法,将其添加到微任务队列中。

七、Promise.all()的执行

还是老样子,先了解一下Promise.all()的语法

1、Promise.all()的语法

1、Promise.all()的作用

用于将多个Promise实例包装成一个新的Promise实例。

2、Promise.all()的参数

接收一个数组,或者具有Iterator接口的类数组,作为参数。

若参数数组中的一项不是Promise对象的实例。会调用Promise.resolve()将其转为Promise对象的实例。

3、Promise.all()生成的Promise实例的状态

let p = Promise.all([p1,p2,p3])

p的状态是有p1,p2,p3三者决定。分以下两种情况

  • p1,p2,p3三者状态都为Fulfilled时,p的状态才有Fulfilled(已成功)
  • p1,p2,p3三者状态只有一个变为Rejected时,p的状态就会变成Rejected(已失败)

4、Promise.all()的then方法中回调函数的参数传递

当p的状态为Fulfilled,p1,p2,p3的返回值会组成一个数组,传递给p的then方法中回调函数。

5、Promise.all()的catch方法中回调函数的参数传递

当p的状态为Rejected,p1,p2,p3中谁的状态先变为Rejected,就将其返回值,传递给p的catch方法中回调函数。

6、Promise.all()的catch方法捕捉错误的情况

  • 参数数组中的p1,p2,p3后面都没有catch方法。

    此时,如果p1,p2,p3其中只有一个变为Rejected,Promise.all()的catch方法马上就会捕捉到其错误。

  • 参数数组中的p1,p2,p3其中一个后面有catch方法。

    如果p1后面有catch方法,当p1的状态变为Rejected,Promise.all()的catch方法不会捕捉到p1的错误。因为p1的错误被本身的catch捕捉,从而p1的状态变为Fulfilled,Promise.all()的then方法中回调函数会被调用。

2、第一道题目

let p1 = new Promise((resolve, reject) => {
    resolve(1)
})
let p2 = new Promise((resolve, reject) => {
    resolve(2)
})
let p3 = new Promise((resolve, reject) => {
    resolve(3)
})
Promise.all([p1, p2, p3]).then(res => {
    console.log(res)
})

new Promise((resolve) => {
    resolve();
})
.then(() => console.log(4))
.then(() => console.log(5))
.then(() => console.log(6))
参考答案
4
[1, 2, 3]
5
6

这道题很简单,难点是为什么[1, 2, 3]4之后输出。按照往常的思路来分析。

先执行Promise.all([p1, p2, p3]),执行结束后就把then方法中回调函数console.log(res)添加到微任务事件队列中。

后执行new Promise((resolve) => { resolve();}),执行结束后把then方法中的回调函数console.log(4)添加到微任务事件队列中。

按此分析,那么[1, 2, 3]4之前输出,但是实际并非如此,为什么?

如果你理解了Promise.all()是怎么实现的。就明白了为什么[1, 2, 3]4之后输出。下面用一段代码来模拟一下Promise.all()的实现。

Promise.all = function(promiseArrs) { //在Promise类上添加一个all方法,接受一个传进来的promise数组
    return new Promise((resolve, reject) => { //返回一个新的Promise
        let arr = []; //定义一个空数组存放结果
        let i = 0;
        function handleData(index, data) { //处理数据函数
            arr[index] = data;
            i++;
            if (i === promiseArrs.length) { //当i等于传递的数组的长度时 
                resolve(arr); //执行resolve,并将结果放入
            }
        }
        for (let i = 0; i < promiseArrs.length; i++) { //循环遍历数组
            promiseArrs[i].then((data) => {
                handleData(i, data); //将结果和索引传入handleData函数
            }, reject)
        }
    })
}

从上面代码来看,Promise.all()中将每个参数Promise对象实例遍历循环执行,将其then方法中的回调函数添加到微任务事件队列中。

那么执行完Promise.all()后,微任务事件队列中就有三个事件了handleData(0, 1)handleData(1, 2)handleData(2, 3)

又因为在Promise.all()中必须等每个参数Promise对象实例的状态都变成Fulfilled,才能将后面的then方法中成功的回调函数console.log(res)添加到微任务事件队列中。

而程序继续执行,遇到new Promise,执行结束后,将then方法中的回调函数console.log(4)添加到微任务事件队列中。

接下来依次执行微任务事件队列中的事件,handleData(0, 1)handleData(1, 2)handleData(2, 3)都执行结束后,可以把Promise.all()后面then方法中的回调函数console.log(res)添加到微任务事件队列中。

继续执行微任务事件队列中的console.log(4),输出4。再执行微任务事件队列中的console.log(res),输出[1, 2, 3]

[1, 2, 3]4之前输出。

2、第二道题目

let p1 = new Promise((resolve, reject) => {
    resolve(1)
})
let p2 = new Promise((resolve, reject) => {
    reject(2)
})
let p3 = new Promise((resolve, reject) => {
    resolve(3)
})
Promise.all([p1, p2, p3]).then(res => {
    console.log(res)
}).catch(err =>{
    console.log(err)
})

new Promise((resolve) => {
    resolve();
})
.then(() => console.log(4))
.then(() => console.log(5))
.then(() => console.log(6))
参考答案
4
5
2
6

此道题的难点是在为什么2会在5之后输出。

遇到Promise.all([p1, p2, p3]),执行中,在其内部添加handleData(0, 1)rejecthandleData(2, 3)到微任务事件队列中。

遇到new Promise,执行结束后,将then方法中的回调函数console.log(4)添加到微任务事件队列中。

依次执行handleData(0, 1)rejecthandleData(2, 3)微任务。执行到reject时,Promise.all([p1, p2, p3])的状态变为已失败,将then方法中已失败的回调函数添加到微任务事件队列中记为then1。

执行console.log(4)微任务,输出4,因then方法固定返回一个新Promise,执行,状态为已成功,任务判断为微任务,故将then方法里面已成功的回调函数console.log(5);添加到微任务事件队列记为then2。

执行then1微任务,因then方法固定返回一个新Promise,执行,状态为已失败,任务判断为微任务,故将catch方法里面的回调函数console.log(err);添加到微任务事件队列记为then3。

执行then2微任务,输出5,因then方法固定返回一个新Promise,执行,状态为已成功,任务判断为微任务,故将then方法里面已成功的回调函数console.log(6);添加到微任务事件队列记为then4。

执行then3微任务,输出2。

执行then4微任务,输出6.

3、第三道题目

let p1 = new Promise((resolve, reject) => {
    resolve(1)
})
let p2 = new Promise((resolve, reject) => {
    reject(2)
}).catch(err =>{
    console.log(err)
})
let p3 = new Promise((resolve, reject) => {
    resolve(3)
})
Promise.all([p1, p2, p3]).then(res => {
    console.log(res)
}).catch(err =>{
    console.log(err)
})

new Promise((resolve) => {
    resolve();
})
.then(() => console.log(4))
.then(() => console.log(5))
.then(() => console.log(6))
参考答案
2
4
5
[1, undefined, 3]
6