0. 前言
面试官:「你写个 Promise 吧。」
我:「对不起,打扰了,再见!」
现在前端越来越卷,不会手写 Promise
都不好意思面试了(手动狗头.jpg)。虽然没多少人会在业务中用自己实现的 Promise
,但是,实现 Promise
的过程会让你对 Promise
更加了解,出了问题也可以更好地排查。
如果你还不熟悉 Promise,建议先看一下 MDN 文档。
在实现 Promise 之前,我建议你先看一遍 Promises/A+ 规范(中文翻译:Promise A+ 规范),本文中不会再次介绍相关的概念。推荐大家优先阅读原版英文,只需高中水平的英语知识就够了,遇到不懂的再看译文。
另外,本文将使用 ES6 中的 Class 来实现 Promise
。为了方便大家跟 Promise/A+ 规范对照着看,下文的顺序将按照规范的顺序来行文。
在正式开始之前,我们新建一个项目,名称随意,按照以下步骤进行初始:
- 打开 CMD 或 VS Code,运行
npm init
,初始化项目 - 新建
PromiseImpl.js
文件,后面所有的代码实现都写在这个文件里
完整代码地址:ashengtan/promise-aplus-implementing
1. 术语
这部分大家直接看规范就好,也没什么好解释的。注意其中对于 value
的描述,value
可以是一个 thenable
(有 then
方法的对象或函数) 或者 Promise
,这点会在后面的实现中体现出来。
2. 要求
2.1 Promise 的状态
一个 Promise
有三种状态:
pending
:初始状态fulfilled
:成功执行rejected
:拒绝执行
一个 Promise
一旦从 pending
变为 fulfilled
或 rejected
,就无法变成其他状态。当 fulfilled
时,需要给出一个不可变的值;同样,当 rejected
时,需要给出一个不可变的原因。
根据以上信息,我们定义 3 个常量,用来表示 Promise
的状态:
const STATUS_PENDING = 'pending' const STATUS_FULFILLED = 'fulfilled' const STATUS_REJECTED = 'rejected'
接着,我们先把 Promise
的基础框架先定义出来,这里我使用 ES6 的 Class 来定义:
class PromiseImpl { constructor() {} then(onFulfilled, onRejected) {} }
这里我们先回想一下 Promise
的基本用法:
const promise = new Promise((resolve, reject) => { // ...do something resolve(value) // or reject(error) }) // 多次调用 const p1 = promise.then() const p2 = promise.then() const p3 = promise.then()
好了,继续完善 PromiseImpl
,先完善一下构造方法:
class PromiseImpl { constructor() { // `Promise` 当前的状态,初始化时为 `pending` this.status = STATUS_PENDING // fulfilled 时的值 this.value = null // rejected 时的原因 this.reason = null } }
另外,我们还要定义两个方法,用于 fulfilled
和 rejected
时回调:
class PromiseImpl { constructor() { // ...其他代码 // 2.1.2 When `fulfilled`, a `promise`: // 2.1.2.1 must not transition to any other state. // 2.1.2.2 must have a value, which must not change. const _resolve = value => { // 如果 `value` 是 `Promise`(即嵌套 `Promise`), // 则需要等待该 `Promise` 执行完成 if (value instanceof PromiseImpl) { return value.then( value => _resolve(value), reason => _reject(reason) ) } if (this.status === STATUS_PENDING) { this.status = STATUS_FULFILLED this.value = value } } // 2.1.3 When `rejected`, a `promise`: // 2.1.3.1 must not transition to any other state. // 2.1.3.2 must have a reason, which must not change. const _reject = reason => { if (this.status === STATUS_PENDING) { this.status = STATUS_REJECTED this.reason = reason } } } }
注意,在 _resolve()
中,如果 value
是 Promise
的话(即嵌套 Promise
),则需要等待该 Promise
执行完成。这点很重要,因为后面的其他 API 如 Promise.resolve
、Promise.all
、Promise.allSettled
等均需要等待嵌套 Promise
执行完成才会返回结果。
最后,别忘了在 new Promise()
时,我们需要将 resolve
和 reject
传给调用者:
class PromiseImpl { constructor(executor) { // ...其他代码 try { executor(_resolve, _reject) } catch (e) { _reject(e) } } }
使用 trycatch
将 executor
包裹起来,因为这部分是调用者的代码,我们无法保证调用者的代码不会出错。
2.2 Then 方法
一个 Promise
必须提供一个 then
方法,其接受两个参数:
promise.then(onFulfilled, onRejected)
class PromiseImpl { then(onFulfilled, onRejected) {} }
2.2.1 onFulfilled
和 onRejected
从规范 2.2.1 中我们可以得知以下信息:
onFulfilled
和 onRejected
是可选参数- 如果
onFulfilled
和 onRejected
不是函数,则必须被忽略
因此,我们可以这样实现:
class PromiseImpl { then(onFulfilled, onRejected) { // 2.2.1 Both `onFulfilled` and `onRejected` are optional arguments: // 2.2.1.1 If `onFulfilled` is not a function, it must be ignored // 2.2.1.2 If `onRejected` is not a function, it must be ignored onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : () => {} onRejected = typeof onRejected === 'function' ? onRejected : () => {} } }
2.2.2 onFulfilled
特性
从规范 2.2.2 中我们可以得知以下信息:
- 如果
onFulfilled
是一个函数,则必须在 fulfilled
后调用,第一个参数为 promise
的值 - 只能调用一次
class PromiseImpl { then(onFulfilled, onRejected) { // ...其他代码 // 2.2.2 If `onFulfilled` is a function: // 2.2.2.1 it must be called after `promise` is fulfilled, // with promise's value as its first argument. // 2.2.2.2 it must not be called before `promise` is fulfilled. // 2.2.2.3 it must not be called more than once. if (this.status === STATUS_FULFILLED) { onFulfilled(this.value) } } }
2.2.3 onRejected
特性
与 onFulfilled
同理,只不过是在 rejected
时调用,第一个参数为 promise
失败的原因
class PromiseImpl { then(onFulfilled, onRejected) { // ...其他代码 // 2.2.3 If onRejected is a function: // 2.2.3.1 it must be called after promise is rejected, // with promise's reason as its first argument. // 2.2.3.2 it must not be called before promise is rejected. // 2.2.3.3 it must not be called more than once. if (this.status === STATUS_REJECTED) { onRejected(this.reason) } } }
2.2.4 异步执行
在日常开发中,我们经常使用 Promise
来做一些异步操作,规范 2.2.4 就是规定异步执行的问题,具体的可以结合规范里的注释阅读,重点是确保 onFulfilled
和 onRejected
要异步执行。
需要指出的是,规范里并没有规定 Promise
一定要用 micro-task
机制来实现,因此你使用 macro-task
机制来实现也是可以的。当然,现在浏览器用的是 micro-task
来实现。这里为了方便,我们使用 setTimeout
(属于 macro-task
)来实现。
因此,我们需要稍微改造下上面的代码:
class PromiseImpl { then(onFulfilled, onRejected) { // ...其他代码 // fulfilled if (this.status === STATUS_FULFILLED) { setTimeout(() => { onFulfilled(this.value) }, 0) } // rejected if (this.status === STATUS_REJECTED) { setTimeout(() => { onRejected(this.reason) }, 0) } } }
2.2.5 onFulfilled
和 onRejected
必须作为函数被调用
这个已经在上面实现过了。
2.2.6 then
可被多次调用
举个例子:
const promise = new Promise((resolve, reject) => { // ...do something resolve(value) // or reject(error) }) promise.then() promise.then() promise.catch()
因此,必须确保当 Promise
fulfilled
或 rejected
时,onFulfilled
或 onRejected
按照其注册的顺序逐一回调。还记得最开始我们定义的 resolve
和 reject
吗?这里我们需要改造下,保证所有的回调都被执行到:
const invokeArrayFns = (fns, arg) => { for (let i = 0; i < fns.length; i++) { fns[i](arg) } } class PromiseImpl { constructor(executor) { // ...其他代码 // 用于存放 `fulfilled` 时的回调,一个 `Promise` 对象可以注册多个 `fulfilled` 回调函数 this.onFulfilledCbs = [] // 用于存放 `rejected` 时的回调,一个 `Promise` 对象可以注册多个 `rejected` 回调函数 this.onRejectedCbs = [] const resolve = value => { if (this.status === STATUS_PENDING) { this.status = STATUS_FULFILLED this.value = value // 2.2.6.1 If/when `promise` is fulfilled, // all respective `onFulfilled` callbacks must execute // in the order of their originating calls to `then`. invokeArrayFns(this.onFulfilledCbs, value) } } const reject = reason => { if (this.status === STATUS_PENDING) { this.status = STATUS_REJECTED this.reason = reason // 2.2.6.2 If/when `promise` is rejected, // all respective `onRejected` callbacks must execute // in the order of their originating calls to `then`. invokeArrayFns(this.onRejectedCbs, reason) } } } }
看到这里你可能会有疑问,什么时候往 onFulfilledCbs
和 onRejectedCbs
里存放对应的回调,答案是在调用 then
时:
class PromiseImpl { then(onFulfilled, onRejected) { // ...其他代码 // pending if (this.status === STATUS_PENDING) { this.onFulfilledCbs.push(() => { setTimeout(() => { onFulfilled(this.value) }, 0) }) this.onRejectedCbs.push(() => { setTimeout(() => { onRejected(this.reason) }, 0) }) } } }
此时 Promise
处于 pending
状态,无法确定其最后是 fulfilled
还是 rejected
,因此需要将回调函数存放起来,待状态确定后再执行相应的回调函数。
注:invokeArrayFns
来源于 Vue.js 3 中的源码。
2.2.7 then
必须返回 Promise
promise2 = promise1.then(onFulfilled, onRejected)
那么,在我们上面的代码中,then
怎么才能返回 Promise
呢?很简单:
class PromiseImpl { then(onFulfilled, onRejected) { let promise2 = new PromiseImpl((resolve, reject) => { if (this.status === STATUS_FULFILLED) { // ...相关代码 } if (this.status === STATUS_REJECTED) { // ...相关代码 } if (this.status === STATUS_PENDING) { // ...相关代码 } }) return promise2 } }
因为调用 then
之后返回一个新的 Promise
对象,使得我们也可以进行链式调用:
Promise.resolve(42).then().then()...
2.2.7.1 ~ 2.2.7.4 这四点比较重要,我们下面分别来看看。
2.2.7.1 如果 onFulfilled
或 onRejected
返回一个值 x
,则运行 Promise
解决过程,[[Resolve]](promise2, x)
。
解释:其实所谓运行 Promise
解决过程就是执行某个操作,我们把这个操作抽取成一个方法,并命名为:promiseResolutionProcedure(promise, x, resolve, reject)
。为了方便,我们把 resolve
和 reject
一并透传进去。
class PromiseImpl { then(onFulfilled, onRejected) { // ...其他代码 let promise2 = new PromiseImpl((resolve, reject) => { if (this.status === STATUS_FULFILLED) { setTimeout(() => { // 2.2.7.1 let x = onFulfilled(this.value) promiseResolutionProcedure(promise2, x, resolve, reject) }, 0) } if (this.status === STATUS_REJECTED) { setTimeout(() => { // 2.2.7.1 let x = onRejected(this.reason) promiseResolutionProcedure(promise2, x, resolve, reject) }, 0) } if (this.status === STATUS_PENDING) { this.onFulfilledCbs.push(() => { setTimeout(() => { // 2.2.7.1 let x = onFulfilled(this.value) promiseResolutionProcedure(promise2, x, resolve, reject) }, 0) }) this.onRejectedCbs.push(() => { setTimeout(() => { // 2.2.7.1 let x = onRejected(this.reason) promiseResolutionProcedure(promise2, x, resolve, reject) }, 0) }) } }) return promise2 } }
2.2.7.2 如果 onFulfilled
或 onRejected
抛出一个异常 e
,则 promise2
必须 rejected
,并返回原因 e
。
解释:实现上面体现在执行 onFulfilled
或 onRejected
时使用 trycatch
包括起来,并在 catch
时调用 reject(e)
。
class PromiseImpl { then(onFulfilled, onRejected) { // ...其他代码 let promise2 = new PromiseImpl((resolve, reject) => { if (this.status === STATUS_FULFILLED) { setTimeout(() => { try { // 2.2.7.1 let x = onFulfilled(this.value) promiseResolutionProcedure(promise2, x, resolve, reject) } catch (e) { // 2.2.7.2 reject(e) } }, 0) } if (this.status === STATUS_REJECTED) { setTimeout(() => { try { // 2.2.7.1 let x = onRejected(this.reason) promiseResolutionProcedure(promise2, x, resolve, reject) } catch (e) { // 2.2.7.2 reject(e) } }, 0) } if (this.status === STATUS_PENDING) { this.onFulfilledCbs.push(() => { setTimeout(() => { try { // 2.2.7.1 let x = onFulfilled(this.value) promiseResolutionProcedure(promise2, x, resolve, reject) } catch (e) { // 2.2.7.2 reject(e) } }, 0) }) this.onRejectedCbs.push(() => { setTimeout(() => { try { // 2.2.7.1 let x = onRejected(this.reason) promiseResolutionProcedure(promise2, x, resolve, reject) } catch (e) { // 2.2.7.2 reject(e) } }, 0) }) } }) // 2.2.7 `then` must return a promise return promise2 } }
2.2.7.3 如果 onFulfilled
不是函数且 promise1
已经 fulfilled
,则 promise2
必须 fulfilled
且返回与 promise1
相同的值。
解释:值的透传,例如:
Promise.resolve(42).then().then(value => console.log(value)) // 42
在第一个 then
时,我们忽略了 onFulfilled
,那么在链式调用的时候,需要把值透传给后面的 then
:
class PromiseImpl { then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value // ...其他代码 } }
2.2.7.4 如果 onRejected
不是函数且 promise1
已经 rejected
,则 promise2
必须 rejected
且返回与 promise1
相同的原因。
解释:同理,原因也要透传:
Promise.reject('reason').catch().catch(reason => console.log(reason)) // 'reason'
class PromiseImpl { then(onFulfilled, onRejected) { onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason } // ...其他代码 } }
2.3 Promise 解决过程
Promise
解决过程是一个抽象的操作,输入一个 promise
和一个值,这里我们将其命名为 promiseResolutionProcedure(promise, x, resolve, reject)
,并在调用时传入 resolve
和 reject
两个方法, 分别用于在 fulfilled
和 rejected
时调用。
2.3.1 如果 promise
和 x
为同一个对象
如果 promise
和 x
为同一个对象,拒绝该 promise
,原因为 TypeError
。
const promiseResolutionProcedure = (promise, x, resolve, reject) => { // 2.3.1 If `promise` and `x` refer to the same object, // reject `promise` with a `TypeError` as the reason if (promise === x) { return reject(new TypeError('`promise` and `x` refer to the same object, see: https://promisesaplus.com/#point-48')) } // ...其他代码 }
2.3.2 如果 x
是一个 Promise 对象
如果 x
是一个 Promise
对象,则需要递归执行:
const promiseResolutionProcedure = (promise, x, resolve, reject) => { // ...其他代码 // 2.3.2 If `x` is a promise, adopt its state: // 2.3.2.1 If `x` is pending, `promise` must remain pending until `x` is fulfilled or rejected. // 2.3.2.2 If/when `x` is fulfilled, fulfill `promise` with the same value. // 2.3.2.3 If/when `x` is rejected, reject `promise` with the same reason. if (x instanceof PromiseImpl) { return x.then( value => promiseResolutionProcedure(promise, value, resolve, reject), reason => reject(reason) ) } }
2.3.3 如果 x
是一个对象或函数
如果 x
是一个对象或函数:
2.3.3.1 将 x.then
赋值给 x
解释:这样做有两个目的:
- 避免对
x.then
的多次访问(这也是日常开发中的一个小技巧,当要多次访问一个对象的同一属性时,通常我们会使用一个变量将该属性存储起来,避免多次进行原型链查找) - 执行过程中
x.then
的值可能被改变
const promiseResolutionProcedure = (promise, x, resolve, reject) => { // ...其他代码 // 2.3.3 Otherwise, if x is an object or function if ((x !== null && typeof x === 'object') || typeof x === 'function') { // 2.3.3.1 Let `then` be `x.then` let then = x.then } }
2.3.3.2 如果在对 x.then
取值时抛出异常 e
,则拒绝该 promise
,原因为 e
const promiseResolutionProcedure = (promise, x, resolve, reject) => { // ...其他代码 if ((x !== null && typeof x === 'object') || typeof x === 'function') { try { // 2.3.3.1 Let `then` be `x.then` } catch (e) { // 2.3.3.2 If retrieving the property `x.then` results in a thrown exception `e`, // reject `promise` with `e` as the reason. reject(e) } } }
2.3.3.3 如果 then
是函数
如果 then
是函数,则将 x
作为 then
的作用域,并调用 then
,同时传入两个回调函数,第一个名为 resolvePromise
,第二个名为 rejectPromise
。
解释:意思就是在调用 then
的时候要指定其 this
值为 x
,同时需要传入两个回调函数。这时候用 call()
来实现是最好不过了:
then.call(x, resolvePromise, rejectPromise)
2.3.3.3.1 ~ 2.3.3.3.4 总结起来的意思如下:
2.3.3.3.1 如果 resolvePromise
被调用,则递归调用 promiseResolutionProcedure
,值为 y
。因为 Promise
中可以嵌套 Promise
:
then.call( x, y => promiseResolutionProcedure(promise, y, resolve, reject), rejectPromise )
2.3.3.3.2 如果 rejectPromise
被调用,参数为 r
,则拒绝执行 Promise
,原因为 r
:
then.call( x, y => promiseResolutionProcedure(promise, y, resolve, reject), // resolvePromise r => reject(r) // rejectPromise )
2.3.3.3.3 如果 resolvePromise
和 rejectPromise
均被调用,或者被同一参数调用了多次,则只执行首次调用
解释:这里我们可以通过设置一个标志位来解决,然后分别在 resolvePromise
和 rejectPromise
这两个回调函数内做判断:
// 初始化时设置为 false let called = false if (called) { return } // `resolvePromise` 和 `rejectPromise` 被调用时设置为 true called = true
- 2.3.3.3.4 调用
then
抛出异常 e
时,如果这时 resolvePromise
或 rejectPromise
已经被调用,则忽略;否则拒绝该 Promise
,原因为 e
const promiseResolutionProcedure = (promise, x, resolve, reject) => { // ...其他代码 let called = false if ((x !== null && typeof x === 'object') || typeof x === 'function') { try { let then = x.then if (typeof then === 'function') { // 2.3.3.3 If `then` is a function, call it with `x` as `this`, // first argument `resolvePromise`, and second argument `rejectPromise` then.call( // call it with `x` as `this` x, // `resolvePromise` // 2.3.3.3.1 If/when `resolvePromise` is called with a value `y`, // run `[[Resolve]](promise, y)`. y => { // 2.3.3.3.3 If both `resolvePromise` and `rejectPromise` are called, // or multiple calls to the same argument are made, // the first call takes precedence, and any further calls are ignored. if (called) { return } called = true promiseResolutionProcedure(promise, y, resolve, reject) }, // `rejectPromise` // 2.3.3.3.2 If/when `rejectPromise` is called with a reason `r`, // reject `promise` with `r` r => { // 2.3.3.3.3 if (called) { return } called = true reject(r) } ) } else { // 2.3.3.4 If `then` is not a function, fulfill `promise` with `x` resolve(x) } } catch (e) { // 2.3.3.3.3 if (called) { return } called = true // 2.3.3.2 If retrieving the property `x.then` results in a thrown exception `e`, // reject `promise` with `e` as the reason. // 2.3.3.3.4 If calling `then` throws an exception `e` // 2.3.3.3.4.1 If `resolvePromise` or `rejectPromise` have been called, ignore it // 2.3.3.3.4.2 Otherwise, reject `promise` with `e` as the reason reject(e) } } }
2.3.3.4 如果 then
不是函数,则执行该 promise
,参数为 x
const promiseResolutionProcedure = (promise, x, resolve, reject) => { // ...其他代码 if ((x !== null && typeof x === 'object') || typeof x === 'function') { try { let then = x.then if (typeof then === 'function') { // 2.3.3.3 } else { // 2.3.3.4 If `then` is not a function, fulfill `promise` with `x` resolve(x) } } catch (e) { } } }
2.3.4 如果 x
既不是对象也不是函数
如果 x
既不是对象也不是函数,执行该 promise
,参数为 x
:
const promiseResolutionProcedure = (promise, x, resolve, reject) => { // ...其他代码 if ((x !== null && typeof x === 'object') || typeof x === 'function') { // 2.3.3 } else { // 2.3.4 If `x` is not an object or function, fulfill `promise` with `x` resolve(x) } }
至此,我们的自定义 Promise
已经完成。这是源码:promise-aplus-implementing。
4. 如何测试
Promise/A+ 规范提供了一个测试脚本:promises-tests,你可以用它来测试你的实现是否符合规范。
在 PromiseImpl.js
里添加以下代码:
// PromiseImpl.js const STATUS_PENDING = 'pending' const STATUS_FULFILLED = 'fulfilled' const STATUS_REJECTED = 'rejected' const invokeArrayFns = (fns, arg) => { // ...相关代码 } const promiseResolutionProcedure = (promise, x, resolve, reject) => { // ...相关代码 } class PromiseImpl { // ...相关代码 } PromiseImpl.defer = PromiseImpl.deferred = () => { const dfd = {} dfd.promise = new PromiseImpl((resolve, reject) => { dfd.resolve = resolve dfd.reject = reject }) return dfd } module.exports = PromiseImpl
然后在 package.json
里添加 scripts
:
// package.json { "scripts": { "test": "promises-aplus-tests PromiseImpl.js" } }
然后,运行 npm run test
,执行测试脚本,如果你的实现符合 Promise/A+ 规范的话,所有测试用例均会通过。
5. 其他 API 的实现
Promise/A+ 规范只规定了 then()
方法的实现,其他的如 catch()
、finally()
等方法并不在规范里。但就实现而言,这些方法可以通过对 then()
方法或其他方式进行二次封装来实现。
另外,像是 all()
、race()
等这些方法,其参数为一个可迭代对象,如 Array
、Set
、Map
等。那么,什么是可迭代对象根据 ES6 中的规范,要成为可迭代对象,一个对象必须实现 @@iterator
方法,即该对象必须有一个名为 @@iterator
的属性,通常我们使用常量 Symbol.iterator 来访问该属性。
根据以上信息,判断一个参数是否为可迭代对象,其实现如下:
const isIterable = value => !!value && typeof value[Symbol.iterator] === 'function'
Promise.resolve
Promise.resolve(value)
,静态方法,其参数有以下几种可能:
- 参数是
Promise
对象 - 参数是
thenable
对象(拥有 then()
方法的对象) - 参数是原始值或不具有
then()
方法的对象 - 参数为空
因此,其返回值由其参数决定:有可能是一个具体的值,也有可能是一个 Promise
对象:
class PromiseImpl { static resolve(value) { return new PromiseImpl((resolve, reject) => resolve(value)) } }
Promise.reject
Promise.reject(reason)
,静态方法,参数为 Promise
拒绝执行时的原因,同时返回一个 Promise
对象,状态为 rejected
:
class PromiseImpl { static reject(reason) { return new PromiseImpl((resolve, reject) => reject(reason)) } }
Promise.prototype.catch
Promise.prototype.catch(onRejected)
其实就是 then(null, onRejected)
的语法糖:
class PromiseImpl { catch(onRejected) { return this.then(null, onRejected) } }
Promise.prototype.finally
顾名思义,不管 Promise
最后的结果是 fulfilled
还是 rejected
,finally
里的语句都会执行:
class PromiseImpl { finally(onFinally) { return this.then( value => PromiseImpl.resolve(onFinally()).then(() => value), reason => PromiseImpl.resolve(onFinally()).then(() => { throw reason }) ) } }
Promise.all
Promise.all(iterable)
,静态方法,参数为可迭代对象:
- 只有当
iterable
里所有的 Promise
都成功执行后才会 fulfilled
,回调函数的返回值为所有 Promise
的返回值组成的数组,顺序与 iterable
的顺序保持一致。 - 一旦有一个
Promise
拒绝执行,则状态为 rejected
,并且将第一个拒绝执行的 Promise
的原因作为回调函数的返回值。 - 该方法会返回一个
Promise
。
class PromiseImpl { static all(iterable) { if (!isIterable(iterable)) { return new TypeError(`TypeError: ${typeof iterable} is not iterable (cannot read property Symbol(Symbol.iterator))`) } return new PromiseImpl((resolve, reject) => { // `fulfilled` 的 Promise 数量 let fulfilledCount = 0 // 收集 Promise `fulfilled` 时的值 const res = [] for (let i = 0; i < iterable.length; i++) { const iterator = iterable[i] iterator.then( value => { res[i] = value fulfilledCount++ if (fulfilledCount === iterable.length) { resolve(res) } }, reason => reject(reason) ) } }) } }
测试一下:
const promise1 = Promise.resolve(42) const promise2 = new PromiseImpl((resolve, reject) => setTimeout(() => resolve('value2'), 1000)) PromiseImpl.all([ promise1, promise2 ]).then(values => console.log('values:', values))
结果:
values: [42, 'value2']
好像挺完美的,但是事实果真如此吗?仔细看看我们的代码,假如 iterable
是一个空的数组呢?假如 iterable
里有不是 Promise
的呢?就像这样:
PromiseImpl.all([]) PromiseImpl.all([promise1, promise2, 'value3'])
这种情况下执行前面的代码,是得不到任何结果的。因此,代码还要再改进一下:
class PromiseImpl { static all(iterable) { if (!isIterable(iterable)) { return new TypeError(`TypeError: ${typeof iterable} is not iterable (cannot read property Symbol(Symbol.iterator))`) } return new PromiseImpl((resolve, reject) => { // `fulfilled` 的 Promise 数量 let fulfilledCount = 0 // 收集 Promise `fulfilled` 时的值 const res = [] // - 填充 `res` 的值 // - 增加 `fulfilledCount` // - 判断所有 `Promise` 是否已经全部成功执行 const processRes = (index, value) => { res[index] = value fulfilledCount++ if (fulfilledCount === iterable.length) { resolve(res) } } if (iterable.length === 0) { resolve(res) } else { for (let i = 0; i < iterable.length; i++) { const iterator = iterable[i] if (iterator && typeof iterator.then === 'function') { iterator.then( value => processRes(i, value), reason => reject(reason) ) } else { processRes(i, iterator) } } } }) } }
现在再来测试一下:
const promise1 = PromiseImpl.resolve(42) const promise2 = 3 const promise3 = new PromiseImpl((resolve, reject) => setTimeout(() => resolve('value3'), 1000)) PromiseImpl.all([ promise1, promise2, promise3, 'a' ]).then(values => console.log('values:', values)) // 结果:values: [42, 3, 'value3', 'a'] PromiseImpl.all([]).then(values => console.log('values:', values)) // 结果:values: []
Promise.allSettled
Promise.allSettled(iterable)
,静态方法,参数为可迭代对象:
- 当
iterable
里所有的 Promise
都成功执行或拒绝执行后才完成,返回值是一个对象数组。 - 如果有嵌套
Promise
,需要等待该 Promise
完成。 - 返回一个新的
Promise
对象。
对于 allSettled
,我们可以在 all
的基础上进行封装:
class PromiseImpl { static allSettled(iterable) { if (!isIterable(iterable)) { return new TypeError(`TypeError: ${typeof iterable} is not iterable (cannot read property Symbol(Symbol.iterator))`) } const promises = iterable.map(iterator => PromiseImpl.resolve(iterator).then( value => ({ status: STATUS_FULFILLED, value }), reason => ({ status: STATUS_REJECTED, reason }) )) return PromiseImpl.all(promises) } }
测试结果:
PromiseImpl.allSettled([ PromiseImpl.resolve(42), PromiseImpl.reject('Oops!'), PromiseImpl.resolve(PromiseImpl.resolve(4242)), 'a' ]).then(values => console.log(values)) // 结果: // [ // { status: 'fulfilled', value: 42 }, // { status: 'rejected', reason: 'Oops!' }, // { status: 'fulfilled', value: 4242 }, // { status: 'fulfilled', value: 'a' } // ]
Promise.race
Promise.race(iterable)
,静态方法,参数为可迭代对象:
- 当
iterable
里的任一 Promise
成功执行或拒绝执行时,使用该 Promise
成功返回的值或拒绝执行的原因 - 如果
iterable
为空,则处于 pending
状态 - 返回一个新的
Promise
对象
例如:
Promise.race([ Promise.resolve(42), Promise.reject('Oops!'), 'a' ]).then(values => console.log(values)) .catch(reason => console.log(reason)) // 结果:42 Promise.race([ Promise.reject('Oops!'), Promise.resolve(42), 'a' ]).then(values => console.log(values)) .catch(reason => console.log(reason)) // 结果:Oops!
实现如下:
class PromiseImpl { static race(iterable) { if (!isIterable(iterable)) { return new TypeError(`TypeError: ${typeof iterable} is not iterable (cannot read property Symbol(Symbol.iterator))`) } return new PromiseImpl((resolve, reject) => { if (iterable.length === 0) { return } else { for (let i = 0; i < iterable.length; i++) { const iterator = iterable[i] if (iterator && typeof iterator.then === 'function') { iterator.then( value => resolve(value), reason => reject(reason) ) return } else { resolve(iterator) return } } } }) } }
这里要注意一点:别忘了使用 return
来结束 for
循环。
测试结果:
PromiseImpl.race([ PromiseImpl.resolve(42), PromiseImpl.reject('Oops!'), 'a' ]).then(values => console.log(values)) .catch(reason => console.log(reason)) // 结果:42 PromiseImpl.race([ PromiseImpl.reject('Oops!'), PromiseImpl.resolve(42), 'a' ]).then(values => console.log(values)) .catch(reason => console.log(reason)) // 结果:'Oops!' PromiseImpl.race([ 'a', PromiseImpl.reject('Oops!'), PromiseImpl.resolve(42) ]).then(values => console.log(values)) .catch(reason => console.log(reason)) // 结果:'a'
6. 共同探讨
- 在 2.3.3.1 把
x.then
赋值给 then
中,什么情况下 x.then
的指向会被改变? - 在 2.3.3.3 如果
then
是函数 中,除了使用 call()
之外,还有什么其他方式实现吗?
7. 总结
实现 Promise
,基本分为三个步骤:
- 定义
Promise
的状态 - 实现
then
方法 - 实现
Promise
解决过程
8. 写在最后
以前,我在意能不能自己实现一个 Promise
,到处找文章,这块代码 Ctrl+C
,那块代码 Ctrl+V
。现在,我看重的是实现的过程,在这个过程中,你不仅会对 Promise
更加熟悉,还可以学习如何将规范一步步转为实际代码。做对的事,远比把事情做对重要。
如果你觉得这篇文章对你有帮助,还请:点赞、收藏、转发;如果你有疑问,请在评论区写出来,我们一起探讨。同时也欢迎关注我的公众号:前端笔记。
参考资料
No comments:
Post a Comment