一、前言
我们上次已经自己去手写了一个promise,实现了原生promise的基本功能,包括能够进行异步,then方法能够进行链式调用等。但是在原生的promise中还是有很多其他的api我们没有进行去实现的。包括Promise.reolve()、Promise.reject()、Promise.All()、Promise.allSettled()、Promise.race()、Promise.any()还有promise中用来捕获内部错误的.catch()方法,还有一定会执行的.finally()方法。那么这次我们就把这些api都去实现下,加深我们对promise的理解。
二、Promose.resolve的实现
我们平时调用resolve方法时是用类名.方法名调用的,这说明resolve可以是一个静态方法。
class MyPromise {
...
static resolve = () => {
}
}
我们知道当调用Promose.resolve(value)的时候也是会返回一个promise的,那我们可以直接返回promise对象并且默认把状态改为fulfilled。代码可以这样写:
class MyPromise {
...
static resolve = (value) => {
return new Mypromise((resolve,reject) => {
resolve(value);
})
}
}
但是如果在Promose.resolve(value)中value传的也是一个promise,那这个又怎么处理的呢?我们先看看原生的promise。我们看下面的代码:
const p = new Promise((res,rej) => {
// res('成功');
rej('拒绝');
})
Promise.resolve(p).then(res => {
console.log(res);
},reject => {
console.log(reject);
})
我们发现,Promise.resolve(p)中返回的其实就是上面的p,那我们可以先判断传过来的value值是不是一个promise,如果是一个promise,那我们直接把这个传过来的promise返回。代码可以这样写:
class MyPromise {
...
static resolve = (value) => {
//判断传入的参数是否为Promise,如果是直接将其返回
if(value instanceof MyPromise) {
return value;
}
//如果传入的参数不是Promise,将其封装成Promise返回,并默认改变为已完成状态
return new Mypromise((resolve,reject) => {
resolve(value);
})
}
}
这样我们的Promose.resolve就差不多完成了,但还是有一个问题的,按照promise规范如果传过来的这个值他是一个thenable 的话那么返回的promise会“跟随”这个thenable的对象,采用它的最终状态;所以我们的代码得进行改造。如下:
class MyPromise {
...
static resolve = (value) => {
if (value instanceof MyPromise) {
return value;
//判断是否为thenable对象
} else if (value instanceof Object && "then" in value) {
return new myPromise((resolve, reject) => {
value.then(resolve, reject);
});
}
return new MyPromise((resolve, reject) => {
resolve(value);
});
};
}
这样我们的Promose.resolve就全部写好了。Promose.resolve返回的也是一个promise,返回的promise要根据传入的参数来进行决定。如果传入的也是一个promise则直接将传入的promise返回;如果传入的是thenable ,则采用它的最终状态;如果是其他的则需要将其封装成一个promise将其返回,并需要用传入的值去完成这个封装的promise。
三、Promise.reject的实现
Promise.reject是返回一个拒绝状态的promise:
class MyPromise {
...
static reject = (value) => {
//返回一个拒绝状态的promise
return new MyPromise((resolve,reject) => {
reject(value);
})
}
}
我们在返回的promise中直接调用拒绝的回调就好了。
四、Promise.all的实现
Promise.all(value)方法参数接收的是一个数组,在这个数组中一般存放的都是promise。当这个数组中所有的promise都执行完成并且都是完成状态。那么Promise.all返回的是一个完成状态的promise,完成状态的Promise.all返回的结果会是一个数组,如果数组中有一个是拒绝状态的,那么promise.all返回的就是拒绝状态的promise,并把第一个拒绝状态的promise的原因返回。所以代码可以这样写:
class MyPromise {
...
static all = (promises) => {
return new MyPromise((resolve,reject) => {
//如果传入的参数不是数组,抛出一个错误
if(!Array.isArray(promises)) return reject(new TypeError('Argument is not iterable'));
//用来保存数组中promise成功执行的返回结果
const result = [];
//如果传的是一个空数组那么直接将其返回
if(promises.length === 0) return resolve(promises);
//遍历数组
promises.forEach(promise => {
promise.then(res => {
//如果是正常完成,将其push到result数组中
result.push(res);
//如果result数组和传进来的数组长度一样,说明已全部执行完成。将result数组返回
result.length === promises.length && resolve(result);
},rej => {
reject(rej);
});
})
})
}
}
如果Promise.all()参数中的数组不是promise又该怎么进行处理呢?我们先看看原生的promise,我们执行下面的代码:
Promise.all([1,2,3]).then(res => {
console.log(res);
})
当执行上面代码时,控制台输出的是**[1,2,3]**也就是被没有对其进行任何的更改,按照原样将其返回了。在我们自己的Promise.all方法中并没有对这样的数据进行处理,所以我们得去改善下我们的代码。可以这样写:
class MyPromise {
...
static all = (promises) => {
return new MyPromise((resolve,reject) => {
//如果传入的参数不是数组,抛出一个错误
if(!Array.isArray(promises)) return reject(new TypeError('Argument is not iterable'));
const result = [];
//如果传的是一个空数组那么直接将其返回
if(promises.length === 0) return resolve(promises);
promises.forEach(promise => {
//判断参数数组中的值是不是promise,如果不是直接将其返回
+ if(promise instanceof MyPromise) {
promise.then(res => {
result.push(res);
result.length === promises.length && resolve(result);
},rej => {
reject(rej);
});
+ } else {
+ result.push(promise);
+ result.length === promises.length && resolve(result);
+ }
})
})
}
}
这样应该差不多了吧?不是的,还是有小问题。我们看看如果用原生的promise执行下面的代码:
const p1 = new Promise((res,rej) => {
res('p1成功')
})
const p2 = new Promise((res,rej) => {
res('p2成功');
})
Promise.all([p1,p2,123]).then(res => {
console.log(res);
})
原生的输出结果:
(3) ['p1成功', 'p2成功', 123]
而用我们自己手写的promise却是下面那个样子的:
(3) [123, 'p1成功', 'p1成功']
顺序好像不对。我们分析下为什么会这样子呢?我们看回我们的Promise.all()的方法,我们是直接去遍历数组,判断数组中是否为promise,如果是promise直接调用then方法。问题就是出现在这里,因为then方法里面的代码是异步的,他不会立即执行。他得等到同步任务执行完,也就是把123通过push方法放到result数组中后才会调用**result.push(res);**这个代码将promise执行完的结果放到result数组中。但此时的123已经先放进result数组中去了。所以我们不能用push方法将结果放到result数组中。我们的代码可以这样写:
class MyPromise {
...
static all = (promises) => {
return new MyPromise((resolve,reject) => {
//如果传入的参数不是数组,抛出一个错误
if(!Array.isArray(promises)) return reject(new TypeError('Argument is not iterable'));
const result = [];
//如果传的是一个空数组那么直接将其返回
if(promises.length === 0) return resolve(promises);
+ promises.forEach((promise,index) => {
if(promise instanceof MyPromise) {
promise.then(res => {
//通过下标将结果保存在相应的位置
+ result[index] = res;
result.length === promises.length && resolve(result);
},rej => {
reject(rej);
});
} else {
//通过下标将结果保存在相应的位置
+ result[index] = promise;
result.length === promises.length && resolve(result);
}
})
})
}
}
通过下标值,就能够准确的定位到数据的位置了,因为是通过下标来指定位置的,所以即使你先执行完。先放进去,但是你的索引没有改变,还是在原来的位置。
五、Promise.allSettled的实现
Promise.allSettled()方法可以传入一个数组,把数组中所有的promise执行并将执行完后的状态和值返回。Promise.allSettled()方法返回的也是一个promise。我们先看用原生的promise执行下面的例子:
const p1 = new Promise((res,rej) => {
res('p1成功')
})
const p2 = new Promise((res,rej) => {
rej('p2失败');
})
Promise.allSettled([p1,p2]).then(res => {
console.log(res);
})
输出的结果:
(2) [{…}, {…}]
0: {status: 'fulfilled', value: 'p1成功'}
1: {status: 'rejected', reason: 'p2失败'}
length: 2
[[Prototype]]: Array(0)
我们看到p1和p2两个promise的状态和结果都返回到了一个数组中。那代码是怎么实现的?其实原理跟Promise.all()方法差不多,将数组中各个promise执行完成之后将结果和状态保存到result数组中,然后将result数组通过resolve去改变状态就是了。我们看下面的代码:
class MyPromise {
...
static allSettled = (promises) => {
//要返回一个promise
return new MyPromise((resolve,reject) => {
//如果传入的参数不是数组,抛出一个错误
if(!Array.isArray(promises)) return reject(new TypeError('Argument is not iterable'));
//定义一个数组来保存执行完各个promise后的状态和结果
const result = [];
// 如果传入的是一个空数组,那么就直接返回一个resolved的空数组promise对象
if (promises.length === 0) return resolve(promises);
//遍历传进来的数组
promises.forEach((promise,index) => {
//判断此值是否为一个promise
if(promise instanceof MyPromise){
promise.then(res => {
//不管有没有出错将其保存到result数组中
result[index] = {
status: 'fulfilled',
value: res
}
//判断是否为最后一个promise,是的话将result返回
result.length === promises.length && resolve(result);
},rej => {
//不管有没有出错将其保存到result数组中
result[index] = {
status:'rejected',
reason:rej
}
//判断是否为最后一个promise,是的话将result返回
result.length === promises.length && resolve(result);
})
}
})
})
}
}
这样子只要是数组中的都是promise,那么我们也可以将数组中promise的结果和状态返回了。但是我们用原生的Promise.allSettled()方法,如果参数数组中的不是promise,也会返回状态和结果的。我们看下面的例子:
const p1 = new Promise((res,rej) => {
res('p1成功')
})
Promise.allSettled([p1,'123']).then(res => {
console.log(res);
})
我们执行上面的例子,我们看到控制台的输出是下面这样的:
(2) [{…}, {…}]
0: {status: 'fulfilled', value: 'p1成功'}
1: {status: 'fulfilled', value: '123'}
length: 2
[[Prototype]]: Array(0)
promise是把字符串123封装成了一个promise,并将状态改成了fulfilled状态。这段代码可以这样子写:
class MyPromise {
...
static allSettled = (promises) => {
//要返回一个promise
return new MyPromise((resolve,reject) => {
//如果传入的参数不是数组,抛出一个错误
if(!Array.isArray(promises)) return reject(new TypeError('Argument is not iterable'));
//定义一个数组来保存执行完各个promise后的状态和结果
const result = [];
// 如果传入的是一个空数组,那么就直接返回一个resolved的空数组promise对象
if (promises.length === 0) return resolve(promises);
//遍历传进来的数组
promises.forEach((promise,index) => {
//判断此值是否为一个promise
if(promise instanceof MyPromise){
promise.then(res => {
//不管有没有出错将其保存到result数组中
result[index] = {
status: 'fulfilled',
value: res
}
//判断是否为最后一个promise,是的话将result返回
result.length === promises.length && resolve(result);
},rej => {
//不管有没有出错将其保存到result数组中
result[index] = {
status:'rejected',
reason:rej
}
//判断是否为最后一个promise,是的话将result返回
result.length === promises.length && resolve(result);
})
+ }else {
+ //如果不是一个promise
+ MyPromise.resovle(promise).then(res => {
+ //不管有没有出错将其保存到result数组中
+ result[index] = {
+ status:'fulfilled',
+ value:res
+ }
+ //判断是否为最后一个promise,是的话将result返回
+ result.length === promises.length && resolve(result);
})
}
})
})
}
}
这个样子,我们的Promise.allSettled()方法也完成了。Promise.allSettled()他是要把所有的promise执行完后的状态和值返回,所以我们返回结果的数组中的值要是一个对象,这个对象中包含了promise执行完之后的状态和值。如果参数数组中的值不是一个promise,则将其转换为promise并且变为成功状态,然后将其返回。
六、Promise.any的实现
Promise.any()方法也是会返回一个promise,感觉跟Promise.all()的功能是相反的,Promise.any()他是如果数组中所有的promise都是拒绝状态的话会返回AggregateError: All promises were rejected,只要有一个promise是成功的,那么就返回这个成功的promise。我们可以看下面的例子:
const p1 = new Promise((res,rej) => {
rej('p1失败')
})
const p2 = new Promise((res,rej) => {
rej('p2失败')
})
Promise.any([p1,p2]).then(res => {
console.log(res);
},rej => {
console.log(rej);
})
上面的p1和p2两个promise都是拒绝状态的,我们看到控制台打印的结果是:
AggregateError: All promises were rejected
如果把测试的例子改成下面的样子:
const p1 = new Promise((res,rej) => {
rej('p1失败')
})
const p2 = new Promise((res,rej) => {
res('p2成功')
})
Promise.any([p1,p2]).then(res => {
console.log(res);
},rej => {
console.log(rej);
})
他会把p2这个成功的promise返回,输出结果如下:
p2成功
那我们分析清楚了这些后,我们开始写我们自己的Promise.any方法:
class MyPromise {
...
static any = (promises) => {
return new MyPromise((resolve,reject) => {
//如果传入的参数不是数组,抛出一个错误
if(!Array.isArray(promises)) return reject(new TypeError("Argument is not iterable"));
//如果传进来的数组是一个空数组,直接将其按全部拒绝返回
if(promises.length === 0) reject(new AggregateError('All promises were rejected'));
//设置一个返回结果的数组来保存结果
const result = [];
//遍历数组
promises.forEach((promise,index) => {
MyPromise.resolve(promise).then(res => {
//如果有成功状态的promise,直接将其返回
resolve(res);
},rej => {
//如果是失败状态的promise,将其保存到result数组中
result[index] = rej;
//判断是否为最后一个promise,是的话将result返回
result.length === promises.length && reject(new AggregateError(result));
})
})
})
}
}
这样Promise.any()方法也完成了,他是等所有的promise方法都是拒绝状态才会返回,如果有一个promise是成功的,则直接将这个成功的promise返回。
七、Promise.race的实现
Promise.race()方法也是传入一个数组,返回的是数组中最快执行完的promise,不管这个promise的状态是成功的还是拒绝的,都会将其返回。不过,我们需要注意的是如果传的参数数组是空的,则返回的 promise 将永远等待。 分析完这些情况后,我们可以写代码:
class MyPromise {
...
static race = (promises) => {
return new MyPromise((resolve,reject) => {
//如果传进来的参数不是数组,则抛出错误
if(!Array.isArray(promises)) return reject(new TypeError("Argument is not iterable"));
if(promises.length > 0) {
//遍历数组
promises.forEach(promise => {
//调用我们此前用的resolve静态方法变成promise然后改变状态将其返回
MyPromise.resolve(promise).then(res => {
resolve(res);
},rej => {
reject(rej);
})
})
}
})
}
}
上面就是Promise.race方法的实现思路,就是把最先执行完的promise返回就可以。
八、Promise.prototype.catch的实现
平时我们在使用promise的时候可以用catch方法来捕获异常,那么这个catch方法是怎么实现的呢?我们一起来看看吧。
class MyPromise {
...
catch = (error) => {
return this.reject(null,error)
}
}
其实他内部调用的是我们前面写的.then(undefined, reject)方法。
九、Promise.prototype.finally的实现
finally是一定会执行的一个方法, 无论结果是fulfilled或者是rejected 都是会执行的。所以代码可以这样子写:
class MyPromise {
...
finally = (callBack) => {
return this.then(callBack, callBack)
}
}