按照 Promise/A+ 规范逐行注释并实现 Promise_在线工具

首页 / 新闻资讯 / 正文

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 变为 fulfilledrejected,就无法变成其他状态。当 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   } } 

另外,我们还要定义两个方法,用于 fulfilledrejected 时回调:

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() 中,如果 valuePromise 的话(即嵌套 Promise),则需要等待该 Promise 执行完成。这点很重要,因为后面的其他 API 如 Promise.resolvePromise.allPromise.allSettled 等均需要等待嵌套 Promise 执行完成才会返回结果。

最后,别忘了在 new Promise() 时,我们需要将 resolvereject 传给调用者:

class PromiseImpl {   constructor(executor) {     // ...其他代码      try {       executor(_resolve, _reject)     } catch (e) {       _reject(e)     }   } } 

使用 trycatchexecutor 包裹起来,因为这部分是调用者的代码,我们无法保证调用者的代码不会出错。

2.2 Then 方法

一个 Promise 必须提供一个 then 方法,其接受两个参数:

promise.then(onFulfilled, onRejected) 
class PromiseImpl {   then(onFulfilled, onRejected) {} } 

2.2.1 onFulfilledonRejected

从规范 2.2.1 中我们可以得知以下信息:

  • onFulfilledonRejected 是可选参数
  • 如果 onFulfilledonRejected 不是函数,则必须被忽略

因此,我们可以这样实现:

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 就是规定异步执行的问题,具体的可以结合规范里的注释阅读,重点是确保 onFulfilledonRejected 要异步执行。

需要指出的是,规范里并没有规定 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 onFulfilledonRejected 必须作为函数被调用

这个已经在上面实现过了。

2.2.6 then 可被多次调用

举个例子:

const promise = new Promise((resolve, reject) => {   // ...do something   resolve(value) // or reject(error) })  promise.then() promise.then() promise.catch() 

因此,必须确保当 Promise fulfilledrejected 时,onFulfilledonRejected 按照其注册的顺序逐一回调。还记得最开始我们定义的 resolvereject 吗?这里我们需要改造下,保证所有的回调都被执行到:

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)       }     }   } } 

看到这里你可能会有疑问,什么时候往 onFulfilledCbsonRejectedCbs 里存放对应的回调,答案是在调用 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 如果 onFulfilledonRejected 返回一个值 x,则运行 Promise 解决过程,[[Resolve]](promise2, x)

解释:其实所谓运行 Promise 解决过程就是执行某个操作,我们把这个操作抽取成一个方法,并命名为:promiseResolutionProcedure(promise, x, resolve, reject)。为了方便,我们把 resolvereject 一并透传进去。

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 如果 onFulfilledonRejected 抛出一个异常 e,则 promise2 必须 rejected,并返回原因 e

解释:实现上面体现在执行 onFulfilledonRejected 时使用 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),并在调用时传入 resolvereject 两个方法, 分别用于在 fulfilledrejected 时调用。

2.3.1 如果 promisex 为同一个对象

如果 promisex 为同一个对象,拒绝该 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.1x.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 如果 resolvePromiserejectPromise 均被调用,或者被同一参数调用了多次,则只执行首次调用

    解释:这里我们可以通过设置一个标志位来解决,然后分别在 resolvePromiserejectPromise 这两个回调函数内做判断:

    // 初始化时设置为 false let called = false  if (called) {     return }  // `resolvePromise` 和 `rejectPromise` 被调用时设置为 true called = true 
  • 2.3.3.3.4 调用 then 抛出异常 e 时,如果这时 resolvePromiserejectPromise 已经被调用,则忽略;否则拒绝该 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+ 规范的话,所有测试用例均会通过。

按照 Promise/A+ 规范逐行注释并实现 Promise_在线工具

5. 其他 API 的实现

Promise/A+ 规范只规定了 then() 方法的实现,其他的如 catch()finally() 等方法并不在规范里。但就实现而言,这些方法可以通过对 then() 方法或其他方式进行二次封装来实现。

另外,像是 all()race() 等这些方法,其参数为一个可迭代对象,如 ArraySetMap 等。那么,什么是可迭代对象根据 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) {     // 1. 参数是 Promise 对象     if (value instanceof PromiseImpl) {       return value     }      // 2. 参数是 thenable     if (value !== null && typeof value === 'object' && typeof value.then === 'function') {       return new PromiseImpl((resolve, reject) => {         value.then(           v => resolve(v),           e => reject(e)         )       })     }      // 3. 参数是原始值或不具有 `then()` 方法的对象     // 4. 参数为空     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 还是 rejectedfinally 里的语句都会执行:

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)   } } 

测试结果:

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 = []     for (const iterator of iterable) {       promises.push(         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. 共同探讨

  1. 2.3.3.1 把 x.then 赋值给 then 中,什么情况下 x.then 的指向会被改变?
  2. 2.3.3.3 如果 then 是函数 中,除了使用 call() 之外,还有什么其他方式实现吗?

7. 总结

实现 Promise,基本分为三个步骤:

  1. 定义 Promise 的状态
  2. 实现 then 方法
  3. 实现 Promise 解决过程

8. 写在最后

以前,我在意能不能自己实现一个 Promise,到处找文章,这块代码 Ctrl+C,那块代码 Ctrl+V。现在,我看重的是实现的过程,在这个过程中,你不仅会对 Promise 更加熟悉,还可以学习如何将规范一步步转为实际代码。做对的事,远比把事情做对重要

如果你觉得这篇文章对你有帮助,还请:点赞、收藏、关注;如果你有疑问,请在评论区写出来,我们一起探讨。同时也欢迎关注我的公众号:前端笔记

参考资料