Promise剖析
Promise的构造方法接收一个executor(),在new Promise()时就立刻执行这个executor回调executor()内部的异步任务被放入宏/微任务队列,等待执行then()被执行,then方法内部做的事情就判断状态,如果状态是成功,调用成功的回调函数。如果状态是失败,调用失败回调函数。then方法是被定义在原型对象中的,负责收集成功/失败回调,放入成功/失败队列。executor()的异步任务被执行,触发resolve/reject,从成功/失败队列中取出回调依次执行。resolve和reject函数是用来更改状态的,resolve: fulfilled,reject: rejected。
Promise A+规范
总结两条核心规则:
- Promise本质是一个状态机,且状态只能为以下三种:Pending(等待态)、Fulfilled(执行态)、Rejected(拒绝态),状态的变更是单向的,只能从Pending -> Fulfilled 或 Pending -> Rejected,状态变更不可逆
- then方法接收两个可选参数,分别对应状态改变时触发的回调。then方法返回一个promise。then 方法可以被同一个 promise 调用多次。
手写Promise
resolve和reject
需要使用箭头函数,目的是为了使this指向调用者。由于resolve/reject是在executor内部被调用,因此需要使用箭头函数固定this指向。在这方法内部,我们会要改变promise状态。
状态定义
我们将状态定义成常量,因为我们会频繁使用它,而常量编辑器会自动提示,方便复用。
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
异步情况
对于异步任务场景,我们可能会多次调用then方法传入多个回调,所以我们会额外定义成功回调队列和失败回调队列,在resolve方法或reject方法执行改变状态时,去取出对应的队列从前往后执行完毕
while (this.successCallback.length) {
const callback = this.successCallback.shift();
callback(this.value);
}
then方法的链式调用
我们思考一下如何实现这种链式调用:
- 显然
.then()需要返回一个Promise,这样才能找到then方法,所以我们会把then方法的返回值包装成Promise。 .then()的回调需要拿到上一个.then()的返回值.then()的回调需要顺序执行,如果中间return了一个Promise,但执行顺序仍要保证是1->2->3。我们要等待当前Promise状态变更后,再执行下一个then收集的回调,这就要求我们对then的返回值分类讨论
对于第一条我们将then方法代码逻辑包装在一个新的Promise中返回来满足。对于第二条我们可以拿到success或fail回调的返回值,通过resolve、reject方法传递给下一个.then方法。
对于第三条我们就需要来分类讨论返回值了。根据实现代码能看出之前一个题目的原理,所以如果链式调用then方法,不传入Promise,那么它就会原样不动以类似val => val形式传递下去。
x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
then链式调用识别Promise对象自返回
正常的Promise当在then方法内返回自身会报错,而我们目前代码没有做这个判断,所以会被执行两次。
解决方法很简单,只需判断返回值和内部创建的Promise对象是否一样就行了。这里有个问题,我们在new Promise对象的过程中,是拿不到thenPromise这个变量的,所以要把这部分代码变为异步。
为什么这样写不报错:
这样写才会报错呢:
想了一下,应该是因为上面的p2接收的是第二个.then所创建返回的Promise对象,在resolvePromise方法中当然会比对不通过了。想想下面返回结果是什么。
捕获错误及 then 链式调用其他状态代码补充
核心是给执行器、then方法的执行语句里加入try catch处理。同时还要处理异步任务的链式调用,我们采用的都是放到setTimeout函数中。这里面大量的用到了同步任务异步任务、闭包等知识,需要好好理解。后期要把使用setTimeout方法变为异步任务的方式优化掉。
处理异步任务
之前在then方法的两个状态中已经处理了异步情况,方式是使用setTimeout将它变为异步任务,这样会等主代码的回调都收集到事件队列后,再开始执行队列中的回调,保证执行顺序。
问题1
在这一节遇到一个问题,为什么错误回调返回的值,会被下个.then方法的成功回调处理?
是因为这句代码进行的处理。
让后面的链式调用能接收前面的错误信息,进行相对应的处理。返回的如果是普通值直接被resolve,是Promise则根据返回结果进行处理。
问题2
为什么在then方法中返回一个Promise对象,能被下一个.then回调拿到返回值?
因为我们是将结果存储到了实例对象的属性中,在.then方法中将this.value或this.reason传入成功/失败回调中。遇到异步任务能保证顺序执行的原因是,异步任务完成后才调用resolve方法,才会继续清空执行成功/失败的任务队列。
将 then 方法的参数变成可选参数
判断successCallback方法是否存在,不存在则默认补充一个val => val。在此还应该判断传入的回调是不是一个函数,不然也返回val => val;
Promise.all 方法的实现
实现方法很简单,详细看代码。它主要解决了异步的并发问题,并且能以异步代码调用顺序拿到结果。它返回的是个Promise对象。
Promise.resolve 方法的实现
Promise.resolve(value)方法返回一个以给定值解析后的Promise 对象。如果该值为promise,返回这个promise;如果这个值是thenable(即带有"then" 方法)),返回的promise会“跟随”这个thenable的对象,采用它的最终状态;否则返回的promise将以此值完成。此函数将类promise对象的多层嵌套展平。
Promise.reject 方法的实现
自己实现的是有缺陷的,参数传入一个Promise对象,和标准Promise执行行为不一致。
下一节附上源码