1.前言:我们为什么要使用Promise
在es6之前,我们为了获取异步结果的时候,往往采用的是回调函数形式,即某个内部含有异步操作的函数,我们在调用该函数之前,传递回调函数作为参数,当我们异步拿到结果时,在内部调用这个回调函数,并把结果传递出来,实现异步回调。
我们可以看下下面的例子:
// request.js :接口的封装
function requestData(url, successCallback, failtureCallback) {
// 模拟网络请求
setTimeout(() => {
// 拿到请求的结果
// url传入的是ali, 请求成功
if (url === "ali") {
// 成功
let names = ["1", "2", "3"]
successCallback(names)
} else { // 否则请求失败
// 失败
let errMessage = "请求失败, url错误"
failtureCallback(errMessage)
}
}, 3000);
}
// main.js :调用
requestData("ali", (res) => {
console.log(res)
}, (err) => {
console.log(err)
})
分析:
- 为了获取接口的异步返回值,我们向封装接口的方法传递了额外的
successCallback和failtureCallback这两个回调函数参数; - 请求成功执行
successCallback回调,传递成功的值,即res,失败回调亦是如此,拿到err; 从表明上看,我们并没有发现这种异步回调有什么不好,那么为什么要放弃呢?
首先,该方法是我们自己封装的,就有必要去设计好回调函数的名称,并且要自己去调用;
其次,回调函数的可读性并不太好,特别是当处理多个异步嵌套的时候,我们就需要传递很多个回调函数,俗称回调地狱;
最后,如果是第三方库使用异步回调,我们甚至需要去看源码或者文档,去了解什么时候才能获取到这个结果等等。
所以,我们需要放弃这种异步回调的方式,正确的来说,是我们需要在自己的开发过程中避免使用这种异步回调,而让这种回调的过程封装到一个标准中,我们只需要按指定的规范去传递回调函数,就能拿到结果,而不必自己去封装去处理这个过程。一万个人有一万个哈姆雷特,正因为如此,我们更应该选择唯一的标准去解决,这个标准就是Promise。
2.说明
Promise(详看MDN)遵循 Promise A+规范(译文),但同es6的Promise一样,我们实现的Promise也在该规范基础上扩展了catch和finally方法。
本文并不会手写完整的Promise,例如返回值直接是Promise对象和返回值是带有then方法的对象等,并没有将这些边缘情况考虑进去。读者如果有兴趣,可自行添加。
另外,本文会分步去介绍自定义Promise从零到完成的整个过程,并会较为详细的说明各步骤的原因,尽量做到步步为营,同时对于需要注意的地方也会特别声明。
那么,就开始我们的Promise之旅吧!
3.promise的结构搭建
一个 Promise 必然处于以下几种状态之一:
- 待定(pending) : 初始状态,既没有被兑现,也没有被拒绝。
- 已兑现(fulfilled) : 意味着操作成功完成。
- 已拒绝(rejected) : 意味着操作失败。
Promise详情请至上面提供的MDN地址查看,此处不再过多描述什么是Promise及如何使用。
我们先简单看下es6中使用Promise的例子,从结果去推结构,从而搭建自己的Promise。
例子:
//成功状态:
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(111);
});
})
promise1.then(
(res) => {
console.log("res:", res); //res: 111
},
(err) => {
console.log("err:", err);
}
);
//拒绝状态:
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(222);
});
})
promise2.then(
(res) => {
console.log("res:", res);
},
(err) => {
console.log("err:", err); //err: 222
}
);
分析:
Promise是一个类;- 实例化的时候,会直接执行传递的回调函数
executor; - 回调函数
executor有两个函数作为参数,分别是reslove和reject; - 异步处理成功后,只需把需要传递的结果作为参数,执行
reslove函数,就会调用then中第一个回调函数,res就是传递的成功结果; - 异步处理失败后,只需把需要传递的结果作为参数,执行
reject函数,就会调用then中第二个回调函数,err就是传递的失败结果; Promise的状态一旦确定,就不可更改,初始化的时候状态为pending,当执行resolve()时把状态改成了fulfilled,执行reject()时改成了rejected。then函数的返回值是新的Promise对象(具体后面链式调用会讲); 像下面这种情况,由于reslove(111)已经把pending改变成了fulfilled,因此下面继续执行reject(22)是无效的,该代码并不会执行,所以then中第二个函数参数也不会被执行。
//成功状态:
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(111);
reject(222);
});
})
promise.then(
(res) => {
console.log("res:", res); //res: 111
},
(err) => {
console.log("err:", err);
}
);
通过上面几点的分析,我们对Promise的结构已经有了大概的认知,下面我们就搭建我们自定Promise的结构。
// 定义Promise的三种状态
const PROMISE_STATUS_PENDING = "pending";
const PROMISE_STATUS_FULFILLED = "resolved";
const PROMISE_STATUS_REJECTED = "rejected";
// 创建自定义Promise的类
class ALPromise {
constructor(executor) {
this.status = PROMISE_STATUS_PENDING; //初始化pending状态
this.value = undefined;
this.reason = undefined;
//执行成功:
const resolve = (value) => {
// 保证Promise状态确定后不可变
if (this.status === PROMISE_STATUS_PENDING) {
this.value = value;
//(使用异步微任务,保证then中回调函数已经加载)使用微任务queueMicrotask进行异步调用
queueMicrotask(() => {
//防止同时写了resolve和reject,异步原因都通过了PROMISE_STATUS_PEDDING判断
if (this.status !== PROMISE_STATUS_PENDING) return;
this.onFulfilled(value);
console.log("执行了resolve", value);
});
}
};
//执行拒绝:
const reject = (reason) => {
// 保证Promise状态确定后不可变
if (this.status === PROMISE_STATUS_PENDING) {
this.reason = reason;
//(使用异步微任务,保证then中回调函数已经加载)使用微任务queueMicrotask进行异步调用
queueMicrotask(() => {
//防止同时写了resolve和reject,异步原因都通过了PROMISE_STATUS_PENDING判断
if (this.status !== PROMISE_STATUS_PENDING) return;
console.log("执行了reject", reason);
this.onRejected(reason);
});
}
};
//执行executor回调函数
executor(resolve, reject);
}
then(onFulfilled, onRejected) {
this.onFulfilled = onFulfilled;
this.onRejected = onRejected;
}
}
//创建Promise对象
const alPromise = new ALPromise((resolve, reject) => {
resolve(111);
// reject(222);
})
// 调用then方法
alPromise.then(
(res) => {
console.log("res:", res); // 111
},
(err) => {
console.log("err:", err);
}
);
以上是自定义Promise的基本结构,这里我们并没有实现then返回值改成新的Promise对象,因为还未涉及到链式调用,等后面讲到链式调用,我们再详细改造。
总之,正如我们看到的,Promise的基本结构,还是还是比较简单的,无非就是四点:
- 初始化执行executor函数;
- 执行
then函数时收集回调函数; - 调用
resolve或reject函数时改变Promise状态; - 调用
resolve或reject函数时手动分别(异步)执行then中的第一个和第二个回调函数,并传递值;
4.Promise中then多次调用及异步调用情况
Promise并没有限制我们调用then方法的次数,我们可以同时调用多次,所以我们需要对之前搭建的结构进行改造,从而实现可以多次调用。
例子如下:
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(111);
});
})
promise1.then(
(res) => {
console.log("res:", res); //res: 111
},
(err) => {
console.log("err:", err);
}
);
promise1.then(
(res) => {
console.log("res:", res); //res: 111
},
(err) => {
console.log("err:", err);
}
);
// 异步调用(1秒后输出)
setTimeout(() => {
promise1.then(
(res) => {
console.log("res:", res); // res:111
},
(err) => {
console.log("err:", err);
}
);
}, 1000);
分析:
- 执行
then函数时,收集到的多个成功或者失败的回调函数存储在数组中; - 执行
resolve或reject函数时遍历并执行上面收集到的成功或失败数组中的所有函数; - 异步调用时,如果我们按照之前的方式,由于定时器是宏任务,那么必然是数组中回调函数全部执行完毕,才会把新的异步回调函数追加到数组中,那么就会导致该回调不会执行,因此当追加进数组之前如果状态已经确定,就不要追加到数组,而是直接执行; 因此,对原结构代码进行改造,如下:
// 定义Promise的三种状态
const PROMISE_STATUS_PENDING = "pending";
const PROMISE_STATUS_FULFILLED = "resolved";
const PROMISE_STATUS_REJECTED = "rejected";
// 创建自定义Promise的类
class ALPromise {
constructor(executor) {
this.status = PROMISE_STATUS_PENDING; //初始化pending状态
this.value = undefined;
this.reason = undefined;
this.onFulfilledFn = []; // 存储多个成功的回调函数
this.onRejectedFn = []; //存储多个失败的回调函数
//执行成功:
const resolve = (value) => {
// 保证Promise状态确定后不可变
if (this.status === PROMISE_STATUS_PENDING) {
this.value = value;
//(使用异步微任务,保证then中回调函数已经加载)使用微任务queueMicrotask进行异步调用
queueMicrotask(() => {
//防止同时写了resolve和reject,异步原因都通过了PROMISE_STATUS_PENDING判断
if (this.status !== PROMISE_STATUS_PENDING) return;
this.onFulfilled(value);
console.log("执行了resolve", value);
});
}
};
//执行拒绝:
const reject = (reason) => {
// 保证Promise状态确定后不可变
if (this.status === PROMISE_STATUS_PENDING) {
this.reason = reason;
//(使用异步微任务,保证then中回调函数已经加载)使用微任务queueMicrotask进行异步调用
queueMicrotask(() => {
//防止同时写了resolve和reject,异步原因都通过了PROMISE_STATUS_PENDING判断
if (this.status !== PROMISE_STATUS_PENDING) return;
console.log("执行了reject", reason);
this.onRejected(reason);
});
}
};
//执行executor回调函数
executor(resolve, reject);
}
then(onFulfilled, onRejected) {
// 1.如果在then调用的时候, 状态已经确定下来
if (this.status === PROMISE_STATUS_FULFILLED && onFulfilled) {
onFulfilled(this.value)
}
if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
onRejected(this.reason)
}
// 2.考虑到多个执行,存储到数组中
this.onFulfilledFn.push(onFulfilled);
this.onRejectedFn.push(onRejected);
}
}
//创建Promise对象
const alPromise = new ALPromise((resolve, reject) => {
// resolve(111);
reject(222);
})
// 调用then方法
alPromise.then(
(res) => {
console.log("res:", res);
},
(err) => {
console.log("err:", err); // 222
}
);
alPromise.then(
(res) => {
console.log("res:", res);
},
(err) => {
console.log("err:", err); // 222
}
);
可以看到,我们只是把回调函数改成了用数组存储,调用的时候遍历数组中每一个回调函数,就实现了then的多次调用功能。
5.Promise的then链式调用
Promise中最难的部分就是关于then链式调用的实现,前面我们有提到then函数的返回值的一个Promise对象,在这里我们就会对上面的代码进行改造。then链式调用的难点在于怎么去实现多个Promise中数据的传递。
我们先看下例子:
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(111);
});
});
promise1
.then(
(res) => {
console.log("res1:", res); // res1:111
},
(err) => {
console.log("err1:", err);
}
)
.then(
(res) => {
console.log("res2:", res); // res2:undefined
},
(err) => {
console.log("err2:", err);
}
);
分析:
(我们可以试着用我们上面自定义实现的Promise来分析用这个例子中的代码逻辑)
-
执行
promise1.then()的时候,内部会把then中两个回调函数存储到对应状态的数组中; -
异步执行
resolve(111)时,调用了onFulfilledFn数组中的回调函数,此处只有这一个,该函数没有返回值,我们知道没有返回值的函数其实内部默认是返回undefined的;(res) => { console.log("res1:", res)} -
由于状态一旦确定就不会改变,所以第二个回调函数
(err) => {console.log("err2:", err)}是不会被执行的; (下面过程因为我们自定义Promise未实现,因此直接描述实际Promise的逻辑,根据这个逻辑的过程,回头在我们自定义的Promise中实现) -
经过上面的步骤,我们知道
then函数已经执行完毕了,我们之前说过then函数其实是返回一个Promise对象的,当然这是Promise内部进行处理的。同时我们获取到了第一个回调函数中的返回值,该例子中返回值是undefined,这个返回值会被当做新Promise调用resolve的参数,即resolve(返回值),相当于如下:return new Promise((resolve,reject)=>{ resolve(undefined) }) -
我们回想一下之前自定义的
Promise,这个resolve()函数是干嘛的呢?没错,执行这个resolve()的时候,其实就是执行了存在onFulfilledFn数组中的所有回调函数,即then中的第一个回调函数参数,当然此处这个then是第一个then返回的新的Promise后面紧接的then,即:新的Promise对象.then( (res) => { console.log("res2:", res); // res2:undefined }, (err) => { console.log("err2:", err); } -
那么很显然
resolve(undefined)中的undefined最终会传递到上面这个then第一个回调函数的res中,因此上面的打印结果才是res2:undefined; 从上面的分析,我们已经大致了解到了Promise中then链式调用的经历过程,当然我们就写了一个例子,而实际情况却有多种,下面我们把大致会出现的情况都罗列出来。
情况:
- 该情况忽略:第一个
then回调函数返回值直接就是Promise对象或是带有then方法的对象,由于说明中我们声明了暂时不实现这两种情况,因此排除在外,读者可自行了解这两种情况是如何确定状态的,有兴趣可自行在最终我们实现的Promise上补充实现; - 其余情况: | 顶层Promise对象状态 | 第一个then回调 | then返回Promise的状态 | 第二个then回调 | | --- | --- | --- | --- | | fulfilled| 第一个函数参数执行|fulfilled | 第一个函数参数执行 | fulfilled| 第一个函数参数执行抛出异常|fulfilled | 第二个函数参数执行 | rejected| 第二个函数参数执行|fulfilled | 第一个函数参数执行 | rejected| 第二个函数参数执行抛出异常|rejected | 第二个函数参数执行
正常来说我们如果不手动设置【回调函数返回值直接就是Promise对象、带有then方法的对象、抛出错误】,而只是返回普通的数据的情况,那么在这条then链中,只要顶部Promise状态固定了,下面所有的新的Promise状态都是确定的,例如:
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(111);
});
});
promise1
.then(
(res) => {
console.log("res1:", res); // res1: 111
},
(err) => {
console.log("err1:", err);
}
)
.then(
(res) => {
console.log("res2:", res); // res2: undefined
},
(err) => {
console.log("err2:", err);
}
)
.then(
(res) => {
console.log("res3:", res); // res3: undefined
},
(err) => {
console.log("err3:", err);
}
)
.then(
(res) => {
console.log("res4:", res); // res4: undefined
},
(err) => {
console.log("err4:", err);
}
);
或者
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(222);
});
});
promise1
.then(
(res) => {
console.log("res1:", res);
},
(err) => {
console.log("err1:", err); // err1:222
}
)
.then(
(res) => {
console.log("res2:", res); // res2: undefined
},
(err) => {
console.log("err2:", err);
}
)
.then(
(res) => {
console.log("res3:", res); // res3: undefined
},
(err) => {
console.log("err3:", err);
}
)
.then(
(res) => {
console.log("res4:", res); // res4: undefined
},
(err) => {
console.log("err4:", err);
}
);
我们知道,除了第一个Promise因为是我们手动控制resolve()或reject()改变状态,下面的都是fulfilled状态,都会执行then中第一个回调函数,并拿到上一个then的返回值作为回调函数的参数;
如果是【回调函数返回值直接就是Promise对象、带有then方法的对象】情况,就可以手动在里面去调用resolve()或reject()改变状态,当然我们前面提到了,此次暂时不写这两种情况;
还有就是【抛出错误(包括代码写错的报错)】的情况,抛出错误的地方会中断执行,同时回调下一个then的第二个回调函数,即新的Promise的状态为rejected;
综上,我们改造原来的代码,把then的链式调用加上。
// 定义promise的三种状态
const PROMISE_STATUS_PENDING = "pending";
const PROMISE_STATUS_FULFILLED = "resolved";
const PROMISE_STATUS_REJECTED = "rejected";
// 工具函数:封装处理错误捕获的公共代码方法
function execFucntionWithCatchError(execFuc, value, resolve, reject) {
try {
const result = execFuc(value);
resolve(result);
} catch (err) {
reject(err);
}
}
// 创建自定义promise的类
class ALPromise {
constructor(executor) {
this.status = PROMISE_STATUS_PENDING;
this.value = undefined;
this.reason = undefined;
this.onFulfilledFn = [];
this.onRejectedFn = [];
const resolve = (value) => {
// 保证promise状态确定后不可变
if (this.status === PROMISE_STATUS_PENDING) {
this.value = value;
// 使用微任务queueMicrotask进行异步调用,而不使用宏任务的定时器
queueMicrotask(() => {
if (this.status !== PROMISE_STATUS_PENDING) return;
// console.log("执行了resolve", value);
this.status = PROMISE_STATUS_FULFILLED;
this.onFulfilledFn.forEach((fn) => fn(value));
});
}
};
const reject = (reason) => {
// 保证promise状态确定后不可变
if (this.status === PROMISE_STATUS_PENDING) {
this.reason = reason;
queueMicrotask(() => {
if (this.status !== PROMISE_STATUS_PENDING) return;
// console.log("执行了reject", reason);
this.status = PROMISE_STATUS_REJECTED;
this.onRejectedFn.forEach((fn) => fn(reason));
});
}
};
// 捕获外面的错误
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled, onRejected) {
// 返回promise对象
return new ALPromise((resolve, reject) => {
// 此处最好不要使用普通函数来作为executor的回调函数,因为内部的this会指向顶层作用域,此处用es6模块,顶层为自动开启严格模式的undefined
// 情况1:考虑到执行then的回调之前,promise的状态已经确定了,则不需要追加到下面数组,直接执行
if (this.status === PROMISE_STATUS_FULFILLED && onFulfilled) {
// try {
// const value = onFulfilled(this.value);
// resolve(value);
// } catch (err) {
// reject(err);
// }
execFucntionWithCatchError(onFulfilled, this.value, resolve, reject);
}
if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
// try {
// const reason = onRejected(this.reason);
// resolve(reason);
// } catch (err) {
// reject(err);
// }
execFucntionWithCatchError(onRejected, this.reason, resolve, reject);
}
// 情况2:执行then回调时还未确定状态,考虑到多个执行,存储到数组中
if (this.status === PROMISE_STATUS_PENDING) {
this.onFulfilledFn.push(() => {
// try {
// const value = onFulfilled(this.value);
// resolve(value);
// } catch (err) {
// reject(err);
// }
if (onFulfilled) execFucntionWithCatchError(onFulfilled, this.value, resolve, reject);
});
this.onRejectedFn.push(() => {
// try {
// const reason = onRejected(this.reason);
// resolve(reason);
// } catch (err) {
// reject(err);
// }
if (onRejected) execFucntionWithCatchError(onRejected, this.reason, resolve, reject);
});
}
});
}
}
const alPromise = new ALPromise((resolve, reject) => {
resolve(111);
// reject(222);
// throw new Error("Error Message");
});
// 链式调用
alPromise
.then(
(res) => {
console.log("res1:", res); // res1:111
// throw new Error("err message");
return "aaa";
},
(err) => {
console.log("err1:", err);
// throw new Error("err message");
return "bbb";
}
)
.then(
(res) => console.log("res2:", res), // res2:aaa
(err) => console.log("err2", err)
);
以上,我们便完成了Promise中then的链式调用。
6. Promise的catch实现
catch方法不属于Promise A+规范中的,但是是属于es6中新增的方法,针对then中需要传递第二个参数去获取rejected状态返回值的一种可读性较强、较美观的写法,因此我们在自定义Promise也中实现它吧。
看例子:
const promise = new Promise((resolve, reject) => {
reject("111");
})
promise.then(res => {
console.log("res:", res);
}).catch(err => {
console.log("err:", err); // err:111
return "catch value";
}).then(res => {
console.log("res:", res); // res:catch value
}).catch(err => {
console.log("err:", err);
})
分析:
catch是为了捕获失败/错误的情况的,相当于原then不传第二个回调函数参数,而用catch方法去接收这个回调函数参数,那么我们是不是可以理解catch就是第二个then,但是第一个参数onFulfilled设置为undefined,第二个参数依旧为onRejected回调函数,那么就把catch的问题转换为then的链式调用了;- 所以当需要使用
catch捕获错误时,我们只需要把第一个then中第二个拒绝状态onRejected回调函数的err传递到下一个then的onRejected中,相当于原本在第一个then的第二个拒绝状态的onRejected回调函数处理的err转移到下一个then的第二个拒绝状态的onRejected回调函数中处理; - 第一个
then不传递第二个参数,我们知道,如果没有第二个参数onRejected,当Promise状态为rejected时,就不能回调onRejected并传递err,因此我们需要想办法设置一个默认的onRejected,从而实现执行onRejected回调,并且把err传递给下一个Promise对象的catch中,即本质上是then的第二个参数onRejected中; - 从
5的链式调用我们知道,正常情况下,除了顶层Promise外,其余上个Promise对象不论是什么状态,都会执行then中的第一个回调函数onFulfilled,而不会传递到第二个回调函数onRejected中,所以我们采用抛出异常的方式,刚好这种方式可以实现; - 最后,
catch方法也返回一个新的Promise对象,目的是为了继续扩展链结构(finally方法);
根据上述分析,我们对代码进行改造,如下:
// 定义promise的三种状态
const PROMISE_STATUS_PENDING = "pending";
const PROMISE_STATUS_FULFILLED = "resolved";
const PROMISE_STATUS_REJECTED = "rejected";
// 工具函数:封装处理错误捕获的公共代码方法
function execFucntionWithCatchError(execFuc, value, resolve, reject) {
try {
const result = execFuc(value);
resolve(result);
} catch (err) {
reject(err);
}
}
// 创建自定义promise的类
class ALPromise {
constructor(executor) {
this.status = PROMISE_STATUS_PENDING;
this.value = undefined;
this.reason = undefined;
this.onFulfilledFn = [];
this.onRejectedFn = [];
const resolve = (value) => {
// 保证promise状态确定后不可变
if (this.status === PROMISE_STATUS_PENDING) {
this.value = value;
// 使用微任务queueMicrotask进行异步调用,而不使用宏任务的定时器
queueMicrotask(() => {
if (this.status !== PROMISE_STATUS_PENDING) return;
this.status = PROMISE_STATUS_FULFILLED;
this.onFulfilledFn.forEach((fn) => fn(value));
});
}
};
const reject = (reason) => {
// 保证promise状态确定后不可变
if (this.status === PROMISE_STATUS_PENDING) {
this.reason = reason;
queueMicrotask(() => {
if (this.status !== PROMISE_STATUS_PENDING) return;
this.status = PROMISE_STATUS_REJECTED;
this.onRejectedFn.forEach((fn) => fn(reason));
});
}
};
// 捕获外面的错误
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled, onRejected) {
//当没有onRejected回调函数时,手动改成默认抛出错误的回调函数
const defaultOnRejected = (err) => {
throw err;
};
onRejected = onRejected || defaultOnRejected;
// 返回promise对象
return new ALPromise((resolve, reject) => {
// 情况1:考虑到执行then的回调之前,promise的状态已经确定了,则不需要追加到下面数组,直接执行
if (this.status === PROMISE_STATUS_FULFILLED && onFulfilled) {
execFucntionWithCatchError(onFulfilled, this.value, resolve, reject);
}
if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
execFucntionWithCatchError(onRejected, this.reason, resolve, reject);
}
// 情况2:执行then回调时还未确定状态,考虑到多个执行,存储到数组中
if (this.status === PROMISE_STATUS_PENDING) {
this.onFulfilledFn.push(() => {
if (onFulfilled) execFucntionWithCatchError(onFulfilled, this.value, resolve, reject);
});
this.onRejectedFn.push(() => {
if (onRejected) execFucntionWithCatchError(onRejected, this.reason, resolve, reject);
});
}
});
}
//定义catch方法:捕获异常
catch(onRejected) {
return this.then(undefined, onRejected);
}
}
const alPromise = new ALPromise((resolve, reject) => {
resolve(111);
// reject(222);
// throw new Error("Error Message");
});
// 链式调用:
alPromise
.then((res) => {
console.log("res1:", res); // res1:111
throw new Error("err1 message");
return "aaa";
})
.then((res) => {
// console.log("res2:", res);
// throw new Error("err2 message");
return "bbb";
})
.catch((err) => {
console.log("err:", err); // err:报错信息
});
7.实现finally方法
finally方法同样不属于Promise A+规范中的,但是是属于es6中新增的方法,不论Promise最终确定为哪种状态(fulfilled或rejected),都会执行finally函数传递的回调函数。
看例子:
const promise = new Promise((resolve, reject) => {
// resolve("resolve message");
reject("reject message");
})
promise.then(res => {
console.log("res:", res);
}).catch(err => {
console.log("err:", err); // reject message
}).finally(() => {
console.log("finally code execute"); // finally code execute
})
分析:
- 不论最终状态确定为
fulfilled还是rejected,都会执行finally传递的回调函数参数; - 我们知道,当
Promise状态为fulfilled时,会执行then的第一个回调函数onFulfilled,并传递过来参数,当Promise状态为rejected时,会执行then的第二个回调函数onRejected或者执行catch的回调函数参数,那么我们可以理解为finally就是then链的继续延伸,我们在执行catch内部时就可以设计调用then方法,接收回调函数参数,然后在调用onFulfilled和onRejected时都执行该回调函数即可; - 最后我们考虑一个问题,我们在设计
catch时,调用then传递的第一个参数是undefined,当时是因为catch捕获错误时,只会执行第二个回调函数onRejected参数,但是在这里就会出现问题; - 当这条链上同时出现
catch和finally时,如果Promise的状态是onFulfilled时,必然不会走catch调用then的第二个回调函数,而是走内部第一个的undefined,而undefined等同于不存在回调函数,无法通过if判断,导致不会执行resolve,无法将当前Promise的状态改为fulfilled,会一直处于pending状态,所以也就不会去调用finally传递的回调函数; - 所以我们就需要判断,当
onFulfilled回调函数不存在时,则创建一个默认的回调函数,在这个回调函数中直接返回原本调用该函数传递来的参数即可; 通过上面的分析,我们来实现下带有finally功能的自定义Promise:
// 定义promise的三种状态
const PROMISE_STATUS_PENDING = "pending";
const PROMISE_STATUS_FULFILLED = "resolved";
const PROMISE_STATUS_REJECTED = "rejected";
// 工具函数:封装处理错误捕获的公共代码方法
function execFucntionWithCatchError(execFuc, value, resolve, reject) {
try {
const result = execFuc(value);
resolve(result);
} catch (err) {
reject(err);
}
}
// 创建自定义promise的类
class ALPromise {
constructor(executor) {
this.status = PROMISE_STATUS_PENDING;
this.value = undefined;
this.reason = undefined;
this.onFulfilledFn = [];
this.onRejectedFn = [];
const resolve = (value) => {
// 保证promise状态确定后不可变
if (this.status === PROMISE_STATUS_PENDING) {
this.value = value;
// 使用微任务queueMicrotask进行异步调用,而不使用宏任务的定时器
queueMicrotask(() => {
if (this.status !== PROMISE_STATUS_PENDING) return;
this.status = PROMISE_STATUS_FULFILLED;
this.onFulfilledFn.forEach((fn) => fn(value));
});
}
};
const reject = (reason) => {
// 保证promise状态确定后不可变
if (this.status === PROMISE_STATUS_PENDING) {
this.reason = reason;
queueMicrotask(() => {
if (this.status !== PROMISE_STATUS_PENDING) return;
this.status = PROMISE_STATUS_REJECTED;
this.onRejectedFn.forEach((fn) => fn(reason));
});
}
};
// 捕获外面的错误
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled, onRejected) {
//解决finally:当没有onFulfilled回调函数时,手动改成默认返回调用该函数传递来的参数
const defaultOnResolve = (value) => {
return value;
};
onFulfilled = onFulfilled || defaultOnResolve;
//解决catch;当没有onRejected回调函数时,手动改成默认抛出错误的回调函数
const defaultOnRejected = (err) => {
throw err;
};
onRejected = onRejected || defaultOnRejected;
// 返回promise对象
return new ALPromise((resolve, reject) => {
// 情况1:考虑到执行then的回调之前,promise的状态已经确定了,则不需要追加到下面数组,直接执行
if (this.status === PROMISE_STATUS_FULFILLED && onFulfilled) {
execFucntionWithCatchError(onFulfilled, this.value, resolve, reject);
}
if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
execFucntionWithCatchError(onRejected, this.reason, resolve, reject);
}
// 情况2:执行then回调时还未确定状态,考虑到多个执行,存储到数组中
if (this.status === PROMISE_STATUS_PENDING) {
this.onFulfilledFn.push(() => {
if (onFulfilled) execFucntionWithCatchError(onFulfilled, this.value, resolve, reject);
});
this.onRejectedFn.push(() => {
if (onRejected) execFucntionWithCatchError(onRejected, this.reason, resolve, reject);
});
}
});
}
//定义catch方法:捕获异常
catch(onRejected) {
return this.then(undefined, onRejected);
}
// 定义finally方法:两种状态都需要执行
finally(onFinally) {
// console.log(222222);
this.then(
() => {
onFinally();
},
() => {
onFinally();
}
);
}
}
const alPromise = new ALPromise((resolve, reject) => {
resolve(111);
// reject(222);
// throw new Error("Error Message");
});
// 链式调用
alPromise
.then((res) => {
console.log("res1:", res); // res1:111
// throw new Error("err1 message");
return "aaa";
})
.then((res) => {
console.log("res2:", res); // res2:aaa
// throw new Error("err2 message");
return "bbb";
})
.catch((err) => {
console.log("err:", err);
return "ccc";
})
.finally(() => {
console.log("这是finally"); // 这是finally
});
以上就是关于finally方法的补充。
综上,我们已经把所有Promise实例对象上的方法完善好了,可以说整个Promise的手写基本算是完成了,其中比较难的地方就是then方法,其余像catch和finally,都是调用then方法。
下面封装几个Promise的类方法。
8. 类方法:resolve和reject的实现
resolve和reject方法比较简单,此处不过多阐述。
class ALPromise{
......
static resolve(value) {
return new ALPromise((resolve) => {
resolve(value);
});
}
static reject(reason){
return new ALPromise((resolve,reject) => {
reject(reason);
});
}
......
}
ALPromise.resolve("Hello World").then(res => {
console.log("res:", res); // Hello World
})
ALPromise.reject("Error Message").catch(err => {
console.log("err:", err); // Error Message
})
9.类方法:all和allSettled的实现
注意:并没有做关于传入的数组中,元素是普通数据的情况。
Promise.all() 方法接收一个promise的iterable类型(注:Array,Map,Set都属于ES6的iterable类型)的输入,并且只返回一个Promise实例, 那个输入的所有promise的resolve回调的结果是一个数组。这个Promise的resolve回调执行是在所有输入的promise的resolve回调都结束,或者输入的iterable里没有promise了的时候。它的reject回调执行是,只要任何一个输入的promise的reject回调执行或者输入不合法的promise就会立即抛出错误,并且reject的是第一个抛出的错误信息。
Promise.allSettled()方法返回一个在所有给定的promise都已经fulfilled或rejected后的promise,并带有一个对象数组,每个对象表示对应的promise结果。
......
// 上面省略ALPromise类的声明
static all(promises) {
return new ALPromise((resolve, reject) => {
const values = new Array(promises.length);
let i = 0; //标识都追加进数组
promises.forEach((promise, index) => {
promise.then(
(res) => {
values[index] = res;
i++;
if (i === promises.length) {
resolve(values);
}
},
(err) => {
reject(err);
}
);
});
});
}
static allSettled(promises) {
return new ALPromise((resolve, reject) => {
const values = new Array(promises.length);
let i = 0; //标识都追加进数组
promises.forEach((promise, index) => {
promise.then(
(res) => {
values[index] = { status: PROMISE_STATUS_FULFILLED, value: res };
i++;
if (i === promises.length) {
resolve(values);
}
},
(err) => {
values[index] = { status: PROMISE_STATUS_REJECTED, value: err };
i++;
if (i === promises.length) {
reject(values);
}
}
)
})
})
}
10.类方法:race和any的实现
Promise.race(iterable) 方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝,即谁先出结果返回谁。
Promise.any() 接收一个Promise可迭代对象,只要其中的一个 promise 成功,就返回那个已经成功的 promise 。如果可迭代对象中没有一个 promise 成功(即所有的 promises 都失败/拒绝),就返回一个失败的 promise 和AggregateError类型的实例,它是 Error 的一个子类,用于把单一的错误集合在一起。本质上,这个方法和Promise.all()是相反的。
......
// 上面省略ALPromise类的声明
static race(promises) {
return new ALPromise((resolve, reject) => {
promises.forEach((promise) => {
// 原版
// promise.then(
// (res) => {
// resolve(res);
// },
// (err) => {
// reject(err);
// }
// );
// 简化
promise.then(resolve, reject);
});
});
}
static any(promises) {
return new ALPromise((reslove, reject) => {
const reasons = [];
promises.forEach(
(promise) => {
promise.then((res) => {
reslove(res);
});
},
(err) => {
reasons.push(err);
if (reasons.length === promises.length) {
reject(new AggregateError(reasons));
}
}
);
});
}
11.总结
以上就是关于手写Promise的所有内容,本文只是针对Promise基本的构建及常用方法等进行的撰写,旨在一方面是了解Promise内部的处理逻辑,当我们使用Promise的时候能更加清晰的知道所做的每一步操作本质是在做哪些事情;另一方面是锻炼复杂的回调函数逻辑,能让我们在阅读理解和封装自己的需求代码时提供一些开拓的思路。
当然,本文还有很多不足之处,比如很多边界情况,比如某些返回值类型的延伸等等,读者有兴趣可自行拓展;
读者如有发现本文有描述不对、不足的地方,欢迎提出宝贵的建议,不吝感谢!
另附完整的自定义Promise代码(注释已移除):
const PROMISE_STATUS_PENDING = "pending";
const PROMISE_STATUS_FULFILLED = "resolved";
const PROMISE_STATUS_REJECTED = "rejected";
function execFucntionWithCatchError(execFuc, value, resolve, reject) {
try {
const result = execFuc(value);
resolve(result);
} catch (err) {
reject(err);
}
}
class ALPromise {
constructor(executor) {
this.status = PROMISE_STATUS_PENDING;
this.value = undefined;
this.reason = undefined;
this.onFulfilledFn = [];
this.onRejectedFn = [];
const resolve = (value) => {
if (this.status === PROMISE_STATUS_PENDING) {
this.value = value;
queueMicrotask(() => {
if (this.status !== PROMISE_STATUS_PENDING) return;
this.status = PROMISE_STATUS_FULFILLED;
this.onFulfilledFn.forEach((fn) => fn(value));
});
}
};
const reject = (reason) => {
if (this.status === PROMISE_STATUS_PENDING) {
this.reason = reason;
queueMicrotask(() => {
if (this.status !== PROMISE_STATUS_PENDING) return;
this.status = PROMISE_STATUS_REJECTED;
this.onRejectedFn.forEach((fn) => fn(reason));
});
}
};
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled, onRejected) {
const defaultOnRejected = (err) => {
throw err;
};
onRejected = onRejected || defaultOnRejected;
const defaultOnResolve = (value) => {
return value;
};
onFulfilled = onFulfilled || defaultOnResolve;
return new ALPromise((resolve, reject) => {
if (this.status === PROMISE_STATUS_FULFILLED && onFulfilled) {
execFucntionWithCatchError(onFulfilled, this.value, resolve, reject);
}
if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
execFucntionWithCatchError(onRejected, this.reason, resolve, reject);
}
if (this.status === PROMISE_STATUS_PENDING) {
this.onFulfilledFn.push(() => {
if (onFulfilled) execFucntionWithCatchError(onFulfilled, this.value, resolve, reject);
});
this.onRejectedFn.push(() => {
if (onRejected) execFucntionWithCatchError(onRejected, this.reason, resolve, reject);
});
}
});
}
catch(onRejected) {
return this.then(undefined, onRejected);
}
finally(onFinally) {
this.then(
() => {
onFinally();
},
() => {
onFinally();
}
);
}
static resolve(value) {
return new ALPromise((resolve) => {
resolve(value);
});
}
static reject(reason) {
return new ALPromise((resolve, reject) => {
reject(reason);
});
}
static all(promises) {
return new ALPromise((resolve, reject) => {
const values = new Array(promises.length);
let i = 0;
promises.forEach((promise, index) => {
promise.then(
(res) => {
values[index] = res;
i++;
if (i === promises.length) {
resolve(values);
}
},
(err) => {
reject(err);
}
);
});
});
}
static allSettled(promises) {
return new ALPromise((resolve, reject) => {
const values = new Array(promises.length);
let i = 0;
promises.forEach((promise, index) => {
promise.then(
(res) => {
values[index] = { status: PROMISE_STATUS_FULFILLED, value: res };
i++;
if (i === promises.length) {
resolve(values);
}
},
(err) => {
values[index] = { status: PROMISE_STATUS_REJECTED, value: err };
i++;
if (i === promises.length) {
reject(values);
}
}
);
});
return values;
});
}
static race(promises) {
return new ALPromise((resolve, reject) => {
promises.forEach((promise) =>
promise.then(resolve, reject);
});
});
}
static any(promises) {
return new ALPromise((reslove, reject) => {
const reasons = [];
promises.forEach(
(promise) => {
promise.then((res) => {
reslove(res);
});
},
(err) => {
reasons.push(err);
if (reasons.length === promises.length) {
reject(new AggregateError(reasons));
}
}
);
});
}
}