JavaScript:手撕Promise源码
一、什么是Promise?
- Promise可以实现异步操作;
- 可以将异步操作队列化,按照期望的顺序执行任务;
- 拥有三种状态:等待(Pending)、已兑现(Fullfilled)、已拒绝(Rejected),且状态只能从Pending向其他两个转化,一旦变化就冻结了;
- 有了
Promise
对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise
对象提供统一的接口,使得控制异步操作更加容易。
二、Promise的使用过程
- 一个简单的promise使用如下:
const p = new Promise((resolve,reject)=>{
try{
setTimeout(()=>{
resolve('I am a Promise');
},2000)
}catch(err){
reject(err);
}
})
p.then(res=>{
console.log(res); // I am a Promise
})
- 首先,我们需要先构造一个promise对象,传入执行
resolve
和reject
的函数(executor); - 然后,通过调用promise对象的then,来获取执行executor函数后返回的结果res;
- 最后执行then里面的回调,打印出结果。
三、Promise的执行流程
-
首先,在构造过程中,Promise会初始化自己的状态为Pending,调用executor函数,把对象自身的resolve方法和reject方法传入executor中,执行resolve方法(如果这里有异步操作,将会把resolve加入微任务队列,继续顺下执行then方法);
-
执行then方法,检测对象状态,若为Pending,则将参数存入上一个状态的任务队列中;否则,根据状态来执行
onFullfilled
或者onRejected
;
- 整个过程下来,任务队列是非常重要的思想;
四、开始手撕
1. 构造函数
- Promise的构造函数需要完成:
- 初始化状态;
- 初始化任务队列;
- 执行回调函数executor;
const PENDING = 'pending';
const FULLFILLED = 'fullfilled';
const REJECTED = 'rejected';
class MyPromise{
constructor(executor){
this.status = PENDING;
this.onFullFilledCallbacks = [];
this.onRejectedCallbacks = [];
this.value = '';
this.reason = '';
// 这里的_resolve和_reject方法是Promise内部处理的方法,后续补充
try{
executor(this._resolve.bind(this),this._reject.bind(this));
}catch(err){
this._reject(err);
}
}
}
2. Resolve方法
- Promise的resolve方法是把Promise状态变更为已兑现时的处理数据方法;
- 当构造函数调用回调函数时,将调用此方法;
class MyPromise{
......
_resolve(value){
if(this.status === PENDING){
this.status = FULLFILLED;
this.value = value;
// 执行任务队列里的函数
this.onFullFilledCallbacks.forEach(fn=>fn());
}
}
}
3. Reject方法
-
当某个操作出现异常时,Promise将会执行reject方法;
-
该方法是把Promise状态变更为已拒绝时的数据处理方法;
-
当构造函数调用回调方法时,将调用此方法;
class MyPromise{
......
_reject(reason){
if(this.status === PENDING){
this.status = REJECTED;
this.reason = reason;
// 执行任务队列的函数
this.onRejectedCallbacks.forEach(fn=>fn());
}
}
}
4. then方法(无链式调用)
- Promise的then方法用于接受前一个resolve或者reject时返回的结果;
- 这里先简易实现一个无链式调用的then方法;
class MyPromise{
......
then(onFullfilled,onRejected){
if(this.status === FULLFILLED){
// 这里的setTimeout用于模拟微任务,实现异步
setTimeout(()=>{
// 将resolve的执行结果传入
onFullfilled(this.value);
})
}
if(this.status === REJECTED){
setTimeout(()=>{
// 将reject的执行结果传入
onRejected(this.reason);
})
}
if(this.status === PENDING){
// 若还未执行resolve或者reject方法,说明Promise对象的回调操作是异步的。则状态还是等待
this.onFullFilledCallbacks.push(()=>{
setTimeout(()=>{
onFullfilled(this.value);
})
});
this.onRejectedCallbacks.push(()=>{
setTimeout(()=>{
onRejected(this.value);
})
})
// 这一步导致无法实现链式调用,每次then的结果都是同一个
return this;
}
}
}
- 来看看测试案例:
const p = new MyPromise((resolve,reject)=>{
setTimeout(()=>{
resolve('hello1');
})
})
p.then(res=>{
console.log(res);
return 'hello2';
})
.then(res=>{
console.log(res);
})
**********************控制台结果***************************
[2 seconds later]
hello1
hello1
- 从结果可以看出,无论调用几次then,返回结果总是第一个res;
- 但我们已经整体实现了Promise的基本思路,接下来就是进行链式功能的改造;
5. then方法(链式调用)
- 链式调用是promise实现异步操作队列化的关键,它能够阻塞进程,使程序如上下文顺序一般执行;
- 上一小点实现了无链式的then方法,无论执行几次都是第一个的res,无法接收到then返回的值;
- 为了接收这个值,我们对then方法做一点小改动,使之每次返回一个新的Promise对象;
class MyPromise{
......
then(onFullfilled,onRejected){
const promise2 = new MyPromise((resolve,reject)=>{
// 这里的this指向的是调用then方法的Promise对象
// 这里的this指向的是调用then方法的Promise对象
// 这里的this指向的是调用then方法的Promise对象
// 这里的this指向的是调用then方法的Promise对象
// 这里的this指向的是调用then方法的Promise对象
// 所以以下操作都是在原来的Promise上执行
if(this.status === FULLFILLED){
setTimeout(()=>{
try{
let x = onFullfilled(this.value);
resolve(x);
}catch(err){
reject(err);
}
})
}
if(this.status === REJECTED){
setTimeout(()=>{
try{
let x = onRejected(this.reason);
resolve(x);
}catch(err){
reject(err);
}
})
}
if(this.status === PENDING){
// 若还未执行resolve或者reject方法,说明Promise对象的回调操作是异步的。则状态还是等待
this.onFullFilledCallbacks.push(()=>{
setTimeout(()=>{
try{
let x = onFullfilled(this.value);
resolve(x);
}catch(err){
reject(err);
}
})
});
this.onRejectedCallbacks.push(()=>{
setTimeout(()=>{
try {
let x = onRejected(this.reason);
resolve(x);
} catch (error) {
reject(error);
}
})
})
}
})
return promise2;
}
}
- 看看测试案例:
let p1 = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve("Avril");
}, 2000);
});
let p2 = p1.then((res) => {
console.log(res);
return "Lavigne";
});
let p3 = p2.then((res) => {
console.log(res);
return "Tomorrow";
});
***********************控制台结果******************************
[2 seconds later]
Avril
Lavigne
Tomorrow
- 每次调用then方法后,返回一个新的Promise对象,这样能够接收到上一个then返回的数据;
- 如果当前状态为Pending,then方法就会将回调存入上一个promise的任务队列,等上一个promise执行resolve方法后,才执行这个then的回调函数;
- 这样一来,我们就成功实现了链式调用,但是有个缺点,当我们的then返回了新的promise对象时,程序就会抛异常。
6. then的健壮性强化:isThenable
- 上文说到then方法的缺点,接下来我们就对其进行修改;
- 上一小点的then函数中,我们只用一个变量将onFullfilled的返回值接收后,直接传入下一个Promise的resolve中,未对其进行检测;
- 因此,我们可以封装一个检测函数,检测then返回值是否是
Thenable
的。
function isThenable(obj){
return !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function';
}
isThenable
函数用于判断obj是否为object或者function,然后判断obj是否有then这个方法。
function resolvePromise(x,resolve,reject){
if(isThenable(x)){
try{
x.then(resolve,reject);
}catch(err){
reject(err);
}
}else{
resolve(x);
}
}
resolvePromise
函数用于根据x对象的类型来进行处理:- 若为Thenable对象, 将执行他的then方法,将resolve和reject方法作为参数传入;
- 否则,将直接执行resolve方法,将x作为result;
- 有了健壮性操作,我们再对MyPromise的部分方法进行修改:
class MyPromise{
......
_resolve(value){
if(isThenable(value)){
return value.then(this._resolve.bind(this),this._reject.bind(this));
}
if(this.status === PENDING){
this.status = FULLFILLED;
this.value = value;
this.onFullFilledCallbacks.forEach((fn)=>fn());
}
}
then(onFullfilled, onRejected) {
// 默认值
onFullfilled = typeof onFullfilled === 'function' ? onFullfilled : value=>value;
onRejected = typeof onRejected === 'function' ? onRejected : reason=>{
console.log('出错了');
throw new Error(reason);
};
const p2 = new MyPromise((resolve, reject) => {
if (this.status === FULLFILLED) {
setTimeout(() => {
try {
let x = onFullfilled(this.value);
resolvePromise(x,resolve,reject);
} catch (error) {
reject(error);
}
});
}
if (this.status === REJECTED) {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(x,resolve,reject);
} catch (error) {
reject(error);
}
});
}
if (this.status === PENDING) {
this.onFullFilledCallbacks.push(() => {
// 这里this指的还是前一个promise的onFullfilledCallbacks
// 用闭包封装 ,记住了this的onFullfilled和this的resolve
setTimeout(() => {
try {
let x = onFullfilled(this.value);
resolvePromise(x,resolve,reject);
} catch (error) {
reject(error);
}
});
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(x,resolve,reject);
} catch (error) {
reject(error);
}
});
});
}
});
return p2;
}
}
- 这里then方法首先先给参数赋予默认值;
- 与之前的相比,再处理新Promise的回调函数时,调用resolvePromise方法来处理then方法返回的值;
7. 一些静态方法的实现
Promise.resolve
方法是resolve的一个语法糖,返回一个Promise对象:
class MyPromise{
......
static resolve(param){
if(param instanceof MyPromise){
return param;
}
return new MyPromise((resolve,reject)=>{
if(isThenable(param)){
setTimeout(()=>{
param.then(resolve,reject);
})
}else{
resolve(param);
}
})
}
}
Promise.reject
同上,也返回一个Promise对象:
class MyPromise{
......
static reject(reason){
return new MyPromise((resolve,reject)=>{
reject(reason);
})
}
}
Promise.all
方法返回一个Promise实例,接收一个类型为Promise的数组,当数组中的所有Promise都实现时才返回实现状态;当有一个Promise执行失败了,则返回失败,而且返回的Promise的值就是第一个Promise的失败的原因;
class MyPromise{
......
static all(promises){
return new NyPromise((resolve,reject)=>{
let index = 0;
let result = [];
if(promises.length === 0){
resolve(result);
}else{
function processValue(i,data){
result[i] = data;
if(++index === promises.length){
resolve(result);
}
}
for(let i=0;i<promises.length;i++){
MyPromise.resolve(promises[i].then(data=>{
processValue(i,data);
},err=>{
reject(err);
return;
}))
}
}
})
}
}
Promise.race
返回一个Promise实例,参数传入一个类型为Promise的数组,当参数中的某个Promise成功resolve或reject后,会返回这个Promise的结果:
class MyPromise{
......
static race(promises){
return new MyPromise((resolve,reject)=>{
if(promises.length === 0){
return;
}else{
for(let i=0;i<promises.length;i++){
MyPromise.resolve(promises[i].then(data=>{
resolve(data);
return;
},err=>{
reject(err);
return;
}))
}
}
})
}
}
8. catch和finally方法
- catch返回一个Promise实例,同时只注册一个失败的回调;
- 当Promise的状态变为rejected时,就会执行这个catch方法;
class MyPromise{
......
catch(onRejected){
return this.then(null,onRejected);
}
}
- finally方法返回一个Promise实例,当所有then方法执行完后