
前言
promise相信大家都会使用,已经存在很多年了,很多项目都存在promise的身影,在解决异步问题上promise功不可没,所以今天就来探索一下promise的源码,感受一下他的魅力
从本篇文章我们能学到什么?
- 学习ts的使用
- 学习promise的常用方法有哪些,以及用法有哪些
- 异步的思路是什么?异步就是先把函数先保存起来,等触发的时候,在执行
- 学习promise.all,promise.allSettle,promise.race, promise.any等的使用场景
- 学习透传
手写promise总结
- 先写出如何使用
- 再写出使用特点
- 基本方法返回的都是promise
为什么有promise?
在promise诞生前都有哪些异步的方式?
-
callback回调函数
缺点:回调地狱 -
发布/订阅模式 缺点:需要定义全局对象,事件名称很多会冲突
所以我们要理解promise就是解决上面的缺点,但是他没有什么神奇的地方,他会吸取callback和发布/订阅模式的优点, 他成了回调函数和观察者模式的结合体
我们都要实现哪些功能
- 同步
- 异步
- 链式调用
- 透传
promise解决了什么问题?
1)回调地狱,可读性变好
2)promise可以支持多个并发的请求,获取并发请求中的数据
3)可以处理异常
4)使用发布/订阅模式处理异步问题,全局变量难以维护,变量名冲突的问题
promise源码
promise初始化
const promise = new Promise((resolve, reject) => {
resolve("111");
});
promise.then(
(result) => {
console.log(result);
},
(reason) => {
console.log("原因", reason);
}
);
很简单的一个例子,输出111
promise规范
要想写出promise,先来了解4条promise规范,很简单的规范
-
promise是一个拥有then方法的对象或者函数
then方法相当于观察者模式中的订阅,所以这就是为什么then需要传入函数
-
promise有三种状态,只能从等待状态到成功或者失败()
- 为什么要有三种状态?防止多次调用,因为你如果调用两次
resolve,那么只能以第一次的为准,第一次resolve后,状态直接就变为SUCCESS,那么也就不会在执行后面的reslove了
-
then方法返回一个新的promise对象
- 为什么要返回
promise,为了支持链式调用
-
promise初始化传入一个函数,函数上有两个参数,resolve,reject,他们也都是函数
- promise中的resolve,reject也就相当于观察者模式中的notify
首先我们初始化一下promise,三种状态,有一个then方法的类
const PENDING = "PENDING";
const SUCCESS = "SUCCESS";
const FAIL = "FAIL";
class MyPromise{
construct(){}
then(){}
}
接着我们完善construct,接受一个函数,函数上有两个函数resolve,reject,resolve接受result,reject接受reason,还要定义onSuccessCb,onFailCb,就相当于观察者模式中存储回调的数组,其实promise中回调都只有一个
constructor(execute) {
// 初始status
this.status = PENDING;
this.value = "";
this.reason = "";
// 为什么我们要使用箭头函数,因为箭头函数中的this指向MyPromise
const resolve = (result) => {
// 从PENDING变为SUCCESS状态
if (this.status === PENDING) {
this.status = SUCCESS;
this.value = result;
this.onSuccessCb.forEach((fn) => fn());
}
};
const reject = (reason) => {
// 从PENDING变为FAIL状态
if (this.status === PENDING) {
this.status = FAIL;
this.reason = reason;
}
};
// 执行excute方法,然后执行回调resolve或者reject
execute(resolve, reject);
}
是不是感觉很简单
这不就是用了回调函数的原理么,没有其他的魔法
上面的内容我们很容易就写出来了,接着写then方法,返回一个promise,then方法有两个函数,onSuccess,onFail
then(onSuccess, onFail) {
const pr = new MyPromise((resolve, reject) => {
if (this.status === SUCCESS) {
onSuccess(this.value);
}
if (this.status === FAIL) {
onFail(this.reason);
}
});
return pr;
}
测试一下,把promise换成我们的MyPromise
const promise = new MyPromise((resolve, reject) => {
resolve("111");
});
promise.then(
(result) => {
console.log(result);
},
(reason) => {
console.log("原因", reason);
}
);
也可以输出111了,棒棒哒,但是我们现在这个例子并没有实现异步功能,于是我们改写一下测试的例子,加入setTimeout
then链式调用,透传,值为promise
const promise = new MyPromise((resolve, reject) => {
setTimeout(()=>{
resolve("111");
},2000);
});
我们可以想一下,如果加入异步代码,那么promise.then的时候promise是什么状态?肯定是PENDING状态,那么我们这时候需要在then方法中加入status为PENDING的情况,then方法有两个参数onSuccess,onFail,那么我们直接执行onSuccess方法肯定是不对的,因为异步后的result还没有返回,所以我们应该把方法先保存下来,在resolve方法执行的时候在执行
class MyPromise {
constructor(execute) {
this.status = PENDING;
this.value = "";
this.reason = "";
this.onSuccessCb = [];
const resolve = (result) => {
if (this.status === PENDING) {
this.status = SUCCESS;
this.value = result;
// resolve的时候执行
this.onSuccessCb.forEach((fn) => fn());
}
};
const reject = (reason) => {
if (this.status === PENDING) {
this.status = FAIL;
this.reason = reason;
}
};
execute(resolve, reject);
}
then(onSuccess, onFail) {
const pr = new MyPromise((resolve, reject) => {
// 这是同步的时候执行的
if (this.status === SUCCESS) {
let x = onSuccess(this.value);
resolve(x);
}
if (this.status === FAIL) {
let x = onFail(this.reason);
reject(x);
}
// 异步的时候会是PEINDG,状态还没有改变的时候
if (this.status === PENDING) {
// 保存函数,但并不执行, 一定要考虑回调函数什么时候执行
this.onSuccessCb.push(() => {
let x = onSuccess(this.value);
resolve(x);
});
this.onFailCb.push(()=>{
let x = onFail(this.reason);
reject(x);
})
}
});
return pr;
}
}
如上面这般,我们实现了then的链式调用和异步方法,新的需求又来了 then方法的第一个参数为null,该怎么办
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
reject("报错了");
}, 2000);
});
const pr = promise.then(
(result) => {
return result;
}
);
pr.then(null,(error) => {
console.log(error);
});
那么我们就要实现值穿透了,当then中传入的不是函数,则这个promise返回上一个promise的值
then(onSuccess, onFail) {
// 实现值穿透 当then中传入的不是函数,则这个promise返回上一个promise的值
// 相当于你在then中写了一个(value)=>{return value}
onSuccess = typeof onSuccess==='function'?onSuccess:value => value;
onFail = typeof onFail ==='function'?onFail: reason => reason;
// 用于实现链式调用
const pr = new MyPromise((resolve, reject) => {
let success = ()=>{
try{
const result = onSuccess(this.value);
resolve(result);
}catch(err){
reject(err);
}
}
let fail = ()=>{
try{
const reason = onFail(this.reason);
reject(result);
}catch(err){
reject(err);
}
}
switch(this.status){
case "PENDING":
this.onSuccessCb.push(success);
this.onFailCb.push(fail);
break;
case "SUCCESS":
success();
break;
case "FAIL":
fail();
break;
}
});
return pr;
}
下一个需求,如果then中的第一个函数或者第二个函数返回promise该如何,就是如何实现调用完一个请求后,如何在下一个then中调用另一个请求
then(onSuccess, onFail) {
// 实现值穿透 当then中传入的不是函数,则这个promise返回上一个promise的值
onSuccess = typeof onSuccess==='function'?onSuccess:value => value;
onFail = typeof onFail ==='function'?onFail: reason => reason;
// 用于实现链式调用
// 使用策略模式
const pr = new MyPromise((resolve, reject) => {
let success = ()=>{
try{
const result = onSuccess(this.value);
return result instanceof MyPromise?result.then((value)=>resolve(value),(reason)=>reject(reason)):resolve(result);
}catch(err){
reject(err);
}
}
let fail = ()=>{
try{
const reason = onFail(this.reason);
return reason instanceof MyPromise?reason.then(resolve,reject):reject(result);
}catch(err){
reject(err);
}
}
switch(this.status){
case "PENDING":
this.onSuccessCb.push(success);
this.onFailCb.push(fail);
break;
case "SUCCESS":
success();
break;
case "FAIL":
fail();
break;
}
});
return pr;
}
这样我们的then方法基本就完备了
catch方法
catch方法用于捕获异常,而then方法的第二个函数也用于捕获异常,那么是不是把catch的onRejected放到then的第二个函数就行了,这是第一点,第二点,catch方法返回一个promise,所以只要return this.then(null,onRejected)就可以了,good
const p = new Promise((resolve)=>{
resolve(1);
})
p.then(()=>{
throw new Error('5');
}).catch(error=>{
console.log(error,'result');
})
catch(onRejected){
return this.then(null,onRejected);
}
Promise.resolve() 方法
resolve相当于啥?相当于观察者模式中的通知呀,一般会直接传入一个值,凡是有例外,如果单独使用resolve方法,那么传入的还有可能是promise
传入的参数的类型
- 基本类型
promise类型
因为resolve后面跟的方法是then,所以你传入promise那就可以直接返回了,你传入基本类型还需要用promise包裹一下
const p = new Promise((resolve)=>{
resolve(1);
})
let c=p.then(()=>{
throw new Error('5');
})
Promise.resolve(c).then(()=>{
console.log('result');
},()=>{
console.log("reject");
})
// 输出:reject
static resolve(value){
if(value instanceof MyPromise){
return value;
}else{
// 返回一个promise对象
// 用resolve包裹,改变状态
return new MyPromise((resolve,reject)=>resolve(value));
}
}
Promise.reject()方法
常见resolve,reject一般都是放值类型的数据,其实也可以放promise数据
需要注意的是:Promise.resolve()不一定会改变状态(如果传入的是一个promise,那么不会改变状态),但是Promise.reject()一定会改变状态
const p = new Promise((resolve)=>{
resolve(1);
})
let c=p.then(()=>{
return 7;
})
Promise.reject(c).then(()=>{
console.log('result');
},(result)=>{
console.log("reject",result);
})
// 输出 reject Promise { 7 }
static reject(reason){
// Promise.reject方法的参数会原封不动地作为reject的参数
return new MyPromise((resolve, reject) => reject(reason))
}
Promise.all()方法
我们先来介绍一下all方法
通俗来说,.all()的作用是接收一组异步任务,然后并行执行异步任务,并且在所有异步操作执行完后才执行回调。
Promise.all()方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例- 只有p1、p2、p3的状态都变成
fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数 - 只要p1、p2、p3之中有一个被
rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数
应用场景:
1, 多个promise并发请求
缺点:如果其中一个reject,都会崩掉
function runAsync (x) {
const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000));
return p;
};
Promise.all([runAsync(1), runAsync(2), runAsync(3)]).then(res => console.log(res))
static all(promiseArr) {
const length = promiseArr.length;
const values = new Array(length);
let count = 0;
return new MyPromise((resolve, reject) => {
for (let i = 0; i < length; i++){
// 要考虑传入的值不是promise的情况
// 为什么使用Promise.resolve(),因为如果是promise的直接return,如果不是的话,会套上一层
MyPromise.resolve(promiseArr[i]).then((value) => {
values[i] = value;
count++;
// 利用resolve和reject只能执行一次的特性
// 为什么放到then里面,因为then是异步的
if(count===length){
resolve(values)}
}).catch((error) => {
reject(error);
})
}
})
}
Promise.allSettled()方法
应用场景:通all,因为我们不希望有一个promise崩掉,全部崩掉,allSettled会返回所有的结果
和Promise.all()不同,Promise.allSettled()即使是遇到reject也会等待所有的promise到最后。所以我们只需要用一个array记录各个promise的success或者fail结果即可。
function runAsync (x) {
const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000));
return p;
};
Promise.allSettled([runAsync(1), runAsync(2), runAsync(3)]).then(res => console.log(res))
// [
// { status: 'fulfilled', value: 1 },
// { status: 'fulfilled', value: 2 },
// { status: 'fulfilled', value: 3 }
// ]
static allSettled(promises) {
let length = promises.length;
let count = 0;
let result = new Array(length);
return new Promise((resolve) => {
for (let i = 0; i < length; i++){
promises[i].then((value) => {
result[i] = {
status: SUCCESS,
value,
}
count++;
// 为什么放到then里面,因为promise是异步的
if (count === length) {
resolve(result);
}
}, (error) => {
result[i] = {
status: FAIL,
reason: error
}
count++;
// 为什么放到then里面,因为promise是异步的
if (count === length) {
resolve(result);
}
})
}
})
}
Promise.race() 方法
应用场景,比如我们要从多个服务器获取相同的功能,那么就可以使用race
缺点: 如果有一个崩掉,就会返回崩掉的结果
.race()的作用也是接收一组异步任务,然后并行执行异步任务,只保留取第一个执行完成的异步操作的结果,其他的方法仍在执行,不过执行结果会被抛弃。
// Promise.race()是一组集合中最先解决或最先拒绝的Promise,返回一个新的Promise。其他的异步方法继续执行
function runAsync (x) {
const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000));
return p;
};
Promise.race([runAsync(1), runAsync(2), runAsync(3)]).then(res => console.log(res))
return new Promise((resolve,reject)=>{
promiseArr.forEach(item=>{
MyPromise.resolve(item).then(
// 后面的在给resolve值,resolve已经不接受了,因为status已经改变了,status只有在PENDING的时候才接受值
val =>resolve(val),
err => reject(err)
)
})
})
Promise.any() 方法
应用场景,通race,但是会返回第一个成功的,如果全部失败,才会失败
MyPromise.any = function(promises){
return new Promise((resolve,reject)=>{
promises = Array.isArray(promises) ? promises : []
let len = promises.length
// 用于收集所有 reject
let errs = []
// 如果传入的是一个空数组,那么就直接返回 AggregateError
if(len === 0) return reject(new AggregateError('All promises were rejected'))
promises.forEach((promise)=>{
promise.then(value=>{
resolve(value)
},err=>{
len--
errs.push(err)
if(len === 0){
reject(new AggregateError(errs))
}
})
})
})
}
finally方法
new Promise((resolve, reject) => {
setTimeout(() => resolve("result"), 2000)
})
.finally(() => console.log("Promise ready"))
.then(result => console.log(result))
// Promise ready
// result
注意:
finally没有参数finally会将结果和 error 传递
finally(cb) {
return this.then((value) => {
cb();
return value;
}, (err) => {
cb();
return err;
})
}
返回的新promise的结果状态由什么来决定
const p = new Promise((resolve, reject) => reject('出错了'))
p.then(null, function (s) {
return s;
}).then((s) => {
console.log(s);
}, (s) => {
console.log('error',s);
});
输出 出错了 并没有输出console.log('error',s);的值
因为
详细表达:
-
- 如果抛出异常:新的promise变为rejected,reason为抛出的异常
-
- 如果返回的是非promise的任意值,新的promise变为resolved,value为返回的值
-
- 如果返回的是另一个新的promise,此promise的结果就成为新promise的结果
总结
基本上常用的promise方法源码都已经写了一遍,大家一定要动手实现一下,其实并没有那么难,要多写两边,会有不同的体会
参考文章
- promise原理
- Promise.then是如何实现链式调用的
- promise.all promise.race promise.allSettled的区别?用promise.all实现promise.allSettled
- Promise.any 的作用,如何自己实现一个 Promise.any
- 要就来45道Promise面试题一次爽到底(1.1w字用心整理)
- Promise.prototype.finally 的作用,如何自己实现 Promise.prototype.finally
- JS异步之Promise解决信任问题(一)
- Promise设计模式 - 简书 (jianshu.com)
- promise(5)promise.then()返回的新promise的结果状态由什么来决定_Lomon6的博客-CSDN博客_promise 返回
- Promise.any 的作用,如何自己实现一个 Promise.any - 掘金 (juejin.cn)