5

按照 Promise/A+ 规范逐行注释并实现 Promise

 2 years ago
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.
neoserver,ios ssh client

按照 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 变为 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+ 规范的话,所有测试用例均会通过。

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

测试结果:

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() 之外,还有什么其他方式实现吗?

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

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

8. 写在最后

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

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK