时至今日,我想大家对 Promise 肯定是不陌生了,它解决的是异步编码风格的问题。
而手撸一个 Promise 往往成为面试中的常客,笔者刚毕业时也看过一些文章尝试过手写一个 MyPromise,发现写不出来,看着别人写的也是一脸懵逼。随着工作经验的增加,慢慢地发现手写个 MyPromise 其实也没那么难。
本文将会采用 TDD(测试驱动开发) 方式,通过从简到繁介绍原生 Promise 语法和书写测试用例。然后根据语法和测试用例来手写 MyPromise。这种循序渐进的方式,不需要你一开始就对 Promise 语法了如指掌,反而通过本文,不仅可以熟悉掌握 Promise 的所有语法,还可以自己写一个 MyPromise,从使用到底层原理彻底搞定 Promise。
希望本文对你有益,如果错误,欢迎指正。
前言
在开始之前,我想你应该具备两个知识点:
- 对 Promise 的概念和使用有个基本的了解,推荐先看看阮一峰老师的文章:ES6 入门教程。不用全部看完,有个基本了解就足够了。
- 大概了解下浏览器的事件循环机制,Promise 回调函数的执行时机是微任务事件。
接下来,我们正式开始。
基本用法
我们先看下 Promise 的基本用法。
new Promise((resolve, reject) => {
console.log('参数函数执行');
resolve('resolve');
}).then(res => {
console.log('then', res);
})
console.log('end');
运行结果如下:
Promise 对象是一个构造函数,用来生成 Promise 实例。构造函数接受一个函数作为参数,该函数有两个参数分别是 resolve 和 reject,它们两个都是函数,由 Promise 内部提供。
基本语法有:
- Promise 有三种状态:
pending(进行中)、fulfilled(已成功)和rejected(已失败),一旦状态改变,就会确定下来,不会再变更了,任何时候都可以得到这个结果。 - 构造函数接收的函数参数会立即被调用执行。
- 调用
resolve函数,状态则会从pending转为fulfilled,并调用onFulfilled回调函数(then方法第一个参数),入参为异步执行结果。 - 调用
reject函数,状态则会从pending转为rejected,并调用onRejected回调函数(then方法第二个参数或者catch方法第一个参数),入参为错误信息。 onFulfilled和onRejected回调函数的执行时机是异步的,并且是微任务事件。
基于上面的基本语法,我们就可以很好理解上述例子的输出结果。参数函数会立即执行,并将 resolve 和 reject 函数作为入参,所以第一行打印了 参数函数执行,然后调用 resolve 函数,Promise 状态转为 fulfilled,并调用 onFulfilled 回调函数,由于是微任务事件,需要等同步代码执行完成后再执行。所以同步语句 console.log('end') 先执行,onFulfilled 回调再执行,并打印出 then resolve。
接下来,我们就开始写我们的 MyPromise。基本的结构是这样的:
- 首先是基本的校验。
- 然后需要提供 resolve 和 reject 方法。
- 接下来,需要同步立即执行参数函数,并将 resolve 和 reject 方法作为参数传入。
"use strict"
// 判断是否为函数的工具方法
const isFunction = fun => typeof fun === 'function';
// 状态常量定义
const PENDING = 'pending';
const FULFILLED ='fulfilled';
const REJECTED = 'rejected';
function MyPromise(handle) {
// 调用方式校验,只能 new,不能直接调用
if (!(this instanceof MyPromise)) {
throw new Error('MyPromise is a constructor, and cannot be called directly');
}
// 参数校验,必须为函数
if (!isFunction(handle)) {
throw new Error('MyPromise must accept a function as parameter');
}
// 内部变量定义
let _result = undefined; // 异步执行结果
let _status = PENDING; // 当前 MyPromise 状态 pending、fulfilled、rejected
// 定义 resolve 函数
const resolve = () => {
// TODO
}
// 定义 reject 函数
const reject = () => {
// TODO
}
// 参数函数立即执行,并将 resolve 和 reject 函数作为参数传入
// 使用 call 确保 handle 内的 this 指向不变
handle.call(null, resolve, reject);
}
测试用例 1:参数函数同步执行
上面的基本结构就可以通过这个测试,我们来验证下:
// 测试用例 1
new Promise(() => console.log('Promise 参数函数执行'));
console.log('Promise end');
new MyPromise(() => console.log('MyPromise 参数函数执行'));
console.log('MyPromise end');
测试通过。
测试用例 2:调用 resolve ,状态为 fulfilled,调用 onFulfilled
onFulfilled 回调函数,是通过 then 方法来注册的(第一个参数),所以接下来,我们需要先实现 then 方法,核心功能点有:
- 可以多次调用
then方法注册回调。状态变更后,按照注册顺序依次执行。 - 第一个参数为
onFulfilled回调函数,第二个参数为onReject回调函数,都是可选的。(如果不是函数,则会被忽略)。
先写测试用例:
// 测试用例 2
const p1 = new Promise((resolve) => resolve('异步执行结果'));
p1.then(r => console.log('Promise then 1', r));
p1.then(r => console.log('Promise then 2', r));
const p2 = new MyPromise((resolve) => resolve('异步执行结果'));
p2.then(r => console.log('MyPromise then 1', r));
p2.then(r => console.log('MyPromise then 2', r));
Promise 执行结果如下:
接下来,我们继续完善功能。
- 增加一个变量
_callbacks来记录回调函数。 - 新增实例对象的
then方法,注册回调函数。 - 完善
resolve函数功能,状态为pending时, 转换为fulfilled,并依次调用onFulfilled回调函数(异步并以微任务形式调用),状态不是pending时,不能执行,直接返回。
// 内部变量定义
const _callbacks = []; // 子项为对象,记录 onFulfilled, onRejected。
// 执行 resolve 逻辑
const handleResolve = (cb) => {
const { onFulfilled } = cb;
isFunction(onFulfilled) && onFulfilled.call(null, _result);
}
// 根据当前状态执行 resolve 或者 reject 回调
const handleCB = (cb) => {
if (_status === FULFILLED) {
handleResolve(cb);
}
}
// 定义实例对象上的 then 方法
this.then = (onFulfilled, onRejected) => {
const cb = {
onFulfilled,
onRejected
};
// 注册对应的回调方法
_callbacks.push(cb);
}
// 定义 resolve 函数
const resolve = (result) => {
const run = () => {
// 如果状态已经改变了,不会再执行了
if (_status !== PENDING) {
return;
}
// 状态变更为 fulfilled
_status = FULFILLED;
// 记录执行结果
_result = result;
// 依次调用 onFulfilled 回调
_callbacks.forEach(cb => handleCB(cb));
}
// 异步以微任务形式执行(借助原生 Promise 来实现微任务)
Promise.resolve().then(run);
}
MyPromise 执行测试用例:
运行结果和 Promise,测试通过。
测试用例 3:调用 reject ,状态为 rejected,调用 onRejected
onRejected 回调函数有两种注册方法:一种是通过 then 方法第二个参数来注册;还有一种是实例对象 catch 方法第一个参数。
先写测试用例:
// 测试用例 3
const p1 = new Promise((resolve, reject) => reject('有错误'));
p1.then(null, r => console.log('Promise reject 1', r));
p1.then(null, r => console.log('Promise reject 2', r));
p1.catch(r => console.log('Promise catch 1', r));
p1.catch(r => console.log('Promise catch 2', r));
const p2 = new MyPromise((resolve, reject) => reject('有错误'));
p2.then(null, r => console.log('MyPromise reject 1', r));
p2.then(null, r => console.log('MyPromise reject 2', r));
p2.catch(r => console.log('MyPromise catch 1', r));
p2.catch(r => console.log('MyPromise catch 2', r));
Promise 执行结果如下:
接下来,继续完善功能:
- 添加
catch方法,catch方法可以看着是then方法的一种简写,可以直接使用then方法来实现。 - 完善 reject 函数功能,状态为
pending时, 转换为rejected,并依次调用onRejected回调函数(异步并以微任务形式调用),状态不是pending时,不能执行,直接返回。 handleCB方法添加rejected判断逻辑。
// 执行 reject 逻辑
const handleReject = (cb) => {
const { onRejected } = cb;
isFunction(onRejected) && onRejected.call(null, _result);
}
// 根据当前状态执行 resolve 或者 reject 回调
const handleCB = (cb) => {
if (_status === FULFILLED) {
handleResolve(cb);
}
if (_status === REJECTED) {
handleReject(cb);
}
}
// 定义实例对象上的 catch 方法
this.catch = (onRejected) => {
return this.then(null, onRejected);
}
// 定义 reject 函数
const reject = (error) => {
const run = () => {
// 如果状态已经改变了,不会再执行了
if (_status !== PENDING) {
return;
}
// 状态变更为 rejected
_status = REJECTED;
// 记录执行结果
_result = error;
// 依次调用 onRejected 回调
_callbacks.forEach(cb => handleCB(cb));
}
// 异步以微任务形式执行(借助原生 Promise 来实现微任务)
Promise.resolve().then(run);
}
MyPromise 执行测试用例:
运行结果和 Promise,测试通过。
测试用例 4:异步注册回调函数也能执行
上面的测试用例中,我们是同步使用 then 或者 catch 来注册回调函数的,能保证在 onResolved 在以微任务调用时,回调队列是不为空的。
Promise 是支持异步注册回调的,也能保证回调函数以微任务时机被调用。
我们先来看测试用例:
// 测试用例 4
const p1 = new Promise((resolve) => resolve('异步执行结果'));
// setTimeout 宏任务,在 resolve 之后执行
setTimeout(() => {
p1.then(r => console.log('Promise then', r));
console.log('Promise end');
}, 0);
const p2 = new MyPromise((resolve) => resolve('异步执行结果'));
setTimeout(() => {
p2.then(r => console.log('MyPromise then', r));
console.log('MyPromise end');
}, 0);
Promise 执行结果如下:
根据执行结果,我们可以得到,Promise 状态确定后注册的回调函数会直接执行,由于先打印 end,所以是异步以微任务事件调用。
我们继续完善 MyPromise。在 then 方法中,我们需要根据当前的状态,执行不同的操作。
// 定义实例对象上的 then 方法
this.then = (onFulfilled, onRejected) => {
// 注册对应的回调方法
const cb = {
onFulfilled,
onRejected
};
// 状态是 pending 注册对应的回调方法。
if (_status === PENDING) {
_callbacks.push(cb);
return;
}
// 状态已确定,异步微任务执行回调
Promise.resolve().then(() => handleCB(cb));
}
MyPromise 执行测试用例:
运行结果和 Promise,测试通过。
测试用例 5:链式调用
我们知道,Promise 是支持链式调用的,也就是 then 返回的是一个新的 Promise 实例对象,而这个新的 Promise 对象的状态是由当前 Promise 对象状态决定的。
我们先来看测试用例:
new Promise((resolve) => resolve('异步执行结果')).then(r => {
console.log('Promise then 1 =>', r);
return 'then 1 result'
}).then(r => console.log('Promise then 2 =>', r))
new MyPromise((resolve) => resolve('异步执行结果')).then(r => {
console.log('MyPromise then 1 =>', r);
return 'then 1 result'
}).then(r => console.log('MyPromise then 2 =>', r))
Promise 执行结果如下:
如果当前 Promise 状态转为 fulfilled,则会调用返回值 Promise 的 resolve,入参是异步执行结果,也是当前 onFulfilled 的返回值。
如果当前 Promise 状态转为 rejected,则会调用返回值 Promise 的 reject,入参是异步执行结果,也就是当前 onRejected 的返回值。
接下来,我们继续完善 MyPromise 功能。
首先,then 方法需要返回一个新的 MyPromise 对象,并且需要记录下 nextResolve 和 nextReject,在当前 MyPromise 状态改变时,需要调用对应的 nextResolve 或者 nextReject。
// 定义实例对象上的 then 方法
this.then = (onFulfilled, onRejected) => {
// 返回新的 MyPromise 实例对象
return new MyPromise((nextResolve, nextReject) => {
const cb = {
onFulfilled,
onRejected,
nextResolve,
nextReject
};
// 状态是 pending 注册对应的回调方法。
if (_status === PENDING) {
_callbacks.push(cb);
return;
}
// 状态已确定,异步微任务执行回调
Promise.resolve().then(() => handleCB(cb));
});
}
接下来,我们需要处理下 handleResolve 和 handleReject。
// 执行 resolve 逻辑
const handleResolve = (cb) => {
const { onFulfilled, nextResolve } = cb;
// 返回 MyPromise 异步结果为 onFulfilled 返回值,或者当前 MyPromise 结果。
let nextVal = _result;
if (isFunction(onFulfilled)) {
nextVal = onFulfilled.call(null, _result);
}
// 触发下一个 MyPromise resolve
nextResolve(nextVal);
}
// 执行 reject 逻辑
const handleReject = (cb) => {
const { onRejected, nextReject } = cb;
// 返回 MyPromise 异步结果为 onRejected 返回值,或者当前 MyPromise 结果。
let nextVal = _result;
if (isFunction(onRejected)) {
nextVal = onRejected.call(null, _result);
}
// 触发下一个 MyPromise reject
nextReject(nextVal);
}
MyPromise 执行测试用例:
运行结果和 Promise,测试通过。
测试用例 6:错误捕获、传递和拦截
对于 Promise,当调用 reject 或者执行代码抛出代码,Promise 能够捕获到该错误,调用 onRejected 回调,如果没有注册该回调,该错误会一直往后传递,直到被一个 onRejected 回调处理,一旦处理了就不会再传递了。
就算没有 catch 错误,Promise 内部的错误也不会影响外层环境。
我们先看测试用例:
new Promise((resolve) => resolve())
.then(r => {
throw new Error('Promise Error');
})
.then(r => console.log(r)).catch(r => console.log('Promise catch 1', r))
.catch(r => console.log('Promise catch 2', r))
new MyPromise((resolve) => resolve())
.then(r => {
throw new Error('MyPromise Error');
})
.then(r => console.log(r)).catch(r => console.log('MyPromise catch 1', r))
.catch(r => console.log('MyPromise catch 2', r))
Promise 执行结果如下:
从上面例子可以看出,当 Promise 抛出错误后,会一直向后传递,直到被处理。
我们继续完善 MyPromise。首先在 handleResolve 和 handleReject 里我们需要添加 try/catch 来捕获执行时错误。
// 执行 resolve 逻辑
const handleResolve = (cb) => {
const { onFulfilled, nextResolve, nextReject } = cb;
try {
// 返回 MyPromise 异步结果为 onFulfilled 返回值,或者当前 MyPromise 结果。
let nextVal = _result;
if (isFunction(onFulfilled)) {
nextVal = onFulfilled.call(null, _result);
}
// 触发下一个 MyPromise resolve
nextResolve.call(null, nextVal);
} catch (e) {
// 抛出错误,直接 reject
nextReject.call(null, e);
}
}
另外,参数函数执行时,也需要添加 try/catch 来捕获执行时错误。
function MyPromise(handle) {
....
try {
// 参数函数立即执行,并将 resolve 和 reject 函数作为参数传入
handle.call(null, resolve, reject);
} catch (e) {
reject(e);
}
}
接下来就要实现错误的传递和拦截功能了。我们需要在 handleReject 中添加一些逻辑,也就是在 reject 时,如果有 onRejected 回调,甚好,直接调用处理来错误,错误不再向后传递,也就是错误被拦截了。如果没有 onRejected 回调,就需要错误向后传递,触发下一个 MyPromise reject。
// 执行 reject 逻辑
const handleReject = (cb) => {
const { onRejected, nextReject, nextResolve } = cb;
try {
// 返回 MyPromise 异步结果为 onRejected 返回值,或者当前 MyPromise 结果。
let nextVal = _result;
// 存在 onRejected 回调,错误不在传递,触发下一个 MyPromise resolve
if (isFunction(onRejected)) {
nextVal = onRejected.call(null, _result);
// 这里是触发 resolve
nextResolve.call(null, nextVal);
} else { // 没有 onRejected 回调,错误向后传递,触发下一个 MyPromise reject
// 这里是触发 reject
nextReject.call(null, nextVal);
}
} catch (e) {
// 抛出错误,直接 reject
nextReject.call(null, e);
}
}
MyPromise 执行测试用例:
运行结果和 Promise 一样,可以看到,执行时的错误能被捕获到,错误能穿过 then 往后传递,一旦被处理,就不会再传递了,测试通过。
测试用例 7:resolve 参数为 Promise 对象
resolve 的参数,也就是异步执行结果也可以是个 Promise 对象,then 返回值的 Promise 对象的状态,需要等该 Promise 对象执行完成,并由它的状态决定。
我们先看看测试用例:
new Promise((resolve, reject) => {
resolve('resolve');
}).then(res => {
return new Promise(res => res('Promise return Promise resolve'))
}).then(r => console.log('Promise return Promise then =>', r))
new Promise((resolve, reject) => {
resolve('resolve');
}).then(res => {
return new Promise((res, rej) => rej('Promise return Promise reject'))
}).then(r => console.log('Promise return Promise then', r)).catch(r => console.log('Promise return Promise catch =>', r))
Promise 执行结果如下:
后面的 Promise 需要等返回值的状态转变。最后一个打印 'Promise return promise catch',说明了返回值 Promise 的状态决定了后面 Promise 的状态。
我们来实现下这个功能,只需要在 resolve 判断下即可。
// 定义 resolve 函数
const resolve = (result) => {
const run = () => {
// 如果状态已经改变了,不会再执行了
if (_status !== PENDING) {
return;
}
// 判断 result 是否为 MyPromise
if (result instanceof MyPromise) {
// 等 result 状态转变再执行 resolve 或者 reject
result.then(resolve, reject);
return;
}
// 状态变更为 fulfilled
_status = FULFILLED;
// 记录执行结果
_result = result;
// 依次调用 onFulfilled 回调
_callbacks.forEach(cb => handleCB(cb));
}
// 异步以微任务形式执行(借助原生 Promise 来实现微任务)
Promise.resolve().then(run);
}
MyPromise 执行测试用例:
// 测试用例 7
new MyPromise((resolve, reject) => {
resolve('resolve');
}).then(res => {
return new MyPromise(res => res('MyPromise return MyPromise resolve'))
}).then(r => console.log('MyPromise return MyPromise then =>', r))
new MyPromise((resolve, reject) => {
resolve('resolve');
}).then(res => {
return new MyPromise((res, rej) => rej('MyPromise return MyPromise reject'))
}).then(r => console.log('MyPromise return MyPromise then', r)).catch(r => console.log('MyPromise return MyPromise catch =>', r))
运行结果和 Promise 一致,测试通过。
测试用例 8:finally,不仅仅是 then 的变形
finally 方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。finally 方法的回调函数不接受任何参数,这意味着没有办法知道,前面的 Promise 状态到底是 fulfilled 还是 rejected。这表明,finally 方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。
我们先看看测试用例:
new Promise(resolve => resolve('异步执行结果'))
.finally(() => console.log('Promise finally then'))
new Promise(() => {throw new Error('出错了')})
.finally(() => console.log('Promise catch then'))
Promise 执行结果:
从运行结果我们可以看出,不管是 resolve 还是 reject,finally 都会执行,而且不接受任何参数(打印 undefined),这和上面介绍的特性是一致的。
不过有一个点需要我们注意,那就是 finally 是不能拦截掉错误的(上述运行结果最终打印出了没有被捕获的错误),所以使用 then 实现 finally 时,需要继续抛出错误。
具体实现如下:
// 定义实例对象上的 finally 方法
this.finally = (onFinally) => {
return this.then(
result => Promise.resolve(onFinally()).then(() => result),
error => Promise.resolve(onFinally()).then(() => { throw error })
);
}
MyPromise 执行测试用例:
new MyPromise(resolve => resolve('异步执行结果'))
.finally(r => console.log('MyPromise finally then', r))
new MyPromise(() => { throw Error ('出错了')})
.finally(r => console.log('MyPromise finally catch', r))
执行结果与 Promise 一致,测试通过。
完整版
至此,我们就完整地实现了一个 MyPromise,相信你对 Promise 的使用也有了更深的了解,下面是完整的代码。
"use strict"
// 判断是否为函数的工具方法
const isFunction = fun => typeof fun === 'function';
// 状态常量定义
const PENDING = 'pending';
const FULFILLED ='fulfilled';
const REJECTED = 'rejected';
function MyPromise(handle) {
// 调用方式校验,只能 new,不能直接调用
if (!(this instanceof MyPromise)) {
throw new Error('MyPromise is a constructor, and cannot be called directly');
}
// 参数校验,必须为函数
if (!isFunction(handle)) {
throw new Error('MyPromise must accept a function as parameter');
}
// 内部变量定义
let _result = undefined; // 异步执行结果
let _status = PENDING; // 当前 MyPromise 状态 pending、fulfilled、rejected
const _callbacks = []; // 子项为对象,记录 onFulfilled, onRejected。
// 执行 resolve 逻辑
const handleResolve = (cb) => {
const { onFulfilled, nextResolve, nextReject } = cb;
try {
// 返回 MyPromise 异步结果为 onFulfilled 返回值,或者当前 MyPromise 结果。
let nextVal = _result;
if (isFunction(onFulfilled)) {
nextVal = onFulfilled.call(null, _result);
}
// 触发下一个 MyPromise resolve
nextResolve.call(null, nextVal);
} catch (e) {
// 抛出错误,直接 reject
nextReject.call(null, e);
}
}
const handleReject = (cb) => {
const { onRejected, nextReject, nextResolve } = cb;
try {
// 返回 MyPromise 异步结果为 onRejected 返回值,或者当前 MyPromise 结果。
let nextVal = _result;
// 存在 onRejected 回调,错误不在传递,触发下一个 MyPromise resolve
if (isFunction(onRejected)) {
nextVal = onRejected.call(null, _result);
// 这里是触发 resolve
nextResolve.call(null, nextVal);
} else { // 没有 onRejected 回调,错误向后传递,触发下一个 MyPromise reject
// 这里是触发 reject
nextReject.call(null, nextVal);
}
} catch (e) {
// 抛出错误,直接 reject
nextReject.call(null, e);
}
}
// 根据当前状态执行 resolve 或者 reject 回调
const handleCB = (cb) => {
if (_status === FULFILLED) {
handleResolve(cb);
}
if (_status === REJECTED) {
handleReject(cb);
}
}
// 定义实例对象上的 catch 方法
this.catch = (onRejected) => {
return this.then(null, onRejected);
}
// 定义实例对象上的 finally 方法
this.finally = (onFinally) =>
return this.then(
result => Promise.resolve(onFinally()).then(() => result),
error => Promise.resolve(onFinally()).then(() => { throw error })
);
}
// 定义实例对象上的 then 方法
this.then = (onFulfilled, onRejected) => {
// 返回新的 MyPromise 实例对象
return new MyPromise((nextResolve, nextReject) => {
const cb = {
onFulfilled,
onRejected,
nextResolve,
nextReject
};
// 状态是 pending 注册对应的回调方法。
if (_status === PENDING) {
_callbacks.push(cb);
return;
}
// 状态已确定,异步微任务执行回调
Promise.resolve().then(() => handleCB(cb));
});
}
const resolve = (result) => {
const run = () => {
// 如果状态已经改变了,不会再执行了
if (_status !== PENDING) {
return;
}
// 判断 result 是否为 MyPromise
if (result instanceof MyPromise) {
// 等 result 状态转变再执行 resolve 或者 reject
result.then(resolve, reject);
return;
}
// 状态变更为 fulfilled
_status = FULFILLED;
// 记录执行结果
_result = result;
// 依次调用 onFulfilled 回调
_callbacks.forEach(cb => handleCB(cb));
}
// 异步以微任务形式执行(借助原生 Promise 来实现微任务)
Promise.resolve().then(run);
}
// 定义 reject 函数
const reject = (error) => {
const run = () => {
// 如果状态已经改变了,不会再执行了
if (_status !== PENDING) {
return;
}
// 状态变更为 rejected
_status = REJECTED;
// 记录执行结果
_result = error;
// 依次调用 onRejected 回调
_callbacks.forEach(cb => handleCB(cb));
}
// 异步以微任务形式执行(借助原生 Promise 来实现微任务)
Promise.resolve().then(run);
}
try {
// 参数函数立即执行,并将 resolve 和 reject 函数作为参数传入
handle.call(null, resolve, reject);
} catch (e) {
reject(e);
}
}
Promise 静态方法
Promise 也提供了一些静态方法,接下来我们也来实现下。
Promise.all()
Promise.all() 方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.all([p1, p2, p3]);
- 只有 p1、p2、p3 状态都变成
fulfilled,p 的状态才会变成fulfilled。此时 p1、p2、p3 的返回值组成一个数组,传递给 p 的回调函数。 - 只要 p1、p2、p3 之中有一个被
rejected,p 的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给 p 的回调函数。
下面看个具体的例子:
const p1 = new Promise(resolve => resolve('promise 1 resolve'));
const p2 = new Promise(resolve => resolve('promise 2 resolve'));
const p3 = new Promise(resolve => resolve('promise 3 resolve'));
const p4 = new Promise((resolve, reject) => reject('promise 4 reject'));
Promise.all([p1, p2, p3])
.then(r => console.log('fulfilled', r))
.catch(r => console.log('reject', r))
Promise.all([p1, p2, p4])
.then(r => console.log('fulfilled', r))
.catch(r => console.log('reject', r))
接下来我们实现下 MyPromise.all。思路也很简单,为每一个 MyPromise 添加 onFulfilled 和 onRejected 回调,如果是 onRejected,那可以直接 reject 了。如果 onFulfilled,需要记录 onFulfilled 数量,全部 onFulfilled 才能 resolve。还有一点就是异步结果是数组而且位置与入参的 promises 一致,所以我们也需要记录各个 MyPromise 对象的位置。具体实现代码如下:
MyPromise.all = function(promises) {
return new MyPromise((resolve, reject) => {
const result = []; // 记录结果
let num = 0; // fulfilled 个数
const total = promises.length; // 总数
// 返回结果和位置一致,所以需要记录下位置信息 i
const _resolve = (res, i) => {
num++; // fulfilled 次数加一
result[i] = res;
// 全部 fulfilled
if (num === total) {
resolve(result);
}
};
// 有 reject,直接就reject
const _reject = (error) => {
reject(error);
};
promises.forEach((p, i) => {
p.then(res => _resolve(res, i), error => _reject(error));
})
})
}
接下来测试下:
const p1 = new MyPromise(resolve => resolve('MyPromise 1 resolve'));
const p2 = new MyPromise(resolve => resolve('MyPromise 2 resolve'));
const p3 = new MyPromise(resolve => resolve('MyPromise 3 resolve'));
const p4 = new MyPromise((resolve, reject) => reject('MyPromise 4 reject'));
MyPromise.all([p1, p2, p3])
.then(r => console.log('fulfilled', r))
.catch(r => console.log('reject', r))
MyPromise.all([p1, p2, p4])
.then(r => console.log('fulfilled', r))
.catch(r => console.log('reject', r))
运行结果和 Promise 一致,搞定。
Promise.race()
Promise.race() 方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.race([p1, p2, p3]);
上面代码中,只要 p1、p2、p3 之中有一个实例率先改变状态,p 的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给 p 的回调函数。
具体实现很简单,由于 resolve 和 reject 只会执行一次,一旦有状态改变,后续就不会再执行了,借助这个可以,可以实现如下:
MyPromise.race = function(promises) {
return new MyPromise((resolve, reject) => {
promises.forEach((p, i) => {
p.then(res => resolve(res), error => reject(error));
})
})
}
测试对比下:
// 测试用例 Promise.race
const p1 = new Promise(resolve => setTimeout(() => {
resolve('promise 1 resolve')
}, 200));
const p2 = new Promise(resolve => setTimeout(() => {
resolve('promise 2 resolve')
}, 300));
const p3 = new Promise((resolve, reject) => setTimeout(() => {
reject('promise 3 reject')
}, 100));
Promise.race([p1, p2, p3])
.then(r => console.log('fulfilled', r))
.catch(r => console.log('reject', r))
const mp1 = new MyPromise(resolve => setTimeout(() => {
resolve('MyPromise 1 resolve')
}, 200));
const mp2 = new MyPromise(resolve => setTimeout(() => {
resolve('MyPromise 2 resolve')
}, 300));
const mp3 = new MyPromise((resolve, reject) => setTimeout(() => {
reject('MyPromise 3 reject')
}, 100));
MyPromise.race([mp1, mp2, mp3])
.then(r => console.log('fulfilled', r))
.catch(r => console.log('reject', r))
由于 p3 和 mp3 率先实现状态转变,所以最终的结果就是 p3 和 mp3 的执行结果。
Promise.allSettled()
ES2020 引入了 Promise.allSettled() 方法,用来确定一组异步操作是否都结束了(不管成功或失败)。
Promise.allSettled() 方法接受一个数组作为参数,数组的每个成员都是一个 Promise 对象,并返回一个新的 Promise 对象。只有等到参数数组的所有 Promise 对象都发生状态变更(不管是 fulfilled 还是rejected ),返回的 Promise 对象才会发生状态变更。
它的返回值格式是这样的:
{ status: 'fulfilled', value: 42 } // resolve
{ status: 'rejected', reason: -1 } // reject
具体实现如下:
MyPromise.allSettled = function(promises) {
return new MyPromise((resolve, reject) => {
const result = []; // 记录结果
let num = 0; // 状态已完成个数
const total = promises.length; // 总数
// 返回结果和位置一致,所以需要记录下位置信息 i
const _handle = (res, i) => {
num++; // 已完成次数加一
result[i] = res;
// 是否已经全部完成
if (num === total) {
resolve(result);
}
};
promises.forEach((p, i) => {
p.then(
res => _handle({ status: 'fulfilled', value: res }, i),
error => _handle({ status: 'rejected', reason: error }, i)
);
})
})
}
测试对比下
// 测试用例 Promise.allSettled
const p1 = new Promise(resolve => resolve('promise 1 resolve'));
const p2 = new Promise(resolve => resolve('promise 2 resolve'));
const p3 = new Promise((resolve, reject) => reject('promise 3 reject'));
Promise.allSettled([p1, p2, p3]).then(r => console.log('promise', r))
const mp1 = new MyPromise(resolve => resolve('MyPromise 1 resolve'));
const mp2 = new MyPromise(resolve => resolve('MyPromise 2 resolve'));
const mp3 = new MyPromise((resolve, reject) => reject('MyPromise 3 reject'));
MyPromise.allSettled([mp1, mp2, mp3]).then(r => console.log('MyPromise', r))
测试结果与 Promise 一致。
Promise.any()
ES2021 引入了 Promise.any() 方法。该方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回。
只要参数实例有一个变成 fulfilled 状态,包装实例就会变成 fulfilled 状态;如果所有参数实例都变成rejected 状态,包装实例就会变成 rejected 状态。
我们简单实现下:
MyPromise.any = function(promises) {
return new MyPromise((resolve, reject) => {
let num = 0; // rejected 个数
const total = promises.length; // 总数
// 有实例 resolve,直接 resolve
const _resolve = res => {
resolve(res);
};
// 全部 reject,抛出错误
const _reject = () => {
num++; // rejected 次数加一
// 全部 rejected
if (num === total) {
reject('AggregateError: All promises were rejected')
}
};
promises.forEach((p) => {
p.then(res => _resolve(res), error => _reject());
})
})
}
测试下
// 测试用例 Promise.any
const mp1 = new MyPromise(resolve => resolve('MyPromise 1 resolve'));
const mp2 = new MyPromise((resolve, reject) => reject('MyPromise 2 reject'));
const mp3 = new MyPromise((resolve, reject) => reject('MyPromise 3 reject'));
MyPromise.any([mp1, mp2])
.then(r => console.log('MyPromise then', r))
.catch(error => console.log('MyPromise error', error))
MyPromise.any([mp2, mp3])
.then(r => console.log('MyPromise then', r))
.catch(error => console.log('MyPromise error', error))
// MyPromise then MyPromise 1 resolve
// MyPromise error AggregateError: All promises were rejected