之前写过一篇关于promise的一些常规用法,以及promise与async/await关系的文章。但是我们知道,要想把一个知识点完全掌握,知其然而不知其所以然是远远不够的,那么接下来将要来探讨promise的原理,来分析分析promise的这种规则机制是如何实现的。我们通过手写promise的方式达到这个目的,同时来加深对primise的理解。
思路
手写promise
之前,我们先来回忆一下promise
的使用方式,便于整理思路。
const p1 = new Promise((resolve, reject)=>{
console.log('此处同步执行');
const a = 2;
setTimeout(()=>{
// 此处异步执行
if(a>0){
resolve('成功');
}else{
reject('失败原因');
}
},1000)
});
p1.then((res)=>{
console.log(res);
},(err)=>{
console.log(err);
})
从上面的代码可以看出,要想使用promise
需要先通过new Promise(fn)
生成一个实例p1
,然后通过实例p1
调用then()
和catch()
方法。因此可以得出以下几点结论:
-
Promise
本身是一个构造函数,其参数fn
是一个同步执行的回调函数,该函数执行的参数也是两个函数resolve
和reject
。这两个参数的作用是等异步操作执行完成后,为后续方法的执行传参,如:then()
和catch()
。 -
then()
用两个函数作为参数,在实例p1
中的resolve
和reject
方法中分别触发对应的函数,并把异步操作执行的结果传递给对应的函数。 -
Promise
有三种状态:pending
、rejected
和resolved
,同步回调函数fn
开始执行时状态为pending
,执行了resolve
和reject
后会将其状态改为resolved
和rejected
。resolved和rejected只能执行一次,且Promise
状态一旦被确定下来,那么就是不可更改的(锁定)。
通过观察Promise
的使用方式得出的几点结论,书写promise的思路大致可以通过下面几个方面来完成:
- 定义
Promise
的三种状态; - 创建构造函数,并为构造函数定义一个回调函数作为参数;
- 在构造函数内定义变量来保存
Promise
的状态,定义两个函数resolve
和reject
,并在构造函数中执行回调函数的时候将此传入; - 函数
resolve
和reject
目前的作用是改变Promise
的状态,保存异步操作返回的值或者失败的原因; - 为构造函数创建
then()
方法,then()
方法的参数是两个函数onResolved
、onRejected
,这两个函数将被传入构造函数内定义的resolve
和reject
方法中执行。此时函数resolve
和reject
发挥了它的第二个作用,就是执行then()
方法传递过来的回调函数。
实现
有了大致的思路,那么接下来就是如何去实现它。
- Promise构造函数的设计,对应思路1、2、3、4
const PROMISE_STATUS_PENDING = 'pending';
const PROMISE_STATUS_FULFILLED = 'fulfilled';
const PROMISE_STATUS_REJECTED = 'rejected';
class myPromise {
// * 记录状态
constructor(executor) {
// * 保存Promise的状态
this.status = PROMISE_STATUS_PENDING;
// * 保存传入的值
this.value = undefined;
this.reason = undefined;
const resolve = value => {
if (this.status == PROMISE_STATUS_PENDING) {
this.status = PROMISE_STATUS_FULFILLED;
this.value = value;
console.log('resolve被调用');
}
};
const reject = reason => {
if (this.status == PROMISE_STATUS_PENDING) {
this.status = PROMISE_STATUS_REJECTED;
this.reason = reason;
console.log('reject被调用');
}
};
executor(resolve, reject);
}
}
- 定义好构造函数,接下来的任务就是书写构造函数的方法了,对应5。修改上面的代码如下:
const PROMISE_STATUS_PENDING = 'pending';
const PROMISE_STATUS_FULFILLED = 'fulfilled';
const PROMISE_STATUS_REJECTED = 'rejected';
class myPromise {
// * 记录状态
constructor(executor) {
// * 保存Promise的状态
this.status = PROMISE_STATUS_PENDING;
// * 保存传入的值
this.value = undefined;
this.reason = undefined;
const resolve = value => {
if (this.status == PROMISE_STATUS_PENDING) {
this.status = PROMISE_STATUS_FULFILLED;
//* 定时器是一个宏任务,会放在下一次事件循环时使用
queueMicrotask(() => {
this.value = value;
console.log('resolve被调用', this.value);
// * 执行then传入进来的第一个回调函数
this.onResolved(this.value);
});
}
};
const reject = reason => {
if (this.status == PROMISE_STATUS_PENDING) {
this.status = PROMISE_STATUS_REJECTED;
queueMicrotask(() => {
this.reason = reason;
console.log('reject被调用', this.reason);
// * 执行then传入进来的第二个回调函数
this.onRejected(this.reason);
});
}
};
executor(resolve, reject);
}
// then方法
then(onResolved, onRejected) {
this.onResolved = onResolved;
this.onRejected = onRejected;
}
}
优化
完成以上代码已经搭建了一个具备基本功能的Promise
,不防试一下,相信它会带给你满意的结果。
const promise = new myPromise((resolve, reject) => {
console.log('状态pending');
resolve('1111');
});
promise.then(
res => {
console.log('res:', res);
},
err => {
console.log('err:', err);
},
);
运行以上代码,命令行会相继输出状态pending
、resolve被调用 1111
、res: 1111
等,这代表着最基础版的Promise
已经完成了。但是它仍然有很多问题,比如then()
方法无法多次调用和链式调用、没有catch()
方法等,所以接下来我们就要优化上面基础版的Promise
,使它具备和官方基本一致的功能。
- 实现
then()
的多次调用
then()
多次调用就需要在构造函数里定义两个数组保存then()
方法中传进来的回调,然后遍历这个数组,执行数组里的所有回调函数,修改代码如下:
const PROMISE_STATUS_PENDING = 'pending';
const PROMISE_STATUS_FULFILLED = 'fulfilled';
const PROMISE_STATUS_REJECTED = 'rejected';
class myPromise {
// * 记录状态
constructor(executor) {
// * 保存Promise的状态
this.status = PROMISE_STATUS_PENDING;
// * 保存传入的值
this.value = undefined;
this.reason = undefined;
this.onResolvedFns = [];
this.onRejectedFns = [];
const resolve = value => {
if (this.status == PROMISE_STATUS_PENDING) {
//* 定时器是一个宏任务,会放在下一次事件循环时使用
queueMicrotask(() => {
this.status = PROMISE_STATUS_FULFILLED;
this.value = value;
console.log('resolve被调用', this.value);
// * 执行then传入进来的第一个回调函数
this.onResolvedFns.forEach(Fn => {
Fn(this.value);
});
});
}
};
const reject = reason => {
if (this.status == PROMISE_STATUS_PENDING) {
queueMicrotask(() => {
this.status = PROMISE_STATUS_REJECTED;
this.reason = reason;
console.log('reject被调用', this.reason);
// * 执行then传入进来的第二个回调函数
this.onRejectedFns.forEach(Fn => {
Fn(this.reason);
});
});
}
};
executor(resolve, reject);
}
// then方法
then(onResolved, onRejected) {
console.log(this.status);
// 如果then方法调用的时候,状态已经确定下来了,应该直接执行的
if (this.status === PROMISE_STATUS_FULFILLED && onResolved) {
onResolved(this.value);
} else if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
onRejected(this.reason);
} else {
// 将成功回调和失败回调添加到数组中
this.onResolvedFns.push(onResolved);
this.onRejectedFns.push(onRejected);
}
}
}
- 实现
then()
的链式调用
从上面的代码中可以清楚的看到then()
方法是挂载在构造函数myPromise
上的,所以为了实现链式调用,需要在then()
方法里返回一个新的Promise
对象,然后使用新的Promise
对象的resolve
方法去处理对应的回调函数的返回值。从代码的简洁度考虑,我们需要封装一个工具函数,用来处理异常和回调函数。
与此同时,当回调函数executor
的执行发生异常时,也许有执行reject
|函数。因此,我们把代码调整如下:
const PROMISE_STATUS_PENDING = 'pending';
const PROMISE_STATUS_FULFILLED = 'fulfilled';
const PROMISE_STATUS_REJECTED = 'rejected';
// 工具函数
function execFunctionWithCatchError(exeFn, value, resolve, reject) {
try {
let result = exeFn(value);
resolve(result);
} catch (err) {
reject(err);
}
}
class myPromise {
// * 记录状态
constructor(executor) {
// * 保存Promise的状态
this.status = PROMISE_STATUS_PENDING;
// * 保存传入的值
this.value = undefined;
this.reason = undefined;
this.onResolvedFns = [];
this.onRejectedFns = [];
const resolve = value => {
if (this.status == PROMISE_STATUS_PENDING) {
// * 添加微任务
//* 定时器是一个宏任务,会放在下一次事件循环时使用
queueMicrotask(() => {
if (this.status !== PROMISE_STATUS_PENDING) return;
this.status = PROMISE_STATUS_FULFILLED;
this.value = value;
// console.log('resolve被调用', this.value);
// * 执行then传入进来的第一个回调函数
this.onResolvedFns.forEach(Fn => {
Fn(this.value);
});
});
}
};
const reject = reason => {
if (this.status == PROMISE_STATUS_PENDING) {
// * 添加微任务
queueMicrotask(() => {
if (this.status !== PROMISE_STATUS_PENDING) return;
this.status = PROMISE_STATUS_REJECTED;
this.reason = reason;
// console.log('reject被调用', this.reason);
// * 执行then传入进来的第二个回调函数
this.onRejectedFns.forEach(Fn => {
Fn(this.reason);
});
});
}
};
// * 在调用executor时判断里面是否抛出异常
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
// then方法
then(onResolved, onRejected) {
return new myPromise((resolve, reject) => {
// 如果then方法调用的时候,状态已经确定下来了,应该直接执行的
if (this.status === PROMISE_STATUS_FULFILLED && onResolved) {
// onResolved(this.value);
execFunctionWithCatchError(onResolved, this.value, resolve, reject);
} else if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
// onRejected(this.reason);
execFunctionWithCatchError(
onRejected,
this.reason,
resolve,
reject,
);
} else {
// 将成功回调和失败回调添加到数组中
this.onResolvedFns.push(() => {
execFunctionWithCatchError(
onResolved,
this.value,
resolve,
reject,
);
});
this.onRejectedFns.push(() => {
execFunctionWithCatchError(
onRejected,
this.reason,
resolve,
reject,
);
});
}
});
}
}
-
catch()
方法的实现
我们知道,在官方提供的Promise
中可不止then()
一种方法,其中最常用的便是catch()
方法了。
catch()
与then()
方法不同,它只会接受一个onRejected
的回调函数,catch()
方法执行的其实是then()
方法第二个参数的工作,then(null, function() {})
就等同于catch(function() {})
。但是用this.then(undefined,onRejected);
来实现catch()
方法显然是不可以的,因为这样做的话,catch()
方法是针对新的Promise
的rejected
的状态,我们要解决的问题就是如何让catch()
方法捕获原Promise
对象的rejected
状态。
所以我们要对then()
方法做一些改动,在方法内部前面判断第二个参数是否有值,如果没有值,就重新赋值为一个函数,函数内部抛出一个异常。这样在新的Promise
就能捕获到原来的promise
的rejected
的状态了。具体实现方式如下:
const PROMISE_STATUS_PENDING = 'pending';
const PROMISE_STATUS_FULFILLED = 'fulfilled';
const PROMISE_STATUS_REJECTED = 'rejected';
// 工具函数
function execFunctionWithCatchError(exeFn, value, resolve, reject) {
try {
let result = exeFn(value);
resolve(result);
} catch (err) {
reject(err);
}
}
class myPromise {
// * 记录状态
constructor(executor) {
// * 保存Promise的状态
this.status = PROMISE_STATUS_PENDING;
// * 保存传入的值
this.value = undefined;
this.reason = undefined;
this.onResolvedFns = [];
this.onRejectedFns = [];
const resolve = value => {
if (this.status == PROMISE_STATUS_PENDING) {
// * 添加微任务
//* 定时器是一个宏任务,会放在下一次事件循环时使用
queueMicrotask(() => {
if (this.status !== PROMISE_STATUS_PENDING) return;
this.status = PROMISE_STATUS_FULFILLED;
this.value = value;
// console.log('resolve被调用', this.value);
// * 执行then传入进来的第一个回调函数
this.onResolvedFns.forEach(Fn => {
Fn(this.value);
});
});
}
};
const reject = reason => {
if (this.status == PROMISE_STATUS_PENDING) {
// * 添加微任务
queueMicrotask(() => {
if (this.status !== PROMISE_STATUS_PENDING) return;
this.status = PROMISE_STATUS_REJECTED;
this.reason = reason;
// console.log('reject被调用', this.reason);
// * 执行then传入进来的第二个回调函数
this.onRejectedFns.forEach(Fn => {
Fn(this.reason);
});
});
}
};
// * 在调用executor时判断里面是否抛出异常
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
// then方法
then(onResolved, onRejected) {
onRejected =
onRejected ||
(err => {
throw err;
});
return new myPromise((resolve, reject) => {
// 如果then方法调用的时候,状态已经确定下来了,应该直接执行的
if (this.status === PROMISE_STATUS_FULFILLED && onResolved) {
// onResolved(this.value);
execFunctionWithCatchError(onResolved, this.value, resolve, reject);
} else if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
// onRejected(this.reason);
execFunctionWithCatchError(
onRejected,
this.reason,
resolve,
reject,
);
} else {
// 将成功回调和失败回调添加到数组中
if (onResolved)
this.onResolvedFns.push(() => {
execFunctionWithCatchError(
onResolved,
this.value,
resolve,
reject,
);
});
if (onRejected)
this.onRejectedFns.push(() => {
execFunctionWithCatchError(
onRejected,
this.reason,
resolve,
reject,
);
});
}
});
}
// * catch方法
catch(onRejected) {
this.then(undefined, onRejected);
}
}
-
finally()
方法的实现
前面讲到的then()
和catch()
都是Promise
的实例方法,也可以称为对象方法,除此之外,Promise
还有一个实例方法,那就是finally()
。finally()
方法大致可以概括如下:
-
finally
是ES9(ES2018)新增的一个特性:表示无论Promise
的状态变为resolved
还是reject
,最终都会被执行的代码。 -
finally
方法是不接收参数的,因为无论前面是resolved
状态,还是reject
状态,它都会执行。 -
finally
其实也是返回一个Promise对象,但是其实很少人会用它。
因此,finally
方法的实现也很简单,只需要在Promise
构造函数中定义一个方法,无论promise
的状态是什么。代码实现如下:
const PROMISE_STATUS_PENDING = 'pending';
const PROMISE_STATUS_FULFILLED = 'fulfilled';
const PROMISE_STATUS_REJECTED = 'rejected';
// 工具函数
function execFunctionWithCatchError(exeFn, value, resolve, reject) {
try {
let result = exeFn(value);
resolve(result);
} catch (err) {
reject(err);
}
}
class myPromise {
// * 记录状态
constructor(executor) {
// * 保存Promise的状态
this.status = PROMISE_STATUS_PENDING;
// * 保存传入的值
this.value = undefined;
this.reason = undefined;
this.onResolvedFns = [];
this.onRejectedFns = [];
const resolve = value => {
if (this.status == PROMISE_STATUS_PENDING) {
// * 添加微任务
//* 定时器是一个宏任务,会放在下一次事件循环时使用
queueMicrotask(() => {
if (this.status !== PROMISE_STATUS_PENDING) return;
this.status = PROMISE_STATUS_FULFILLED;
this.value = value;
// console.log('resolve被调用', this.value);
// * 执行then传入进来的第一个回调函数
this.onResolvedFns.forEach(Fn => {
Fn(this.value);
});
});
}
};
const reject = reason => {
if (this.status == PROMISE_STATUS_PENDING) {
// * 添加微任务
queueMicrotask(() => {
if (this.status !== PROMISE_STATUS_PENDING) return;
this.status = PROMISE_STATUS_REJECTED;
this.reason = reason;
// console.log('reject被调用', this.reason);
// * 执行then传入进来的第二个回调函数
this.onRejectedFns.forEach(Fn => {
Fn(this.reason);
});
});
}
};
// * 在调用executor时判断里面是否抛出异常
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
// then方法
then(onResolved, onRejected) {
onRejected =
onRejected ||
(err => {
throw err;
});
return new myPromise((resolve, reject) => {
// 如果then方法调用的时候,状态已经确定下来了,应该直接执行的
if (this.status === PROMISE_STATUS_FULFILLED && onResolved) {
// onResolved(this.value);
execFunctionWithCatchError(onResolved, this.value, resolve, reject);
} else if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
// onRejected(this.reason);
execFunctionWithCatchError(
onRejected,
this.reason,
resolve,
reject,
);
} else {
// 将成功回调和失败回调添加到数组中
if (onResolved)
this.onResolvedFns.push(() => {
execFunctionWithCatchError(
onResolved,
this.value,
resolve,
reject,
);
});
if (onRejected)
this.onRejectedFns.push(() => {
execFunctionWithCatchError(
onRejected,
this.reason,
resolve,
reject,
);
});
}
});
}
// * catch方法
catch(onRejected) {
this.then(undefined, onRejected);
}
// finally方法
finally(onFinally) {
return this.then(
() => {
onFinally();
},
() => {
onFinally();
},
);
}
}
-
Promise
的类方法的实现
前面说到的then
、catch
、finally
方法都属于Promise
的实例方法,都是存放在Promise
的prototype
上的。下面我们将学习类方法。
-
Promise.resolve()
方法:有时候我们已经有一个现成的内容,希望将其转成Promise
来使用,这个时候我们可以使用Promise.resolve()
方法来完成,所以Promise.resolve()
相当于new Promise
,并且执行resolve
操作。 -
Promise.reject()
方法:reject
方法类似于resolve
方法,只是会将Promise对象的状态设置为rejected状态,所以Promise.reject
无论传过来的参数是什么状态,都会直接作为reject
的参数传递到catch
的。 -
Promise.all()
方法:Promise.all()
我们在上一篇中讲过,对于用法这里不再多做阐述,大致可以归纳为只有当所有的promise
都变成resolved
状态时,原promise
才会变成resolved
状态,相反当任意一个promise
变成rejected
状态时,原promise
就会变成rejected
状态,并且仍然处于pending
状态的promise
将不会获取到结果。不明白的同学请自行查阅Promise 与async/await。 -
Promise.allSettled()
方法:Promise.allSettled
是ES11(ES2020)中新添加的API,它用于解决Promise.all()
方法的一个缺陷(也是其特征):当有一个Promise
变成rejected
状态时,新Promise
就会立即变成对应的rejected
状态。 Promise.allSettled方法会在所有的
Promise都有结果时,无论是
resolved,还是
rejected,才会有最终的状态,并且这个
Promise的结果一定是
resolved`。 -
Promise.race()
方法:Promise.race()
方法同样在上一篇中讲过,大致可以归纳为:数组中的其中一个promise
返回状态时,无论此状态是resolved
或者rejected
,它都将会成为原promise
的状态,即先到先得。 -
Promise.any()
方法:Promise.any()
方法是ES12中新增的方法,和Promise.race()
方法是类似的。Promise.any()
方法会等到一个resolved
状态,才会决定新Promise
的状态,就算所有的Promise
都是rejected
的,那么也会等到所有的Promise
都变成rejected
状态,err信息为:AggregateError: All promises were rejected
。
看完所有Promise
类方法的使用,接下来我们就开下怎么来实现他们吧。
const PROMISE_STATUS_PENDING = 'pending';
const PROMISE_STATUS_FULFILLED = 'fulfilled';
const PROMISE_STATUS_REJECTED = 'rejected';
// 工具函数
function execFunctionWithCatchError(exeFn, value, resolve, reject) {
try {
const result = exeFn(value);
resolve(result);
} catch (err) {
reject(err);
}
}
class myPromise {
// * 记录状态
constructor(executor) {
// * 保存Promise的状态
this.status = PROMISE_STATUS_PENDING;
// * 保存传入的值
this.value = undefined;
this.reason = undefined;
this.onResolvedFns = [];
this.onRejectedFns = [];
const resolve = value => {
if (this.status == PROMISE_STATUS_PENDING) {
// * 添加微任务
//* 定时器是一个宏任务,会放在下一次事件循环时使用
queueMicrotask(() => {
if (this.status !== PROMISE_STATUS_PENDING) return;
this.status = PROMISE_STATUS_FULFILLED;
this.value = value;
// console.log('resolve被调用', this.value);
// * 执行then传入进来的第一个回调函数
this.onResolvedFns.forEach(Fn => {
Fn(this.value);
});
});
}
};
const reject = reason => {
if (this.status == PROMISE_STATUS_PENDING) {
// * 添加微任务
queueMicrotask(() => {
if (this.status !== PROMISE_STATUS_PENDING) return;
this.status = PROMISE_STATUS_REJECTED;
this.reason = reason;
// console.log('reject被调用', this.reason);
// * 执行then传入进来的第二个回调函数
this.onRejectedFns.forEach(Fn => {
Fn(this.reason);
});
});
}
};
// * 在调用executor时判断里面是否抛出异常
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
// then方法
then(onResolved, onRejected) {
onRejected =
onRejected ||
(err => {
throw err;
});
return new myPromise((resolve, reject) => {
// 如果then方法调用的时候,状态已经确定下来了,应该直接执行的
if (this.status === PROMISE_STATUS_FULFILLED && onResolved) {
// onResolved(this.value);
execFunctionWithCatchError(onResolved, this.value, resolve, reject);
} else if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
// onRejected(this.reason);
execFunctionWithCatchError(
onRejected,
this.reason,
resolve,
reject,
);
} else {
// 将成功回调和失败回调添加到数组中
if (onResolved)
this.onResolvedFns.push(() => {
execFunctionWithCatchError(
onResolved,
this.value,
resolve,
reject,
);
});
if (onRejected)
this.onRejectedFns.push(() => {
execFunctionWithCatchError(
onRejected,
this.reason,
resolve,
reject,
);
});
}
});
}
// * catch方法
catch(onRejected) {
this.then(undefined, onRejected);
}
// finally方法
finally(onFinally) {
return this.then(
() => {
onFinally();
},
() => {
onFinally();
},
);
}
// 类方法resolve
static resolve(value) {
return new myPromise((resolve, reject) => {
resolve(value);
});
}
// 类方法reject
static reject(reason) {
return new myPromise((resolve, reject) => {
reject(reason);
});
}
// 类方法all
static all(promises) {
// * 问题关键:什么时候执行resolve,什么时候执行reject
return new myPromise((resolve, reject) => {
let values = [];
promises.forEach(promise => {
promise
.then(res => {
values.push(res);
if (values.length == promises.length) resolve(values);
})
.catch(err => {
reject(err);
});
});
});
}
// 类方法allSettled
static allSettled(promises) {
return new myPromise((resolve, reject) => {
let results = [];
promises.forEach(promise => {
promise
.then(res => {
results.push({ status: PROMISE_STATUS_FULFILLED, value: res });
if (results.length == promises.length) resolve(results);
})
.catch(err => {
results.push({ status: PROMISE_STATUS_REJECTED, value: err });
if (results.length == promises.length) resolve(results);
});
});
});
}
// 类方法race
static race(promises) {
return new myPromise((resolve, reject) => {
promises.forEach(promise => {
// promise.then(res=>{
// resolve(res);
// }).catch(err=>{
// reject(err);
// })
promise.then(resolve, reject);
});
});
}
// 类方法any
static any(promises) {
// * resolve 必须等待有一个成功的结果
// * reject 所有的都失败才执行 reject
return new myPromise((resolve, reject) => {
let reasons = [];
promises.forEach(promise => {
promise
.then(res => {
resolve(res);
})
.catch(err => {
reasons.push(err);
if (reasons.length == promises.length) {
reject(
new AggregateError(
reasons,
' AggregateError: All promises were rejected',
),
);
}
});
});
});
}
}
说了这么多,最后放上一张Promise
的知识点关系图作为结束。