提到异步编程,就会谈到这个家伙:Promise
论如何实现then对值的穿透性
前言
Promise 的本质是一个有限状态机,存在三种状态:
- PENDING(等待)
- FULFILLED(成功)
- REJECTED(失败)
故手写 Promise第一步即是const创建状态变量
对于 Promise 而言,状态的改变不可逆,即由等待态变为其他的状态后,就无法再改变了
- Promise.resolve():返回一个成功的Promise实例
- Promise.reject():返回一个失败的Promise实例
- Promise.finally():无论成功还是失败,都会执行
- Promise.all():全部都成功就返回成功,有一个失败就结束返回失败
- Promise.race():只要有一个结束了就结束,返回状态就是那一个的状态
- Promise.any():只要有一个成功就返回成功,所有失败返回失败
- Promise.allSettled():全部Promise都结束了,才结束,永远是成功状态
在待会手写的Promise中,将会囊括上述几种Promise方法,所以我们需要先对每个方法的功能及用法有个了解
Promise.resolve
的对象是Promise,那么这个Promise作为返回值返回。而且返回的Promise状态变为resolved
1
2
3
4
5
6function fn(resolve, reject){
setTimeout(() =>{ resolve(123) },3000)
}
let p0 = new Promise(fn)
let p1 = Promise.resolve(p0)
console.log(p0 === p1) //true,返回的Promise即是传入的Promise对象在手写过程中,对于resolve方法的实现,我们只需要返回一个新的promise对象,当然是我们自定义的promise
then返回的也是Promise实例,catch也是
1
2
3let then0 = p0.then();
let p2 = Promise.resolve(then0)
console.log(then0 === p2) //true,then返回的是Promise实例then的实现会是手写promise的一大难点,需要考虑两个大问题
首先对于参数的处理,默认参数当然是有成功回调与失败回调,需要考虑当用户不传参数的情况,需要做一些处理,直接返回函数值,用箭头函数来描述即:onFulfilled = val => val, onRejected = err => err
第二个需要考虑的问题就是对于值的穿透性,容易忽略的一点就是我们每次调用then都需要返回一个新的promise对象
如果遇到reject,直接进入catch
catch函数就相当于我们的Rejected状态,相当于我们去调用了一个只包含OnRejected的then函数,格式大概为then(null,OnRejected)
如果catch中有resolve,会进入之后的then
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30function start() {
return new Promise((resolve, reject) => {
resolve(1);
});
}
start()
.then(data => {
// promise p1
console.log('result of p1: ', data);
return Promise.reject(2); // p2
})
.then(data => {
// promise p2
console.log('result of p2: ', data);
return Promise.resolve(3); // p3
})
.catch(ex => {
// promise p3
console.log('error: ', ex);
return Promise.resolve(4); // p4
})
.then(data => {
// promise p4
console.log('result of p4: ', data);
});
// 输出结果
// result of p1: 1
// error: 2
// result of p4: 4catch之后还是可以接then,可以理解成catch的本质就是一个特殊的then
温故知新
Promise如何消灭回调地狱
- 什么是回调地狱
- 多层嵌套函数的问题
- 每种任务的处理结果都存在成功或失败,需要在任务结束后分别处理这两种可能
- 解决方法
- 回调函数延迟绑定:当任务结束后,去then或者catch中执行回调函数
- 返回值穿透:通过then().then()…不断传递,和延迟绑定配合完成链式调用
- 错误冒泡:错误会一直传递,被catch接收到(不管是在任务中还是在then中),不用频繁检查错误(见上面catch例子)
- 解决的效果
- 实现链式调用,解决多层嵌套
- 实现错误冒泡后一站式处理,防止增加代码混乱度
Promise为何要引进微任务(抽象)
Promise的执行函数是同步执行的(传入的函数立即执行),但是里面有异步操作,在异步操作结束后会调用 resolve方法,或者中途遇到错误调用 reject方法,这两者都是作为微任务进入到 EventLoop 中。
微任务中process.nextTick比then先执行
利用微任务解决了两大痛点:
- 使用异步回调替代同步回调解决了浪费CPU性能的问题
- 放到当前宏任务最后执行,解决了回调执行的实时性问题
Promise.all的顺利执行
Promise.all
如果有一个 promise 抛出错误就不能执行then,可以通过在每个promise 中设置 try-catch,当异步语句出错进入catch时强行resolve,这样就可以执行Promise.all
的then方法了。
all方法是全员通过才可,我们可以通过新建结果数组,保存 resolve值,当resolve的长度等于promises数组的长度,则可以顺利执行,相对而言race方法只要有一员通过即可
原生实现Promise
1 | const PENDING = 'pending' |
注意点:
保存回调函数,对应在PENDING状态下调用的then函数,且在保存回调函数的时候需要用setTimeout模拟异步,换言之,在push的时候就要包装要setTimeout来模拟
用setTimeout模拟异步
在then里面,对于REJECTED的状态,我们利用OnRejected拿到值之后,之后要调用resolve(res),拿到了失败的回调结果之后应该继续往下走,我们只有在catch到错误的时候才会去reject(err),待考究
all跟race方法对于错误是一致的,有一个报错就结束。