Promise 是一种异步解决方法,它的出现解决了使用回调函数处理异步逻辑,造成回调地狱的问题,本文主要从 Promise 的基本用法、Promise A+、手写 Promise 角度出发,深度分析 Promise。
一、Promise 基本用法
Promise 的含义
Promise 对象有两种状态:
- 对象的状态不受外部影响。Promise 有三种状态:
pending(进行中)、fulfilled(已成功)、rejected(已失败)
,只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。 - 状态一旦改变,就不会再变,其他任何时候都能得到这个结果。Promise 的状态改变只有两种可能:
pending -> fulfilled
或pending -> rejected
Promise 方法
- Promise 是一个构造函数,需要使用
new
关键字生成实例; - Promise 中有一个 executor 立即执行函数,每次 new 时,会立即执行(后面手写会具体讲解);
- executor 中有两个参数
resolve
和reject
方法,resolve 表示成功的回调,reject 表示失败的回调; - Promise.prototype.then():前面 resolve 和 reject 执行后,then 方法会接收传入的参数,then 方法的第一个参数
onFulfilled
表示执行 resolve 成功时的回调,then 方法的第二个参数onRejected
表示执行 reject 失败时的回调,两个回调函数都是可选的; - Promise.prototype.catch()方法:是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数;
- Promise.prototype.finally()方法:不管 Promise 对象的状态如何,最终都会执行此方法;
- Promise.all():用于将多个 Promise 实例包装成新的 Promise 实例,如:const p = Promise.all([p1, p2, p3]);当 p1、p2、p3 的状态都编程 fulfilled 时,p 状态才编程 fulfilled,此时 p1、p2、p3 返回的结果组成一个数组,传递给 p 的回调函数。如果其中一个状态是 rejected,则被 reject 的实例返回;
- Promise.race()方法:同 Promise.all()方法,将多个 Promise 实例组装成一个新的 Promise 实例,但是只要其中有任意一个实例状态改变,那么率先改变的 Promise 实例的返回值,就是返回的回调函数;
- Promise.allSettled()方法:接收一组 Promise 实例作为参数,包装成一个新的 Promise 实例,只有等所有参数都返回结果,不管状态是 fulfilled 还是 rejected,包装实例才结束;
- Promise.any()方法:接收一组 Promise 实例作为参数,包装成一个新的 Promise 实例,只要有一个实例的状态变为 fulfilled,包装实例就会变为 fulfilled;如果所有参数实例都是 rejected,包装函数的状态会变成 rejected;
- Promise.reslove()方法:
a. 如果参数是一个 Promise 实例,Promise 不做任何修改原样返回;
b. 如果参数是thenable
对象,即有then
方法的对象,那么这个对象会转为 Promise 对象,立即执行 then 方法;
c. 如果不具有 then 方法或者不是对象,Promise.resolve()方法会返回一个新的 Promise 对象,并将参数返回给回调;
d. 如果不带参数,直接返回一个 resolved 状态的 Promise 对象; - Promise.reject()方法:返回一个新的 Promise 对象,该状态为 rejected;
上面只简单介绍了 Promise 中存在的方法,并没有具体实例介绍每种方法是如何使用的,不了解的可以看阮一峰老师的 Promise。在第一次学习 Promise 时,就有很多疑问:Promise 的底层是如何实现异步操作的?Promise.then()方法是一个微任务,是如何实现的?如果不了解eventLoop
的同学自行网上查询,有很多相关文章。
二、Promise A+
手写 Promise 之前,先讲讲什么是 Promise A+,Promise 的底层实现规范,是依赖于Promise A+来实现的,英文好的建议阅读原版,下面是我翻译后的,如果不正确,请不宁指出。
一个 promise 表示异步操作的最终结果。与 promise 进行交互的主要方式是通过其 then 方法,该方法注册回调以接收 promise 的最终值或无法实现 promise 的原因。
该规范详细介绍了该 then 方法的行为,提供了可互操作的基础,所有 Promises / A +兼容的 Promise 实现都可以依靠该基础来提供。因此,该规范应被认为是非常稳定的。尽管 Promises / A +组织有时会通过向后兼容的微小更改来修订此规范,以解决新发现的极端情况,但只有在仔细考虑,讨论和测试之后,我们才会集成大型或向后不兼容的更改。
从历史上看,Promises / A +阐明了早期 Promises / A 提案的行为条款,将其扩展为涵盖实际行为,并省略了未指定或有问题的部分。
最后,核心的 Promises / A +规范不处理如何创建,履行或拒绝诺言,而是选择专注于提供一种可互操作的 then 方法。伴随规范中的未来工作可能涉及这些主题。
1. 术语
1.1. “promise”中有一个 then 方法,该方法时符合这个规范行为的函数或对象;
1.2. “thenable”定义 then 方法的对象和函数
1.3. “value”表示任何合法的 JavaScript 值(包括 undefined,ableable 或 promise)
1.4. “exception”表示使用 throw 抛出的值
1.5. “reason”表示 promise 被拒绝的原因
2. 必要条件
2.1 Promise 状态
promise 必须是这三个状态中的一种:pending(进行中)、fulfilled(已成功)、rejected(已失败)
2.1.1. 当一个 promise 处于 pending 的时候:
2.1.1.1. 它可能变为 fulfilled 状态或者 rejected 状态。
2.1.2. 当一个 promise 处于 fulfilled 状态的时候:
2.1.2.1. 一定不能转换为任何其它状态
2.1.2.2. 必须有一个不能改变的值
2.1.3. 当一个 promise 处于 rejected 状态的时候:
2.1.3.1. 一定不能转换为任何其它状态
2.1.3.2. 必须有一个不能改变的原因
在这里,”一定不能改变”意味着不变的身份(例如 ===),但是并不意味着深度不可变性。
2.2 then 方法
Promise 必须提供一个 then 方法来访问最终的值或原因。
Promise 的 then 方法接受俩个参数:
promise.then(onFulfilled, onRejected);
复制代码
2.2.1 onFulfilled 和 onRejected 都是可选的参数
2.2.1.1. 如果 onFulfilled 不是一个函数,它必须被忽略
2.2.1.2. 如果 onRejected 不是一个函数,它必须被忽略
2.2.2. 如果 onFulfilled 是一个函数
2.2.2.1. 它必须在 promise 的状态为 fulfilled 后调用,promise 的值作为它的第一个参数。
2.2.2.2. 它一定不能在 promise 的状态变为 fulfilled 前调用。
2.2.2.3. 它一定不能被调用多次。
2.2.3. 如果 onRejected 是一个函数
2.2.3.1. 它必须在 promise 变为 rejected 之后调用,用 promise 的结果 reason 作为它的第一个参数。
2.2.3.2. 它一定不能在 promise 状态变为 rejected 前调用。
2.2.4. 在执行上下文栈中只包含平台代码之前,onFulfilled 或 onRejected 一定不能被调用 [3.1]
2.2.5. onFulfilled 和 onRejected 必须被作为函数调用(没有 this 值) [3.2]
2.2.6. 同一个 promise 上,then 方法可以被调用多次
2.2.6.1. 如果 promise 是 fulfilled,所有相应的 onFulfilled 回调必须按照他们原始调用 then 的顺序执行
2.2.6.2. 如果 promise 是 rejected,所有相应的 onRejected 回调必须按照他们原始调用 then 的顺序执行
2.2.7. then 必须返回一个 promise [3.3]
promise2 = promise1.then(onFulfilled, onRejected);
复制代码
2.2.7.1. 如果 onFulfilled 或 onRjected 返回一个值 x,运行 promise 解决程序[Resolve]
2.2.7.2. 如果 onFulfilled 或 onRejected 抛出一个异常 e,promise2 必须用 e 作为原因被拒绝
2.2.7.3. 如果 onFulfilled 不是一个函数并且 promise1 被解决,promise2 必须用与 promise1 相同的值被解决
2.2.7.4. 如果 onRejected 不是一个函数并且 promise1 被拒绝,promise2 必须用与 promise1 相同的原因被拒绝
2.3 Promise 解决程序
promise 解决程序是一个抽象操作,它以一个 promise 和一个值作为输入,我们将其表示为[[Resolve]](promise, x)。如果 x 是一个 thenable,它尝试让 promise 采用 x 的状态,并假设 x 的行为至少在某种程度上类似于 promise。否则,它将会用值 x 解决 promise。
这种 thenable 的特性使得 Promise 的实现更具有通用性:只要其暴露一个遵循 Promise/A+协议的 then 方法即可。这同时也使遵循 Promise/A+规范的实现可以与那些不太规范但可用的实现能良好共存。
要运行[[Resolve]](promise, x),需要执行如下步骤:
2.3.1. 如果 promise 和 x 引用同一个对象,用一个 TypeError 作为原因来拒绝 promise
2.3.2. 如果 x 是一个 promise,采用它的状态:[3.4]
2.3.2.1. 如果 x 是 pending,promise 必须保持等 pending 状态,直到 x 变为 fulfilled 或 rejected 状态
2.3.2.2. 如果 x 是 fulfilled,用相同的 value 解决 promise
2.3.2.3. 如果 x 是 rejected,用相同的 reason 拒绝 promise
2.3.3. 否则,如果 x 是一个对象或函数
2.3.3.1. 让 then 成为 x.then。[3.5]
2.3.3.2. 如果检索属性 x.then 导致抛出了一个异常 e,用 e 作为原因拒绝 promise
2.3.3.3. 如果 then 是一个函数,用 x 作为 this 调用它。then 方法的参数为俩个回调函数,第一个参数叫做 resolvePromise,第二个参数叫做 rejectPromise:
2.3.3.3.1. 如果 resolvePromise 用一个值 y 调用,运行[[Resolve]](promise, y)。
2.3.3.3.2. 如果 rejectPromise 用一个原因 r 调用,用 r 拒绝 promise。
2.3.3.3.3. 如果 resolvePromise 和 rejectPromise 都被调用,或者对同一个参数进行多次调用,那么第一次调用优先,以后的调用都会被忽略。
2.3.3.3.4. 如果调用 then 抛出了一个异常 e
2.3.3.4.1. 如果 resolvePromise 或 rejectPromise 已经被调用,忽略它
2.3.3.4.2. 否则,用 e 作为原因拒绝 promise
2.3.3.4. 如果 then 不是一个函数,用 x 解决 promise
2.3.4. 如果 x 不是一个对象或函数,用 x 解决 promise
如果 promise 用一个循环的 thenable 链解决,由于[[Resolve]](promise, thenalbe)的递归特性,最终将导致[[Resolve]](promise, thenable)被再次调用,遵循上面的算法将会导致无限递归。规范中并没有强制要求处理这种情况,但也鼓励实现者检测这样的递归是否存在,并且用一个信息丰富的 TypeError 作为原因拒绝 promise。[3.6]
注解
3.1. 这里“平台代码”意味着引擎、环境以及 promise 的实现代码。在实践中,这需要确保 onFulfilled 和 onRejected 异步地执行,并且应该在 then 方法被调用的那一轮事件循环之后用新的执行栈执行。这可以用如 setTimeout 或 setImmediate 这样的“宏任务”机制实现,或者用如 MutationObserver 或 process.nextTick 这样的“微任务”机制实现。由于 promise 的实现被考虑为“平台代码”,因此在自身处理程序被调用时可能已经包含一个任务调度队列。
3.2. 严格模式下,它们中的 this 将会是 undefined;在非严格模式,this 将会是全局对象。
3.3. 假如实现满足所有需求,可以允许 promise2 === promise1。每一个实现都应该记录是否能够产生 promise2 === promise1 以及什么情况下会出现 promise2 === promise1。
3.4. 通常,只有 x 来自于当前实现,才知道它是一个真正的 promise。这条规则允许那些特例实现采用符合已知要求的 Promise 的状态。
3.5. 这个程序首先存储 x.then 的引用,之后测试那个引用,然后再调用那个引用,这样避免了多次访问 x.then 属性。此类预防措施对于确保访问者属性的一致性非常重要,因为访问者属性的值可能在俩次检索之间发生变化。
3.6. 实现不应该在 thenable 链的深度上做任意限制,并且假设超过那个任意限制将会无限递归。只有真正的循环才应该引发一个 TypeError;如果遇到一个无限循环的 thenable,永远执行递归是正确的行为。
三、模拟 Promise 实现
Promise.then()方法是异步任务,在 Promise A+中提到,可以使用 setTimeout 或 setImmediate 这样的“宏任务”机制实现,也可以使用 MutationObserver 或 process.nextTick 这样的“微任务”机制实现。Promise 的实现是基于 V8 引擎的,promise.then()是微任务,故下面的模拟代码使用queueMicrotask()创建微任务。
看个例子:
// 立即执行函数
function executor(resolve, reject) {
// 成功的回调
resolve("success");
// 失败的回调
reject("error");
}
const promise = new Promise(executor);
promise.then(
// onFulfilled函数
(value) => console.log(1, value), // 1 success
// onRejected函数
(reason) => console.log(2, reason)
);
// 输出 1 success
复制代码
根据例子的结果和 Promise A+的规则分析得出以下结果:
- Promise 有三种状态:
pending(进行中)、fulfilled(已成功)、rejected(已失败)
;- Promise 的状态改变只有两种可能:
pending -> fulfilled
或pending -> rejected
;- Promise 有一个立即执行函数 executor 函数,有两个回调为 resolve、reject 函数,用来更改状态;
- then 方法用来访问最终的成功的值或失败的原因, 如果状态是成功调用成功的回调,如果是失败,调用失败的回调;
1. 创建 MyPromise 类,resolve 和 reject 方法
class MyPromise {
constructor(executor) {
// 立即执行函数
executor();
}
/**
这里使用箭头函数的原因:
1、函数中的this指向的是全局window,严格模式指向undefined
2、new运算符会创建一个空的JavaScript对象作为this的上下文
3、箭头函数不会创建执行上下文,this是指向MyPromise的实例
*/
// 更改成功后的状态
resolve = () => {};
// 更改失败后的状态
reject = () => {};
}
复制代码
2. MyPromise 状态管理
const PEDDING = "pedding";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
class MyPromise {
// 初始化存储状态,默认值为pending
status = PEDDING;
// 初始化resolve参数value
value = null;
// 初始化reject参数reason
reason = null;
// 构造函数
constructor(executor) {
// 立即执行函数
executor(this.resolve, this.reject);
}
/**
这里使用箭头函数的原因:
1、函数中的this指向的是全局window,严格模式指向undefined
2、new运算符会创建一个空的JavaScript对象作为this的上下文
3、箭头函数不会创建执行上下文,this是指向MyPromise的实例
*/
// 更改成功后的状态
resolve = (value) => {
// 如果状态为pending,则设置为fulfilled状态,并缓存值
if (this.status === PEDDING) {
// 改变状态为fulfilled
this.status = FULFILLED;
// 缓存成功之后的值
this.value = value;
}
};
// 更改失败后的状态
reject = (reason) => {
// 如果状态为pending,则设置为rejected状态,并缓存失败原因
if (this.status === PEDDING) {
// 改变状态为rejected
this.status = REJECTED;
// 缓存失败原因
this.reason = reason;
}
};
}
复制代码
3. then 方法实现
// ...
class MyPromise {
//...
// then方法
then(onFulfilled, onRejected) {
// 如果是成功状态,执行成功回调,如果是失败状态,执行失败回调
if (this.status === FULFILLED) {
// 调用成功回调,把成功的值返回
onFulfilled(this.value);
} else if (this.status === REJECTED) {
// 调用失败回调,把失败原因返回
onRejected(this.reason);
}
}
}
复制代码
测试一下:
const promise = new MyPromise((resolve, reject) => {
resolve("success");
reject("error");
});
promise.then(
(value) => console.log("resolve", value),
(reason) => console.log("reject", reason)
);
// 输出: resolve success
复制代码
结果正确,继续。
4. Promise 中的异步逻辑和 then 方法多次调用
上面的实现没有异步逻辑处理,如果加上异步逻辑会出现什么情况呢?
const promise = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve("success");
reject("error");
});
});
promise.then(
(value) => console.log("resolve", value),
(reason) => console.log("reject", reason)
);
// 输出: 什么也没有!
复制代码
为什么会出现这样的情况?前面提到过 eventLoop,js 在编译的时候会创建执行上下文(全局、函数和 eval 执行上下文),执行上下文存放在执行上下文栈中(执行栈),es6 的 let 和 const 的块级作用域存放在词法环境中。而异步任务存放在任务队列中,这里使用了 setTimeout 是宏任务,需要等待调用栈中的任务执行完成,才执行宏任务中的代码。
分析上面代码:
主线程立即执行代码,setTimeout 是宏任务,存放到宏任务队列,then 方法在执行栈中立即执行,这时 status 的状态为 pending,结束。再从宏任务中拿出 resolve 执行,后面就没有代码了,故上面都不会返回。
那么如何解决呢?可以借助队列(先进先出)的数据结构来缓存数据,当执行 then 方法时,状态还是 pending 状态,则缓存待执行的回调,当调用 resolve 或 reject 方法时,判断缓存中是否有回调缓存,如果有就执行。如下:
MyPromise 中新增两个数组
// 初始化成功回调缓存
onFulfilledCallback = [];
//初始化失败回调缓存
onRejectedCallback = [];
复制代码
then 方法回调函数存入数组
// then方法
then(onFulfilled, onRejected) {
// 如果是成功状态,执行成功回调,如果是失败状态,执行失败回调
if (this.status === FULFILLED) {
// 调用成功回调,把成功的值返回
onFulfilled(this.value);
} else if (this.status === REJECTED) {
// 调用失败回调,把失败原因返回
onRejected(this.reason);
} else {
// 缓存当前待执行的成功回调,入成功队列
this.onFulfilledCallback.push(onFulfilled);
// 缓存当前待执行失败的回调,入失败队列
this.onRejectedCallback.push(onRejected);
}
};
复制代码
resolve 和 reject 中执行缓存的回调
// 更改成功后的状态
resolve = (value) => {
// 如果状态为pending,则设置为fulfilled状态,并缓存值
if (this.status === PEDDING) {
// 改变状态为fulfilled
this.status = FULFILLED;
// 缓存成功之后的值
this.value = value;
// 若缓存回调中存在值,则执行
while (this.onFulfilledCallback.length) {
// 出队列执行回调
this.onFulfilledCallback.shift()(value);
}
}
};
// 更改失败后的状态
reject = (reason) => {
// 如果状态为pending,则设置为rejected状态,并缓存失败原因
if (this.status === PEDDING) {
// 改变状态为rejected
this.status = REJECTED;
// 缓存失败原因
this.reason = reason;
// 若缓存回调中存在值,则执行
while (this.onRejectedCallback.length) {
// 出队列执行回调
this.onRejectedCallback.shift()(reason);
}
}
};
复制代码
写个例子执行下上面的程序,如下:
const promise = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve("success");
reject("error");
});
});
promise.then(
(value) => console.log(1, "resolve"),
(reason) => console.log(2, "reject")
);
promise.then(
(value) => console.log(3, "resolve"),
(reason) => console.log(4, "reject")
);
// 输出:
// 1 resolve
// 2 resolve
复制代码
是预期结果,到这里已经解决了异步调用问题,then 方法多次调用问题。
5. then 链式调用
then 方法链式调用需要返回一个 Promise,上一个 then 中返回一个 Promise 对象,下一个 then 接受上一个 resolve 的参数。
举个例子:
const promise = new MyPromise((resolve, reject) => {
resolve();
});
promise
.then(() => {
console.log(1, "resolve");
return new MyPromise((resolve) => {
resolve("success");
});
})
.then((value) => console.log(2, value));
// 输出: 1 "resolve"
// test copy.html:99 Uncaught TypeError: Cannot read property 'then' of undefined
复制代码
报错是因为,then 方法中并没有返回值,在 Promise A+中要求 Promise 的 then 方法返回一个 Promise 对象,故改造下代码。如下:
// ...
class MyPromise {
//...
// then方法
then(onFulfilled, onRejected) {
const promise2 = new Promise((resolve, reject) => {
// 如果是成功状态,执行成功回调,如果是失败状态,执行失败回调
if (this.status === FULFILLED) {
// 调用成功回调,把成功的值返回
const x = onFulfilled(this.value);
this.resolvePromise(x, resolve, reject);
} else if (this.status === REJECTED) {
// 调用失败回调,把失败原因返回
const x = onRejected(this.reason);
this.resolvePromise(x, resolve, reject);
} else {
// 缓存当前待执行的成功回调,入成功队列
this.onFulfilledCallback.push(onFulfilled);
// 缓存当前待执行失败的回调,入失败队列
this.onRejectedCallback.push(onRejected);
}
});
return promise2;
};
resolvePromise = (x, resolve, reject) => {
if (x instanceof MyPromise) {
x.then(resolve, reject);
} else {
resolve(x);
}
};
}
复制代码
再执行下:
const promise = new MyPromise((resolve, reject) => {
resolve();
});
promise
.then(() => {
console.log(1, "resolve");
return new MyPromise((resolve) => {
resolve("success");
});
})
.then((value) => console.log(2, value));
// 输出
// 1 "resolve"
// 2 "success"
复制代码
链式调用已经实现,如果 then 方法返回的是自己,会出现什么情况呢,先看下 Promise 本身自己调用自己是什么情况?
抛出错误,如下:
Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>
复制代码
看下上面封装的代码执行自己返回自己的情况,如下:
const promise = new MyPromise((resolve, reject) => {
resolve();
});
const p1 = promise.then(() => {
return p1;
});
// 输出 test copy.html:108 Uncaught (in promise) ReferenceError: Cannot access 'p1' before initialization
复制代码
那么只需要判断如果返回的是 then 本身,则抛出错误即可,代码改造如下:
// ...
class MyPromise {
//...
// then方法
then(onFulfilled, onRejected) {
const promise2 = new Promise((resolve, reject) => {
// 如果是成功状态,执行成功回调,如果是失败状态,执行失败回调
if (this.status === FULFILLED) {
// 调用成功回调,把成功的值返回
const x = onFulfilled(this.value);
this.resolvePromise(promise2, x, resolve, reject);
} else if (this.status === REJECTED) {
// 调用失败回调,把失败原因返回
const x = onRejected(this.reason);
this.resolvePromise(promise2, x, resolve, reject);
} else {
// 缓存当前待执行的成功回调,入成功队列
this.onFulfilledCallback.push(onFulfilled);
// 缓存当前待执行失败的回调,入失败队列
this.onRejectedCallback.push(onRejected);
}
});
return promise2;
};
resolvePromise = (promise2, x, resolve, reject) => {
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'));
}
if (x instanceof MyPromise) {
x.then(resolve, reject);
} else {
resolve(x);
}
};
}
复制代码
在执行看看
Uncaught (in promise) ReferenceError: Cannot access 'p1' before initialization
复制代码
竟然报错了,从结果上看,初始化前无法访问p1
,即必须等 promise2 执行完成。根据 eventLoop 的知识,这里需要创建一个异步任务去等待 promise2 执行,前面已经使用过的queueMircotask
方法。
then(onFulfilled, onRejected) {
const promise2 = new Promise((resolve, reject) => {
// 如果是成功状态,执行成功回调,如果是失败状态,执行失败回调
if (this.status === FULFILLED) {
// === 新增部分 ===
queueMicrotask(() => {
// 调用成功回调,把成功的值返回
const x = onFulfilled(this.value);
this.resolvePromise(promise2, x, resolve, reject);
});
} else if (this.status === REJECTED) {
// 调用失败回调,把失败原因返回
const x = onRejected(this.reason);
this.resolvePromise(promise2, x, resolve, reject);
} else {
// 缓存当前待执行的成功回调,入成功队列
this.onFulfilledCallback.push(onFulfilled);
// 缓存当前待执行失败的回调,入失败队列
this.onRejectedCallback.push(onRejected);
}
});
return promise2;
};
复制代码
运行结果,及是我们期望的类型错误抛出,如下:
Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>
复制代码
6. 捕捉错误
在代码的的执行器阶段,then 方法处理成功失败的阶段,都需要捕捉错误。
捕捉执行器错误
constructor(executor) {
try {
// 立即执行函数
executor(this.resolve, this.reject);
} catch (err) {
this.reject(err);
}
}
复制代码
举个例子执行看下结果
const promise = new MyPromise((resolve, reject) => {
throw Error("ececutor error!");
resolve();
});
const p1 = promise.then(
(value) => console.log("resolve", value),
(reason) => console.log("reject", reason.message)
);
// 输出 reject ececutor error!
复制代码
then 方法中错误捕捉和代码完善
then = (onFulfilled, onRejected) => {
const promise2 = new Promise((resolve, reject) => {
const fulfilledMircotask = () =>
queueMicrotask(() => {
try {
// 调用成功回调,把成功的值返回
const x = onFulfilled(this.value);
this.resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
});
const rejectedMircotask = () =>
queueMicrotask(() => {
try {
// 调用失败回调,把失败原因返回
const x = onRejected(this.reason);
this.resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
});
// 如果是成功状态,执行成功回调,如果是失败状态,执行失败回调
if (this.status === FULFILLED) {
fulfilledMircotask();
} else if (this.status === REJECTED) {
rejectedMircotask();
} else {
// 缓存当前待执行的成功回调,入成功队列
this.onFulfilledCallback.push(fulfilledMircotask);
// 缓存当前待执行失败的回调,入失败队列
this.onRejectedCallback.push(rejectedMircotask);
}
});
return promise2;
};
复制代码
举个例子执行看下结果
const promise = new MyPromise((resolve, reject) => {
resolve("success");
});
const p1 = promise
.then((value) => {
console.log("resolve", value);
throw Error("then throw error!");
})
.then(
() => console.log("resolve"),
(reason) => {
console.log("reject", reason.message);
}
);
// 输出
// resolve success
// reject then throw error!
复制代码
成功打印出了 then 中的错误信息。
7. then 中的参数作为可选参数
在执行 then 方法是,传入的 onFulfilled、onRejected 函数默认传入的,也可以是可选的。
then(onFulfilled, onRejected) {
// 如果传入的不是函数,则转化为函数
const onFulfilledReal =
Object.prototype.toString.call(onFulfilled) === "[object Function]"
? onFulfilled
: (value) => value;
const onRejectedReal =
Object.prototype.toString.call(onRejected) === "[object Function]"
? onRejected
: (reason) => {
throw reason;
};
const promise2 = new Promise((resolve, reject) => {
// ...
}
return promise2;
}
复制代码
举个例子测试一下,如下:
const promise = new MyPromise((resolve, reject) => {
resolve("success");
});
const p1 = promise
.then()
.then()
.then(
(value) => console.log("resolve", value),
(reason) => console.log("reject", reason)
);
// 输出 resolve success
复制代码
预期结果。
8. 完善 promiseResolve 方法
根据 Promise A+规范中 2.3.3 之后的规范,晚上 promiseResolve 的代码
- x 是一个函数或对象,then = x.then;
- 若 x.then 执行失败,则抛出异常,return reject(error);
- 若 then 是一个函数,用 x 作为 this 调用它,有两个参数:resolvePromise 和 then.call(x, (y) => resolvePromise(promise, y, resolve, reject), (r) => reject(r));
- 其他则根据 Promise A+抛出错误即可
代码如下:
resolvePromise = (promise, x, resolve, reject) => {
// 如果 promise 和 x 指向同一对象,即自己调用自己,则抛出类型错误
if (promise === x) {
return reject(
new TypeError("The promise and the return value are the same")
);
}
if (typeof x === "object" || typeof x === "function") {
let then;
try {
// 把 x.then 赋值给 then
then = x.then;
} catch (error) {
// 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
return reject(error);
}
// 如果 then 是函数
if (typeof then === "function") {
let called = false;
// 将 x 作为函数的作用域 this 调用
// 传递两个回调函数作为参数,resolvePromise 和 rejectPromise
try {
then.call(
x,
// 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)
(y) => {
// 如果 resolvePromise 和 rejectPromise 均被调用,
// 或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
// 实现这条需要前面加一个变量called
if (called) return;
called = true;
resolvePromise(promise, y, resolve, reject);
},
// 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
(r) => {
if (called) return;
called = true;
reject(r);
}
);
} catch (error) {
// 如果调用 then 方法抛出了异常 e:
// 如果 resolvePromise 或 rejectPromise 已经被调用,则忽略之
if (called) return;
// 否则以 e 为据因拒绝 promise
reject(error);
}
} else {
// 如果 then 不是函数,以 x 为参数执行 promise
resolve(x);
}
} else {
// 如果 x 不为对象或者函数,以 x 为参数执行 promise
resolve(x);
}
};
复制代码
9. resolve 和 reject 静态方法实现
// resolve 静态方法
static resolve(value) {
// 如果是当前对象的实例,原样返回
if (value instanceof MyPromise) {
return value;
}
// 重新new一个Promise对象
return new MyPromise((resolve) => {
resolve(value);
});
}
// reject 静态方法
static reject(reason) {
// 如果是当前对象的实例,原样返回
if (reason instanceof MyPromise) {
return reason;
}
// 重新new一个Promise对象
return new MyPromise((_, reject) => {
reject(reason);
});
}
复制代码
测试一下:
MyPromise.resolve("success").then((value) => {
console.log("resolve", value); // resolve success
});
MyPromise.reject("error").then(
(value) => {
console.log("resolve", value);
},
(reason) => console.log("reject", reason) // reject error
);
// 输出
// resolve success
// reject error
复制代码
10. catch 方法实现
Promise.prototype.catch()方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。
代码实现:
// catch 实现
catch(onRejected) {
this.then(undefined, onRejected);
}
复制代码
测试一下:
const promise = new MyPromise((resolve, reject) => {
reject("error");
});
const p1 = promise
.then((value) => console.log("resolve", value))
.catch((err) => {
console.log("catch", err);
});
// 输出 catch error
复制代码
11. finally 代码实现
finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。
代码实现:
// finally 无论成功或失败都会执行
finally(fn) {
return this.then(
// 成功状态执行
(value) => {
return MyPromise.resolve(fn()).then(() => {
return value;
});
},
// 失败状态执行
(error) => {
return MyPromise.resolve(fn()).then(() => {
throw error;
});
}
);
}
复制代码
12. all 方法实现
all 方法返回一个 promise 对象,要求传入的数组中所有的参数状态都变为 fulfilled,则返回 fulfilled 状态,如果有一个 rejected 状态,则返回 rejected 状态。
分析如下:
- 返回一个 Promise:all = (promiseList) => new MyPromise(// … );
- 传入数组中所有的状态都变为 fulfilled 状态,则返回 fulfilled 状态,则需要遍历传入的数组,通过 MyPromise.resolve(promiseList[i]).then((value) => {}, (reason) => {})来判断。
- 当执行 Promise.resolve()成功的个数等于传入数组的长度,则返回结果。
代码实现如下:
static all(promiseList) {
return new MyPromise((resolve, reject) => {
// 获取传入数组的长度
const len = promiseList.length;
// 初始化结果数组
const result = [];
// 初始化计数器
let count = 0;
// 遍历传入的方法
promiseList.forEach((promise) => {
MyPromise.resolve(promise).then(
(value) => {
count++;
result[index] = value;
if (count === len) {
return resolve(result);
}
},
(reason) => reject(reason)
);
});
});
}
复制代码
13. race 方法实现
race 方法,返回一个 promise 对象,要求传入的参数不管成功还是失败,第一个改变的状态就是返回的状态。
分析:
- 返回一个 promise, race = (promiseList) => new MyPromise(() => {});
- 遍历传入的 promiseList,获取传入的每一个 promiseItem, 这里使用 for 遍历,因为只要有返回就中断循环;
- 无论成功或失败,第一个状态改变,则返回,MyPromise.resolve(promiseList[i]).then((value) => {
return reslove(value)
}, (reason) => {
return reject(reason);
})
代码实现:
// race 静态方法,谁先完成,就返回谁
static race(promiseList) {
return new MyPromise((resolve, reject) => {
// 获取传入数组的长度
const len = promiseList.length;
if (len === 0) {
resolve();
} else {
for (let i = 0; i < len; i++) {
MyPromise.resolve(promiseList[i]).then(
(value) => {
return resolve(value);
},
(reason) => {
return reject(reason);
}
);
}
}
});
}
复制代码
14. allSettled 方法实现
Promise.allSettled()方法返回一个在所有给定的 promise 都已经 fulfilled 或 rejected 后的 promise,并带有一个对象数组,每个对象表示对应的 promise 结果。
一旦所指定的 promises 集合中每一个 promise 已经完成,无论是成功的达成或被拒绝,未决议的 Promise 将被异步完成。那时,所返回的 promise 的处理器将传入一个数组作为输入,该数组包含原始 promises 集中每个 promise 的结果。
对于每个结果对象,都有一个 status 字符串。如果它的值为 fulfilled,则结果对象上存在一个 value 。如果值为 rejected,则存在一个 reason 。value(或 reason )反映了每个 promise 决议(或拒绝)的值。
分析:
- 返回一个 promise 对象, allSettled = (promiseList) => new MyPromise(() => {});
- 遍历 promiseList,并记录无论成功或失败的个数之和;
- 当所有的个数之和等于数组的长度,则返回。
代码实现:
// allSettled静态实现
static allSettled(promiseList) {
return new MyPromise((reslove, reject) => {
// 缓存数组长度
const len = promiseList.length;
// 初始化结果数组
const result = [];
// 初始化计数器
let count = 0;
// 如果传入的是空数组,则返回空数组
if (len === 0) {
resolve(result);
} else {
// 遍历传入数组
for (let i = 0; i < len; i++) {
MyPromise.resolve(promiseList[i]).then(
(value) => {
// 成功则计数器加1
count++;
// 存储成功状态,设置状态为fulfilled
result[i] = {
status: "fulfilled",
value,
};
// 如果传入的数组长度和技术器相等,则通过resolve返回结果
if (len === count) {
return resolve(result);
}
},
(reason) => {
// 失败,计数器加1
count++;
// 存储失败状态的结果,设置返回状态为rejected
result[i] = {
status: "rejected",
reason,
};
// 如果传入的数组长度和技术器相等,则通过resolve返回结果
if (len === count) {
return resolve(result);
}
}
);
}
}
});
}
复制代码
到此,一个自定义版的 Promise 已经完成,接下来使用promises-aplus-tests
安装测试一下
四、Promise A+ 测试
手写实现的 Promise 需要遵守 Promise A+规范,需要promises-aplus-tests
来测试是否完全遵守 Promise A+规范。
1. 安装 promises-aplus-tests
yarn add promises-aplus-tests -D 或 npm install promises-aplus-tests -D
复制代码
2. 加入测试代码 deferred
MyPromise.deferred = function () {
var result = {};
result.promise = new MyPromise(function (resolve, reject) {
result.resolve = resolve;
result.reject = reject;
});
return result;
};
复制代码
3. 配置 package.json 文件
{
"name": "my-promise",
"version": "1.0.0",
"description": "",
"main": "my-promise.js", // 入口文件
"scripts": {
"test": "promises-aplus-tests my-promise" // 配置命令
},
"author": "",
"license": "ISC",
"devDependencies": {
"promises-aplus-tests": "^2.1.2"
}
}
复制代码
4. 启动测试
yarn test & npm run test
复制代码
效果:
总结
上文从 Promise 的基本用法,Promise A+、手写实现 Promise、测试手写代码等角度分析了 Promise 底层实现方式。Promise 是异步方案的一种解决方法,但是如果链路较长,也会存在代码难以维护的情况,针对这样的情况,es6 后提出了 Generator 函数、Async await 的异步解决方案。若需了解,请关注后续文章。
长文不易,点赞支持一下就是对笔者最好的鼓励。
手写 Promise 已放到仓库MyPromise。