promise实现以及最详细的注解(完美符合A+规范)
promise是目前前端解决异步的最好方式,解决了回调地狱的痛点,开发中常用的async ,await 也是promise的语法糖。而真正想要搞明白promise,最好的方式是实现一遍promise,本文会讲解promise每一处实现。
promise我自己实现了也有4-5次了,一开始就是抄,到了后面会自己思考哪一步是否多余,到最后发现一开始抄的其实已经是精华了,但是新人一下子看promise实现,将近200行一定会产生畏惧,而我也从头开始写一个promise,来帮助大家理解,顺便加强自己的记忆。
1. promise简介
-
“promise” is an object or function with a
thenmethod whose behavior conforms to this specification. -
“thenable” is an object or function that defines a
thenmethod. -
“value” is any legal JavaScript value (including
undefined, a thenable, or a promise). -
“exception” is a value that is thrown using the
throwstatement. -
“reason” is a value that indicates why a promise was rejected.
这是A+规范上的promise定义,我来总结一下:
promise是一个对象或者方法,有一个then方法挂载在上面。
promise有一个value。
promise有一个reason,说明为什么rejected
2. promise实现 part1
在A+规范上,promise实现它分为了2.1,2.2,2.3,三个part,大致上可以实现3个函数去实现3个part。但是在具体实现里每个函数里的功能可能会发生重叠。
我会在代码里备注每一处规范,并且在代码后进行总结。
const PENDING = 'pending'
const FULFILLED = 'fulfuilled'
const REJECTED = 'rejected'
function Promise(executor) {
// 这里必须要将this,给保存下来。
const self = this;
// 每一个promise初始状态为pending
self.status = PENDING;
// 这里是记录回调函数的方法。用处之后会说
self.fulfiledCallbacks = [];
self.rejectedCallbacks = [];
// 定义resolve方法, resolve方法接受一个参数,会将promise的value置为这个参数。
function resolve(value) {
// 确保只有当PENDING时才会执行resolve
if (self.status === PENDING) {
// 修改此时的promise status 确保只会执行一次resolve
self.status = FULFILLED
self.value = value
// 执行then方法的函数
self.fulfiledCallbacks.forEach(fn => fn())
}
}
// 定义reject方法, reject方法接受一个参数,会将promise的reason置为这个参数。
function reject(reason) {
if (self.status === PENDING) {
self.status = REJECTED
self.reason = reason
self.rejectedCallbacks.forEach(fn => fn())
}
}
try {
// 当执行executor 这个传入的方法时,如果报了异常,需要捕捉到并且reject出来。
executor(resolve,reject)
} catch(e) {
reject(e)
}
}
上面的代码虽然每一行都有注释,但是第一次阅读promise相关代码的话一定会有疑问。
- const self = this; 为什么要记录保存
fulfiledCallbacks和rejectedCallbacks是干什么的?
逐一分析一下:
-
这里的this是什么呢?
举一个例子 const promise = new Promise()的话,那么
this指向的就是promise这个被我们new出来的对象,然而我们是知道promise是用来解决异步的,在异步执行时,this指的就不是这个对象了。最典型的例子:
function test() { this.a = 1 } test.prototype.func1 = function() { setTimeout(this.func2) } test.prototype.func2 = function() { console.log(this.a); } const test1 = new test(); test1.func1(); // undefined test1.func2(); // 1在
setTimeout中的方法,this会指向window,这就是为什么要一开始储存this的定义的原因了,因为this会变,而储存成变量后则不会变。 -
这里用到的一个思想是函数队列。
当出现下面这种情况:
const promise = new Promise() promise.then(() => {}) promise.then(() => {}) promise.then(() => {})一个promise如果被多次调用then方法,如果一开始promise的状态是pending,那我们就应该把then里面的函数给储存下来,当promise的状态改变后,执行对应的函数。
这是符合A+规范中的2.2.6。
2.2.6
thenmay be called multiple times on the same promise.- If/when
promiseis fulfilled, all respectiveonFulfilledcallbacks must execute in the order of their originating calls tothen. - If/when
promiseis rejected, all respectiveonRejectedcallbacks must execute in the order of their originating calls tothen.
- If/when
这样的话,我们是把part1给写完了。
3. promise实现 part2
// then方法要接受2个参数,分别是两个方法,当promise是成功或者失败时调用的方法
Promise.prototype.then = function(onFulfilled, onRejected) {
// 同样要保存this
const self = this;
// 判断onfulfilled是否是函数,如果是函数,那就不用修改,否则的话,按照2.2.7.3的规则,给什么就返回什么,但是仍然是个函数。
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
// 判断onRejected是否是函数,如果是函数,那就不用修改。
onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason};
// 无论如何then方法都会返回一个promise,也就是返回这个promise2,
const promise2 = new Promise((resolve, reject) => {
// 当promise的当前状态是完成时
if (self.status === FULFILLED) {
// 我们用setTimeout来模拟then方法,执行onFulfilled方法或者执行onRejected方法都是异步的过程。
setTimeout(() => {
// 这里一定要用try来捕捉异常,如果当在执行onFulfilled or onRejected,出现异常时规范中要求promise2 reject异常。
try {
// 这里会执行onFulfilled方法,接受promise的value作为参数,至于为什么储存下来以及执行resolvePromise方法会在之后说。
let x = onFulfilled(self.value)
resolvePromise(promise2,x, resolve, reject);
} catch(e) {
reject(e)
}
})
}
if (self.status === REJECTED) {
setTimeout(() => {
try {
let x = onRejected(self.reason);
resolvePromise(promise2, x, resolve, reject);
} catch(e) {
reject(e)
}
})
}
if (self.status === PENDING) {
// 如果promise的状态是pending,就应该按照之前所说将其储存到回调队列里。
self.fulfiledCallbacks.push(
// 需要注意,这里需要用函数包裹起来setTimeout方法,因为我们放进去的是一个函数,之后当状态改变调用这个函数也就是setTimeout异步函数。
() => {
setTimeout(() => {
try {
let x = onFulfilled(self.value)
resolvePromise(promise2,x, resolve, reject);
} catch(e) {
reject(e)
}
})
}
)
// 逻辑同上
self.rejectedCallbacks.push(
() => {
setTimeout(() => {
try {
let x = onRejected(self.reason);
resolvePromise(promise2, x, resolve, reject);
} catch(e) {
reject(e)
}
})
}
)
}
})
// 返回这个对象
return promise2;
}
then方法的疑问点应该主要在于resolvePromise这个function,这个function其实是在part3中实现的。
这个方法出现的目的其实就是为了根据规范去确定promise2,也就是promise.then()究竟返回什么样的一个promise2,这个promise2的状态究竟是什么样的。
4. promise实现 part3
// 接受一个promise,x,x为执行onFulfilled or onRejected方法的值,同时为了实现方便,传入promise的resolve与reject
function resolvePromise(promise2, x, resolve, reject) {
// 如果promise2 与 x 指的是一个对象,也就是说onFulfilled or onRejected返回的是promise2,那显然会出现一个闭环的情况,无限递归,因此需要抛出异常。
if (promise2 === x) {
throw TypeError('chain error');
}
// 这里就是判断x是否是object或者是function
if (x && typeof x === 'object' || typeof x === 'function') {
// 声明一个变量 used,确保执行一次后就更改used,之后如果再次执行,used为true直接跳过。
let used = false;
// 这里必须得用try来捕捉异常,无论是取x.then的异常还是calling then的异常
try {
// 先将then取出来,这时候需要捕捉异常
let then = x.then;
// 如果then是一个方法
if (typeof then === 'function') {
// 完全遵循2.3.3.3的规则 If then is a function, call it with x as this, first argument resolvePromise, and second argument rejectPromise
then.call(x, (y) => {
// 注意在这里,如果used,也就是说已经执行过then.call ,那么就会忽视这段代码
if (used) {
return ;
}
used = true;
resolvePromise(promise2, y, resolve, reject);
}, (r) => {
if (used) {
return ;
}
used = true;
reject(r);
})
}
// 如果then不是一个方法,那么直接resolve(x)
else {
resolve(x);
}
} catch(e) {
// 这里同样也需要判断,这点容易忽视, 因为这里不只是判断x.then抛出的异常,也有可能是在执行then.call的时候出现的异常,如果已经执行过resolvePromise or rejectPromise, 就应该忽视下面的
if (used) {
return ;
}
used = true;
reject(e);
}
} else {
// 如果都不是,那就是一个基础类型,那么用resolve将其包裹起来返回
resolve(x);
}
}
part3主要就是解决链式调用中 promise2的状态。
如果用心的copy代码逐行分析,问题应该不会很大了,我来举一个需要用到used的地方,相信大家就能彻底明白这个函数为什么这样实现了。
const promise = new Promise((resolve,reject) => {
resolve(1);
})
const promise2 = promise.then((value) => {
// 创建一个函数
const fun = function() {
}
// 这个函数上有then方法,传递进去的两个参数是resolve和reject。
fun.prototype.then = function fun1(resolve, reject){
resolve(1)
reject(2);
}
// 返回这个构造函数创建的对象
const obj = new fun();
return obj;
})
setTimeout(() => {
console.log(promise2); // Promise { 1 }, 是会忽视reject(2)的 如果调换顺序就会打印出reject 2.
})
这里一定要理解在实现当中,我们用的then.call的形式调用这个方法,是传递进去两个函数,这两个函数分别有一个y参数和r参数。而在定义的then方法中resolve,和reject就是两个方法,会在我们的实现里以then.call的形式调用,在定义的then方法中,调用resolve或reject方法就是对应的在实现中的2个参数,请务必搞清楚这其中的关系。(我也是实现了多次后才搞明白,js的函数参数非常的绕)
虽然上面的resolvePromise看上去非常复杂,但是实际上如果把used的相关逻辑去掉,其他的代码几乎都是按照规范上的规则进行书写的。**(没有自己的想法,无情的码字机器)**之后可以继续深究为什么规范是这样定义的,有什么好处。
5. 总结
promise的实现已经完成了,我的实现也是参考了许多人的版本,最后实现了多次,并且几乎每一行代码都带有自己的理解。
贴一下所有代码,而promise自带的许多方法,在之后会实现。
const PENDING = 'pending'
const FULFILLED = 'fulfuilled'
const REJECTED = 'rejected'
function Promise(executor) {
// 这里必须要将this,给保存下来。
const self = this;
// 每一个promise初始状态为pending
self.status = PENDING;
// 这里是记录回调函数的方法。用处之后会说
self.fulfiledCallbacks = [];
self.rejectedCallbacks = [];
// 定义resolve方法, resolve方法接受一个参数,会将promise的value置为这个参数。
function resolve(value) {
// 确保只有当PENDING时才会执行resolve
if (self.status === PENDING) {
// 修改此时的promise status 确保只会执行一次resolve
self.status = FULFILLED
self.value = value
// 执行then方法的函数
self.fulfiledCallbacks.forEach(fn => fn())
}
}
// 定义reject方法, reject方法接受一个参数,会将promise的reason置为这个参数。
function reject(reason) {
if (self.status === PENDING) {
self.status = REJECTED
self.reason = reason
self.rejectedCallbacks.forEach(fn => fn())
}
}
try {
// 当执行executor 这个传入的方法时,如果报了异常,需要捕捉到并且reject出来。
executor(resolve,reject)
} catch(e) {
reject(e)
}
}
// then方法要接受2个参数,分别是两个方法,当promise是成功或者失败时调用的方法
Promise.prototype.then = function(onFulfilled, onRejected) {
// 同样要保存this
const self = this;
// 判断onfulfilled是否是函数,如果是函数,那就不用修改,否则的话,按照2.2.7.3的规则,给什么就返回什么,但是仍然是个函数。
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
// 判断onRejected是否是函数,如果是函数,那就不用修改。
onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason};
// 无论如何then方法都会返回一个promise,也就是返回这个promise2,
const promise2 = new Promise((resolve, reject) => {
// 当promise的当前状态是完成时
if (self.status === FULFILLED) {
// 我们用setTimeout来模拟then方法,执行onFulfilled方法或者执行onRejected方法都是异步的过程。
setTimeout(() => {
// 这里一定要用try来捕捉异常,如果当在执行onFulfilled or onRejected,出现异常时规范中要求promise2 reject异常。
try {
// 这里会执行onFulfilled方法,接受promise的value作为参数,至于为什么储存下来以及执行resolvePromise方法会在之后说。
let x = onFulfilled(self.value)
resolvePromise(promise2,x, resolve, reject);
} catch(e) {
reject(e)
}
})
}
if (self.status === REJECTED) {
setTimeout(() => {
try {
let x = onRejected(self.reason);
resolvePromise(promise2, x, resolve, reject);
} catch(e) {
reject(e)
}
})
}
if (self.status === PENDING) {
// 如果promise的状态是pending,就应该按照之前所说将其储存到回调队列里。
self.fulfiledCallbacks.push(
// 需要注意,这里需要用函数包裹起来setTimeout方法,因为我们放进去的是一个函数,之后当状态改变调用这个函数也就是setTimeout异步函数。
() => {
setTimeout(() => {
try {
let x = onFulfilled(self.value)
resolvePromise(promise2,x, resolve, reject);
} catch(e) {
reject(e)
}
})
}
)
// 逻辑同上
self.rejectedCallbacks.push(
() => {
setTimeout(() => {
try {
let x = onRejected(self.reason);
resolvePromise(promise2, x, resolve, reject);
} catch(e) {
reject(e)
}
})
}
)
}
})
// 返回这个对象
return promise2;
}
// 接受一个promise,x,x为执行onFulfilled or onRejected方法的值,同时为了实现方便,传入promise的resolve与reject
function resolvePromise(promise2, x, resolve, reject) {
// 如果promise2 与 x 指的是一个对象,也就是说onFulfilled or onRejected返回的是promise2,那显然会出现一个闭环的情况,无限递归,因此需要抛出异常。
if (promise2 === x) {
throw TypeError('chain error');
}
// 这里就是判断x是否是object或者是function
if (x && typeof x === 'object' || typeof x === 'function') {
// 声明一个变量 used,确保执行一次后就更改used,之后如果再次执行,used为true直接跳过。
let used = false;
// 这里必须得用try来捕捉异常,无论是取x.then的异常还是calling then的异常
try {
// 先将then取出来,这时候需要捕捉异常
let then = x.then;
// 如果then是一个方法
if (typeof then === 'function') {
// 完全遵循2.3.3.3的规则 If then is a function, call it with x as this, first argument resolvePromise, and second argument rejectPromise
then.call(x, (y) => {
// 注意在这里,如果used,也就是说已经执行过then.call ,那么就会忽视这段代码
if (used) {
return ;
}
used = true;
resolvePromise(promise2, y, resolve, reject);
}, (r) => {
if (used) {
return ;
}
used = true;
reject(r);
})
}
// 如果then不是一个方法,那么直接resolve(x)
else {
resolve(x);
}
} catch(e) {
// 这里同样也需要判断,这点容易忽视, 因为这里不只是判断x.then抛出的异常,也有可能是在执行then.call的时候出现的异常,如果已经执行过resolvePromise or rejectPromise, 就应该忽视下面的
if (used) {
return ;
}
used = true;
reject(e);
}
} else {
// 如果都不是,那就是一个基础类型,那么用resolve将其包裹起来返回
resolve(x);
}
}
Promise.prototype.catch = function (onRejected) {
return this.then(null, onRejected)
}
Promise.defer = Promise.deferred = function () {
let dfd = {}
dfd.promise = new Promise((resolve, reject) => {
dfd.resolve = resolve
dfd.reject = reject
})
return dfd
}
module.exports = Promise
可以用promises-aplus-tests去测试你实现的promise是否完全符合A+规范,具体方法可以github看一下这个库。