按照 Promise/A+ 规范逐行注释并实现 Promise
source link: https://segmentfault.com/a/1190000041640722
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
按照 Promise/A+ 规范逐行注释并实现 Promise
面试官:「你写个 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
这部分大家直接看规范就好,也没什么好解释的。注意其中对于 value
的描述,value
可以是一个 thenable
(有 then
方法的对象或函数) 或者 Promise
,这点会在后面的实现中体现出来。
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()
之外,还有什么其他方式实现吗?
实现 Promise
,基本分为三个步骤:
- 定义
Promise
的状态 - 实现
then
方法 - 实现
Promise
解决过程
8. 写在最后
以前,我在意能不能自己实现一个 Promise
,到处找文章,这块代码 Ctrl+C
,那块代码 Ctrl+V
。现在,我看重的是实现的过程,在这个过程中,你不仅会对 Promise
更加熟悉,还可以学习如何将规范一步步转为实际代码。做对的事,远比把事情做对重要。
如果你觉得这篇文章对你有帮助,还请:点赞、收藏、转发;如果你有疑问,请在评论区写出来,我们一起探讨。同时也欢迎关注我的公众号:前端笔记。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK