promise是一个基于原型实现的类,可以被链式then调用,是业务中解决异步的常用措施
前言
关于promise的基础语法,可以参考我两年前写的不成熟小文以及蹩脚的使用示例,promise相关的常见面试题也可以参考这里
规范或定义
术语
promise:类实例,通过new获取,接受一个可选的函数类型参数
thenable:一个可链式调用的函数,用于获取promise的值
value:成功的返回值,对应resolve函数
reson:失败的原因,对应rejected
exception:异常,对应throw
三种状态:pending、fulfilled、rejected
pending:初始状态,可通过resolve或rejected修改状态
fulfilled:成功状态,由resolve触发
rejected:失败状态,由reject触发
then
用于获取上一个promise的值,包括成功和失败
onFulfilled和onRejected仅在状态改变时被执行一次,且这两个函数应该是微任务(queueMicrotask)
可被多次注册多次执行,当状态改变时需要按注册的先后顺序执行
支持链式调用
回调函数传出的结果应当进行解析保持格式统一
当回调过程中throw error时,统一适用reject拒绝
若回调不是函数,应当向上取值
解析parsePromise
接受四个参数:promise,x,resolve,reject
若promise===x,则reject掉
若x是promise
当前是pending状态,等待
当前是fulfilled状态,返回该value
当前是rejected状态,返回该reson
若为对象
避免报错try...catch
若为函数
修正this指向,并递归解析
否则
返回x
实现
初始化
定义三种状态标识:pending、fulfilled、rejected
定义辅助函数isFunc判断是否传入的是函数,对于传入的onReject或onFulfilled如果不为函数应该直接返回value或reason
定义成功和失败的返回结果容器value和reason
定义status存储当前的promise状态
定义resole和reject接口,用于修改promise的状态,该状态仅在status为pending时执行
接收入参,应当立即调用,并将resolve和reject函数抛出以等待合适的时机修改status
定义then函数,并作简单的容错处理,若传入的不是函数类型,则包装一个默认函数;为了实现链式调用,在最后返回一个新的MyPromise实例
由于new Promise是同步任务,故存在直接修改状态的情况,即在new Promise的回调中直接调用resolve或reject。那么在.then返回的promise中需要返回上一次的值,即this.value或this.reason
示例
同样的,如果第一个new Promise回调时,也可能是执行了异步逻辑如宏任务微任务又或是ajax,此时直接调用.then由于未调用resolve或reject仍为pending,无法获取上一次的结果,需要等待状态改变后再计算结果抛出;且.then可被注册多次,每一级的then都需要等待上一次的then返回的promise的结果。故使用数组先将它们存起来等待调用时机
由于只有第一个会被阻塞,故只需要监听第一个status由pending变为fulfilled或rejected即可
示例
除了同步和异步,还可能在回调中写了错误的代码,对于同步代码,其在constructor中会被直接执行,而异步逻辑则在监听到status时遍历执行,故异常处理应当有两处
示例
示例
注意
必须显示的调用resolve,否则不会触发status的改变,从而.then无法执行
在setTimeout中书写的错误无法也不应当在类内部捕获
为了不阻塞代码,需要将resolve和reject的执行修改为微任务
示例
resolve和reject作为函数,允许存在返回值,返回值将透传到下一个then中。需要对返回值作进一步解析
若返回值是实例本身,则应当报循环引用错误(a返回的直接是实例本身,也可能a返回的是一个Promise,但是该Promise返回的是a)
若返回值是MyPromise实例,则应该对其解析获取其resolve或reject的值
若为对象且非null,则尝试取then,并解析其resolve或reject(对上一个递归求值)
否则直接返回
对于catch而言,也是返回一个promise以链接调用。且该方法专门用于获取reason。恰好,then方法也是用来获取value和reason的,也恰好返回是新的promise
原生promise也支持不new的方式创建一个promise实例,对应静态方法resolve和reject
原生的race的表现是,接收多个promise,只要有一个状态先改变了,那么race的结果就是这个已经改变的。需要注意的是:
传入的值可能不是一个promise,需要调用静态的resolve方法将其转为promise
需要使用.then进行求值,当上一个promise(使用resolve包装过的)为异步时第二个会放入回调队列中等待
race也是一个promise,当存在一个先执行完毕时,直接执行race的resolve求值即可
原生的all的表现是,所有的promise均为fulfilled时,返回,否则为rejected
all的逻辑其实和race有点像,只不过这里是取的rejected的值
定义数组存储then成功数,全部执行时执行resolve
parsePromise的bug
promise.all的问题
直接arr.push会导致数据的非一一对应,可以通过arr[i]的形式来存储每一个异步对应的结果,同时另外使用变量count作为结束条件