按照 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`.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值