- 摘要
本文列出了 Promise.then 的各种执行情况的代码,设计了9道题,通过完成这些题目,你会更加精准的掌握Promise的用法。
你可先做试一试,然后拉到最后边检查答案是否正确。欢迎你在最下方留言出你的答题情况。
如果你全部对了(目前我还没有遇到过),请你一定告知我,让我膜拜一下。
如果你觉得有点难,请你认真看完这篇文章中间那一段,如果还是觉得对promise不熟悉,可以先看看这里。
好吧,先来看看题吧
- 练习
第1题
var log= console.log
var p1 = new Promise((res,rej)=>{
log(1)
res()
})
log(2)
p1.then(()=>{log(3)})
.then(()=>{log(4)})
.then(()=>{log(5)})
第2题
var log= console.log
var p1 = new Promise((res,rej)=>{
log(1)
res()
})
log(2)
p1.then(()=>{log(3)}).then(()=>{log(4)})
p1.then(()=>{log(5)})
第3题
var log= console.log
var p1 = new Promise((res,rej)=>{
log(1)
res()
})
log(2)
p1.then(()=>{log(3)})
log(4)
p1.then(()=>{log(5)})
第4题
var log= console.log
var p1 = new Promise((resolve,reject)=>{
log(1)
resolve()
})
console.log(2)
p1.then(()=>{log(3)}).then(()=>{log(4)})
console.log(5)
p1.then(()=>{log(6)}).then(()=>{log(7)})
第5题
new Promise((res, rej) => {
log(1);
res();
})
.then(() => {
log(2);
return new Promise((res, rej)=>{
log(3);
res();
})
.then(()=>{log(4)})
.then(()=>{log(5)})
})
.then(()=>{log(6)})
第6题
new Promise((res, rej)=>{
log(1);
res();
})
.then(() => {
log(2);
new Promise((res, rej)=>{
log(3);
res();
})
.then(() => {log(4);})
.then(() => {log(5)})
})
.then(() => {
log(6)
})
第7题
new Promise((res, rej)=>{
log(1);
res()
})
.then(()=>{
log(2)
let p = new Promise((res, rej)=>{
log(3)
res()
})
p.then(()=>{log(4)})
p.then(()=>{log(5)})
})
.then(()=>{log(6)})
第8题
let p = new Promise((res, rej) => {
log(1)
res()
})
p.then(() => {
log(2)
new Promise((res, rej) => {
log(3)
res()
})
.then(() => {log(4)})
.then(() => {log(5)})
})
p.then(() => {log(6)})
第9题 终极考题
let log = console.log
new Promise((res, rej) => {
log(1)
res()
})
.then(() => {
log(2);
new Promise((res, rej)=> {
log(3)
res()
})
.then(()=>{log(4)})
.then(()=>{log(5)})
return new Promise((res,rej)=>{
log(6)
res()
})
.then(()=>{log(7)})
.then(()=>{log(8)})
})
.then(()=>{log(9)})
- 如何理解Promise是异步
new Promise是同步的
定义promise的一般格式:
var p1 = new Promise((res,rej)=>{
// 代码...这里的代码是同步
res(1) // res(1)也是同步的
})
示例:
const log = console.log
log(1)
var p1 = new Promise((res,rej)=>{
log(2)
res()
})
log(3)
// 输出:1 2 3
上面的代码中,new 构造器中的代码全是同步执行的。
哪到底哪里是异步的呢?
.then中的函数是异步执行的
const log = console.log
console.log(1)
var p1 = new Promise((res,rej)=>{
log(2)
res()
})
p1.then(()=>{log(3)})
log(4)
// 输出:1 2 4 3
上面的代码中,当new 语句结束时,p1的状态已经从pending扭转到了resolved,所以p1.then()中的函数会被执行,但是,它却在log(4) 之后才执行。所以我们说它是异步的。
现在,我们搞清楚了一件事:.then中的函数是异步执行的,而不能笼统地说Promise是异步的。
.then会让回调进入微任务队列
还是看一段代码
const log = console.log
console.log(1)
var p1 = new Promise((res,rej)=>{
log(2)
res()
})
p1.then(()=>{log(3)}).then(()=>{log(4)})
p1.then(()=>{log(5)})
上面的结果是:1 2 3 4 5 吗?不对!是1 2 3 5 4 . 为啥?
这里先给结论:
.then() 会向 微任务队列做入队操作
整段代码看做宏任务。宏任务结束之后依次执行微任务。
第一步:全部同步任务执行完成(整段代码看做宏任务)。输出 1, 2。p1的状态为resolved,所以log(3)进入微任务,log(5) 也进入微任务。而log(4)所在的then目前还轮到它入队列:原因是p1.then(()=>{log(3)}) 的状态未知。
第二步:依次执行微任务,取出 ()=>{log(3)} 执行,输出 3 。p1.then(()=>{log(3)}) 的返回值 是一个全新的状态为resolved的promise对象,所以log(4)进入队列
第三步:依次执行微任务。输出5, 4。
本质上,先做同步代码,再执行异步代码(又细分宏任务和微任务)。
- 如何理解同步任务,宏任务,微任务
还是看代码:
const log = console.log
log(1)
var p1 = new Promise((res,rej)=>{
log(2)
res()
})
log(3)
setTimeout(()=>{log(4)})
p1.then(()=>{log(5)})
log(6)
结果:1 2 3 6 5 4
分析过程:
整段代码
一个宏任务(整体script)先执行,代码执行的过程中:同步代码立即顺序执行;遇到 异步代码(宏任务或者微任务),就跳过它(不立即执行),视情况把它加入微任务队列和宏任务队列。
一个宏任务中代码全部执行完成(同步代码执行完成,异步代码进入了对应的队列),依次执行微任务队列中的代码;如果微任务队列清空了,则再去进入宏任务队列取下一个宏任务。
常见的宏任务和微任务
宏任务:
script(整体代码)
setTimeout
setInterval
I/O
UI交互事件
postMessage
MessageChannel
setImmediate(Node.js 环境)
微任务:
Promise.then
Object.observe
MutaionObserver
process.nextTick(Node.js 环境)
好了,我们完成上面9道题的基础知识就准备就绪了,可以试一试来完成它们了。
- 答案
这里的答案在chrome浏览器中验证。
第1题
var log= console.log
var p1 = new Promise((res,rej)=>{
log(1)
res()
})
p1.then(()=>{log(3)})
.then(()=>{log(4)})
.then(()=>{log(5)})
log(2)
// 1 2 3 4 5
上面的代码中,三个.then的回调是一个执行完成之后,将后续一个入微任务队列的过程。一个接一个。
第2题
var log= console.log
var p1 = new Promise((res,rej)=>{
log(1)
res()
})
p1.then(()=>{log(3)}).then(()=>{log(4)})
p1.then(()=>{log(5)})
log(2)
// 1 2 3 5 4
先log(3)入微任务队列,然后log(5)入微任务列,执行log(2); 到此,整体script完成;
找出微任务队列中的第一个:log(3)执行,同时激活log(4)的那个then,让log(4)进入队列。
第3题
var log= console.log
var p1 = new Promise((res,rej)=>{
log(1)
res()
})
log(2)
p1.then(()=>{log(3)})
log(4)
p1.then(()=>{log(5)})
// 1 2 4 3 5
第4题
var log= console.log
var p1 = new Promise((resolve,reject)=>{
log(1)
resolve()
})
console.log(2)
p1.then(()=>{log(3)}).then(()=>{log(4)})
console.log(5)
p1.then(()=>{log(6)}).then(()=>{log(7)})
// 1 2 5 3 6 4 7
第5题
new Promise((res, rej) => {
log(1)
res()
})
.then(()=>{
log(2)
return new Promise((res, rej)=>{
log(3)
res()
})
.then(()=>{log(4)})
.then(()=>{log(5)})
})
.then(() => {log(6)})
// 1 2 3 4 5 6
上面的代码中有一个特点:第一个then的回调中又有一个return Promise对象,把代码抽象一下:
Promise对象1.then(()=>{
return Promise对象2
})
.then(val=>{})
第一个then的返回值 (假定叫p3) 是由Promise对象2来决定的:
p3与Promise对象2不是同一对象,但它们的状态和值(promiseState和promiseValue) 是一致的。
所以,上面的代码中 第二个then一定会等到Promise对象2确定下来之后会进入微任务队列。
第6题
new Promise((res, rej) => {
log(1)
res()
})
.then(() => {
log(2)
new Promise((res, rej)=>{
log(3)
res()
})
.then(()=>{log(4)})
.then(()=>{log(5)})
})
.then(() => {
log(6)
})
// 1 2 3 4 6 5
你认真看下第6题与第5题的区别:少了一个return。是吧,其实我们可以把补充一个return undefined, 具体如下 。
new Promise((res, rej)=>{
log(1)
res()
})
.then(() => {
log(2)
new Promise((res, rej) => {
log(3)
res()
})
.then(()=>{log(4)})
.then(()=>{log(5)})
return undefined // 这句是补充的
})
.then(() => {
log(6)
})
如果你不清楚 then(()=>{ return undefined }) 有什么用处,建议回去参考我前面写的Promise-链式用法 。
第7题
new Promise((res, rej) => {
log(1);
res()
})
.then(()=>{
log(2)
let p = new Promise((res, rej)=> {
log(3)
res()
})
p.then(()=>{log(4)})
p.then(()=>{log(5)})
})
.then(()=>{log(6)})
// 1 2 3 4 5 6
第8题
let p = new Promise((res, rej)=> {
log(1)
res()
})
p.then(() => {
log(2)
new Promise((res, rej)=>{
log(3)
res()
})
.then(()=>{log(4)})
.then(()=>{log(5)})
})
p.then(() => {log(6)})
// 1 2 3 6 4 5
第9题 终极考题
let log = console.log
new Promise((res, rej) => {
log(1)
res()
})
.then(() => {
log(2);
new Promise((res, rej) => {
log(3)
res()
})
.then(() => {log(4)})
.then(() => {log(5)})
return new Promise((res, rej)=>{
log(6)
res()
})
.then(() => {log(7)})
.then(() => {log(8)})
})
.then(() => {log(9)})
// 1 2 3 6 4 7 5 8 9
分析过程:
(1)1 是同步代码,先执行;然后 log(2)所在的部分是第一个微任务;
(2)执行这个微任务中,2 3 6 是同步代码这里省略不讲
(3)后续微任务的执行流程如下图(靠左是先进入的,有中划线的表示执行完成的,新加入的在右边):
- 小结
本文列了9道有意设计的题目,希望你会能快速准确的完成这些题目,并掌握背后的原理。
如果对你有帮助,希望你转发,点赞,关注。