在读这篇文章之前可以看一下我之前写的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实例。
其中resolve
、reject
是两个函数,由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)
、reject
、handleData(2, 3)
到微任务事件队列中。
遇到new Promise
,执行结束后,将then方法中的回调函数console.log(4)
添加到微任务事件队列中。
依次执行handleData(0, 1)
、reject
、handleData(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