前言
Promise
对于前端开发来说应该不陌生了,其主要用于在一个异步操作中返回结果值,并且支持链式调用。今天就来讨论一个Promise
链式调用相关的面试题。
说出其打印结果并解释过程
:
Promise.resolve().then(() => {
console.log(0);
return Promise.resolve(4);
}).then((res) => {
console.log(res);
});
Promise.resolve().then(() => {
console.log(1);
}).then(() => {
console.log(2);
}).then(() => {
console.log(3);
}).then(() => {
console.log(5);
}).then(() => {
console.log(6);
})
执行结果:
0
1
2
3
4
5
6
注意,本文涉及异步、微任务和事件循环等知识,如果还不了解这一块的朋友可移步:搞不清楚事件循环,那就看看这篇文章。上述案例主要是涉及了两个知识点:
- 交替执行
then
Promise
状态变化对应的事件循环
交替执行
Promise
可通过new
或者调用类方法resolve()
、reject()
等创建一个实例对象,每一个实例对象都会有then
、catch
、finally
等实例方法。这些方法在调用时会返回一个新生成的promise对象,这就是链式调用的基础。举个栗子:
Promise.resolve().then(() => {
throw new Error('error');
}).catch((e) => console.log(e)).finally(() => console.log('finally')).then(() => console.log('hello'))
运行结果:
上述例子结合使用了then、catch和finally,说明Promise是可以链式调用的。
如果有多个fulfilled(已兑现) 的promise实例,同时执行then链式调用,then会交替执行。这个是编译器做的优化,主要是为了避免某一个promise占用的时间太长。
Promise.resolve().then(() => {
console.log(1);
}).then(() => {
console.log(2);
}).then(() => {
console.log(3);
});
Promise.resolve().then(() => {
console.log(10);
}).then(() => {
console.log(20);
}).then(() => {
console.log(30);
});
运行结果:
运行结果很直接的反应了then是会交替执行的。注意是多个已兑现的promise实例,如果是单个promise实例,则会按照顺序执行,即使有多个then:
const p1 = Promise.resolve(1);
p1.then(res => console.log(res)).then(() => console.log(0))
p1.then(res => console.log(res * 10))
p1.then(res => console.log(res * 100))
p1.then(res => console.log(res * 1000))
运行结果:
这里需要注意:最后打印出来的是0,这是因为事件循环会先执行第一轮的微任务事件,然后才会执行第二轮。
微任务队列和事件循环
在promise实例的then方法中返回一个promise实例,听起来有点绕,看代码就清晰了:
Promise.resolve().then(() => {
return Promise.resolve(100);
}).then((res) => console.log(res)) // 100
每一个promise实例必然存在于pending、fulfilled、rejected三种状态的某一个,上述代码可以解释为两步:
- promise实例有初始的pending状态变为fulfilled状态
- then挂载到微任务队列,在下一轮事件循环中执行
等价于:
Promise.resolve().then(() => {
// 第一步,状态改变
const p = Promise.resolve(100);
// 第二步,添加到队列中
Promise.resolve().then(() => {
p.then(res => console.log(res)); // 100
})
})
看完这个,我们再来看一个复杂点的例子:
Promise.resolve().then(() => {
console.log(1);
return Promise.resolve(2);
}).then((res) => {
console.log(res);
}).then(() => {
console.log(3);
});
Promise.resolve().then(() => {
console.log(10);
}).then(() => {
console.log(20);
}).then(() => {
console.log(30);
}).then(() => {
console.log(40);
});
执行结果:
我们结合事件循环来分析:
- 第一轮事件循环中,先执行打印1,然后实例化promise是一个异步过程,添加到新的事件循环中;打印10
- 第二轮事件循环中,实例化promise并由pending状态转为fulfilled状态;打印20
- 第三轮事件循环中,将promise实例的then添加到微任务队列中,并且在下一次事件循环中执行;打印30
- 第四轮事件循环中,执行promise实例的then方法;打印40
- 第五轮事件循环中,打印3
看懂了这个例子,再回头看面试题,是不是就很好理解了。
总结
虽然是一道简单的面试题,但涉及了异步、事件循环、微任务等知识点。本次面试题还仅仅是对微任务的考量,如果在Promise的过程中嵌套宏任务,或者宏任务中嵌套微任务,那么复杂程度提升的就不是一点点了。在实际开发中,这样混乱的场景应该要避免,否则出现了问题都不知道怎么去排查。
最后,还是总结一下关键的知识点:
- 多个promise实例的then方法是交替执行的
- 在then方法中返回一个promise实例,可能会有两个异步过程:pending状态变为fulfilled、将实例的then方法添加到微任务队列中