深海鱼的博客 深海鱼的博客
首页
  • 《ES6 教程》
  • 《TypeScript》
  • 《Vue》
  • 《React》
  • 《Git》
  • Javascript
  • CSS
手写系列
  • React项目实战
  • Vue3项目实战
  • 服务端项目实战
  • 鸿蒙项目实战
  • 小小实践
  • Vue全家桶

    • Vue2.0
    • Vue3.0
    • VueRouter
    • Vuex
  • React

    • React
  • Axios
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

深海鱼

尽人事,知天命,知行合一。
首页
  • 《ES6 教程》
  • 《TypeScript》
  • 《Vue》
  • 《React》
  • 《Git》
  • Javascript
  • CSS
手写系列
  • React项目实战
  • Vue3项目实战
  • 服务端项目实战
  • 鸿蒙项目实战
  • 小小实践
  • Vue全家桶

    • Vue2.0
    • Vue3.0
    • VueRouter
    • Vuex
  • React

    • React
  • Axios
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • new与instanceof的实现
  • call、apply和bind实现
  • Promise的原理与实现
    • 一、Promise的特点
      • 状态与状态流转
      • 链式调用
      • ES6中Promise的组成部分
    • 二、Promise的基本使用
    • 三、Promise的规范
      • 1. 相关术语
      • 2. 要求
      • 2.1 Promise的状态
      • 2.2 then方法
      • 2.3 Promise解析过程
      • 3. 规范解释
      • 4. ECMCScript中的Promise规范
    • 四、Promise的实现
      • 1. 声明Promise类
      • 2. 构造函数constructor(executor)
      • 3. resolve和reject函数
      • 4. then实例方法
      • 5. resolvePromise
      • 6. runTask辅助函数
      • 7. catch与finally实例方法
      • 8. 各种静态方法
    • 五、测试
  • 手写系列
深海鱼
2024-06-19
目录

Promise的原理与实现

# Promise的原理与实现

Promise是异步编程的一种解决方案,它比传统的解决方案“回调函数和事件”更合理和更强大。Promise由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。

一个Promise是一个代理,它代表一个在创建promise时不一定已知的值。它允许你将处理程序与异步操作的最终成功值或失败原因关联起来。这使得异步方法可以像同步方法一样返回值:异步方法不会立即返回最终值,而是返回一个promise,以便在将来的某个时间点提供该值。

# 一、Promise的特点

# 状态与状态流转

  1. Promise有三种状态:pending、fulfilled、rejected。
    • pending:初始状态,既没有被兑现,也没有被拒绝。
    • fulfilled:意味着操作成功完成。
    • rejected:意味着操作失败。
  2. Promise只能从pending状态变为fulfilled或rejected状态。
  3. Promise的状态一旦改变,就不会再变。

# 链式调用

连续执行两个或者多个异步操作是一个常见的需求,在上一个操作执行成功之后,开始下一个的操作,并带着上一步操作所返回的结果。在传统的回调风格中,这种操作会导致经典的回调地狱问题 (opens new window)。Promise通过一个Promise链来解决这个问题,这也是Promise API 的核心优势。

  1. Promise对象具有then方法,then方法接收两个参数:onFulfilled和onRejected。
  2. then方法返回一个新的Promise对象,所以支持链式调用。

# ES6中Promise的组成部分

在ES6的标准中,Promise包含以下几个主要的部分:

  • Promise构造函数
  • Promise静态方法和静态属性
  • Promise实例方法

其中构造函数与实例方法中的then方法是Promise规范的组成部分,也是最主要的的部分,其它的静态方法和静态属性等是Promise的辅助工具。

# 二、Promise的基本使用

const p = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('success')
  }, 1000)
}).then(res => {
  return 1
}).catch(err => {
  console.log('错误1', err)
})

p.then(res => {
  console.log(res)
  return 2
})

p.then(res => {
  console.log(res)
  throw new Error('error')
}).catch(err => {
  console.log("错误2", err)
})

// 输出:
"succes"
1
2
"错误2 Error: error"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 三、Promise的规范

我们现在谈的Promise规范通常指Promise/A+规范 (opens new window)。主要内容如下:

# 1. 相关术语

  • Promise:是一个具有then方法的符合本规范的对象或函数
  • thenable:是一个具有then方法的对象或函数
  • value:一个合法的Javascript值
  • exception:通过throw声明的一个异常值
  • reason:一个合法的Javascript值,用于拒绝Promise的原因

# 2. 要求

规范对Promise的状态、then方法的要求以及Promise的解析过程都做了要求,这也是Promise规范中最核心的部分。

# 2.1 Promise的状态

一个promise的状态必须是pending,fulfilled和 rejected中的一个。

  1. 当处于pending状态时,可以改变状态为fulfilled,或者改变状态为rejected。
  2. 当处于fulfilled状态时,不能改变为任何状态,且必须有个不可被改变的value。
  3. 当处于rejected状态时,不能改变为任何状态,且必须有个不可被改变的reason。

提示

此处说的“不可改变”,只是其引用不可变,但如果值本身是个对象,它的内容是可能被改变的。即不保证深度不可变。

# 2.2 then方法

一个promise必须提供一个then方法,用于处理其value和reason。该方法接受两个参数:onFulfilled和onRejected。

对于promise.then(onFulfilled, onRejected):

  1. onFulfilled和onRejected都是可选参数。
    • 如果onFulfilled不是一个函数,则onFulfilled必须被忽略。
    • 如果onRejected不是一个函数,则onRejected必须被忽略。
  2. 如果onFulfilled是一个函数,则:
    • 它必须在promise状态变为fulfilled时调用,并接收value作为第一个参数。
    • 在变为fulfilled之前不能被调用
    • 只能被调用一次
  3. 如果onRejected是一个函数,则:
    • 它必须在promise状态变为rejected时调用,并接收reason作为第一个参数。
    • 在变为rejected之前不能被调用
    • 只能被调用一次
  4. 在执行上下文堆栈(execution contetxt stack)只包含平台代码(platform code)之前,不能调用onFulfilled或onRejected。
  5. onFulfilled或onRejected必须被作为普通函数调用(比如没有this值)。
  6. then可以在同一个promise上多次调用
    • 当promise已经处于fulfilled或变为fulfilled状态时,所有的onFulfilled回调函数都会按顺序被调用。
    • 当promise已经处于rejected或变为rejected状态时,所有的onRejected回调函数都会按顺序被调用。
  7. then必须返回一个promise:promise2 = promise1.then(onFulfilled, onRejected)
    • 如果onFulfilled或者onRejected返回一个值x,则进入Promise的解析流程,调用[[Resolve]](promise2, x)
    • 如果onFulfilled或者onRejected抛出一个异常e,则promise2必须被拒绝,并将e作为拒绝原因
    • 如果onFulfilled不是一个函数且promise1状态变为fulfilled,promise2必须被设置成fulfilled,并将promise1的值作为成功原因
    • 如果onRejected不是一个函数且promise1状态变为rejected,promise2必须被设置成rejected,并将promise1的拒绝原因作为promise2的拒绝原因

# 2.3 Promise解析过程

Promise解析时接收一个promise和一个x作为参数,根据x的类型,执行不同的操作,并将这个过程记为[[Resolve]](promise, x)。如果x是一个Thenable对象,那么会在假设x的行为表现得像一个Promise的基础上,让promise采取x的状态。否则就使用x来兑现promise。

这种对thenable的处理使得Promise的实现更具通用性:只要它们暴露了一个遵循**Promise/A+**规范的then方法,那么就它们之间就可以相互操作。同时也使得Promise的实现可以与那些不太符合规范的实现能够良好的共存。

运行[[Resolve]](promise2, x),需要经过以下步骤:

为了与前面的then方法呼应上,这里将promise记为promise2

  1. 如果promise2和x是同一个对象,则promise2必须被拒绝,并抛出一个TypeError作为拒绝原因。
  2. 如果x是一个Promise,则promise2采用x的状态:
    1. 如果x处于pending状态,promise2必须保持pending状态,直到x被兑现或拒绝。
    2. 如果x处于fulfilled状态,则promise2必须被兑现,并采用x相同的终值。
    3. 如果x处于rejected状态,则promise2必须被拒绝,并采用相同的拒绝原因。
  3. 否则,如果x是一个对象或者函数:
    1. 令then等于x.then
    2. 如果x.then取值抛出了异常e,则以e作为原因拒绝promise2
    3. 如果then是一个函数,则将x作为this执行then,并传入两个参数:第一个参数是resolvePromise,第二个参数是rejectPromise。
    4. 当resolvePromise接收一个参数y被调用时,执行[[Resolve]](promise2, y)
    5. 当rejectPromise接收一个参数r被调用时,以r作为原因拒绝promise2
    6. resolvePromise和rejectPromise只能被调用一次
    7. 如果then执行抛出一个异常e,promise2以e拒绝:如果 resolvePromise或rejectPromise已经执行过了,则忽略这个异常,否则以e拒绝promise2
    8. 否则,then不是一个函数,则promise2以x为值解决
  4. 否则,promise2以x为值解决

注意

如果一个promise被一个循环的thenable链中的对象解决,而[[Resolve]](promise, x)的递归性质又使得其被再次调用,根据上述的算法,这个promise将陷入无限的递归当中并永远处于pending状态。算法虽然不强制要求处理这种情况,但是鼓励实现者检测这样的递归是否存在,若检测到,则 抛出一个TypeError拒绝这个promise。

示例
let thenable = {
  then: function(onFulfilled, onRejected) {
    onFulfilled(thenable)
  }
}

let promise = new Promise((resolve, reject) => {
  resolve(thenable)
})

promise.then(
  res => console.log(res),
  err => console.log(err)
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 3. 规范解释

  • 规范中所说的“平台代码”指的是引擎、环境以及Promise的实现代码。在实践中要保证onFulfilled和onRejected函数是异步执行的,并且是在then方法被调用后的新一轮事件循环中的新执行栈中执行。这个机制可以采用“宏任务(Macro Task)”的机制来实现,比如setTimeout或setImmediate;也可以使用“微任务(Micro Task)”的机制来实现,如MutationObserver或process.nextTick。由于Promise的实现被认为是平台代码的一部分,它可能自身就包含了一个任务调度队列或者trampoline(一种递归到尾调用的优化技术)。
  • onFulfilled或onRejected必须被作为普通函数调用,在严格模式中,其this为undefined,否则为全局对象
  • 在处理thenable时,令then=x.then并测试该引用,这种方式可以避免多次访问x.then,确保属性的一致性,因为其值可能在检索调用时发生了改变。

# 4. ECMCScript中的Promise规范

从以上的规范来看,Promise/A+ 规范并没有提及我们平时使用Promise时使用到的构造函数、reject函数、resolve函数以及各种静态方法的具体定义和实现方式(但是指明了它们之间是同步还是异步执行的要求)。这是因为Promises/A+规范的思想并不是提供某种库API。它是为了提供一组最小的可互操作的原语,以便任何使用Promise的人都可以依赖于它具有统一的接口。这对于跨库互操作尤其重要,例如在应用程序的不同组件的接缝处,可能在内部使用不同的Promise库。为Promise库指定完整的API并不是实现这一目标的必要条件。

然而ECMAScript6中,新增了Promise对象是对Promise/A+规范的一种具体实现。它对Promise实现的要求提出更加严格的要求,已经超出了Promise/A+规范。换句话说,ECMAScript的Promise对象只是众多``Promise/A+`规范实现的一种,你自己的实现则不一定非要严格遵循ECMAScript的Promise规范。

ECMAScript@2025 Language Specification#Promise-Object (opens new window)

以下为ECMAScript的Promise规范对reject函数、resolve函数以及constructor函数的定义:

resolve(resolution):

  1. 令F为激活的函数对象
  2. 确保F是一个具有[[Promise]]属性(内部槽)的对象
  3. 令promise为F.[[Promise]]属性的值
  4. 令alreadyResolved为F.[[AlreadyResolved]]属性的值
  5. 如果alreadyResolved.[[Value]]为true,return undefined
  6. 令alreadyResolved.[[Value]]为true
  7. 如果resolution为promise,throw a TypeError
  8. 如果resolution为不是一个对象,则执行FulfillPromise(promise, resolution),并return undefined
  9. 否则令then为resolution.then(处理thenable,处理步骤与上文promise.then基本一致)
  10. return undefined

reject(reason):

  1. 令F为激活的函数对象
  2. 确保F是一个具有[[Promise]]属性(内部槽)的对象
  3. 令promise为F.[[Promise]]属性的值
  4. 令alreadyResolved为F.[[AlreadyResolved]]属性的值
  5. 如果alreadyResolved.[[Value]]为true,return undefined
  6. 令alreadyResolved.[[Value]]为true
  7. 执行RejectPromise(promise, reason)
  8. return undefined

提示

ECMAScript中,reject和resolve都是内置的匿名函数,它们含有[[Promise]]、[[AlreadyResolved]]等属性,是Promise对象的内部槽。

constructor(executor):

  1. 如果NewTarget是undefined,抛出一个TypeError异常。
  2. 如果executor不可调用,抛出一个TypeError异常。`
  3. 创建promise对象,且原型为Promise.prototype,定义[[PromiseState]]、[[PromiseResult]]、[[PromiseFulfillReactions]]、[[PromiseRejectReactions]]、[[PromiseIsHandled]]。
  4. 设置[[PromiseState]]为pending。
  5. 设置[[PromiseFulfillReactions]]为空数组。
  6. 设置[[PromiseRejectReactions]]为空数组。
  7. 设置[[PromiseIsHandled]]为false。
  8. 创建resolvingFunctions,即用于改变状态的resolve和reject函数。
  9. 调用executor函数,并传入resolve和reject函数作为参数。结果为completion
  10. 如果completion是异常值,则调用reject函数,并传入completion作为参数。
  11. 返回promise。

# 四、Promise的实现

如上文所说Promise/A+规范一种解决思想,不是具体的实现。在社区中有非常多的实现,除了Javascript的各种具体实现方式,还包括Java、Python、PHP、Swift、Objective-C等等。可以点击这里查看Conformant Implementations (opens new window)。

下面,我们将一步步的实现Promise/A+规范以及ECMAScript Promise规范中的一些静态方法。

# 1. 声明Promise类

class Promise {
  // 构造函数
  constructor(executor) { 
    function resolve(value) { } // resolve函数
    function reject(reason) { } // reject函数
  } 

  then(onFulfilled, onRejected) { } // then方法
  catch(onRejected) { } // catch方法
  finally(onFinally) { } // finally方法

  static resolve(value) { } // resolve静态方法
  static reject(reason) { } // reject静态方法
  static all(promises) { } // all静态方法
  static race(promises) { } // race静态方法
  static any(promises) { } // any静态方法
  static allSettled(promises) { } // allSettled静态方法
  static withResolvers() { } // withResolvers静态方法
}

// Promise解析函数
function resolvePromise(promise, x, resolve, reject) {}

// 判断是否为函数的辅助函数
function isFunction(o) { 
  return typeof o === 'function'
} 

// 异步任务函数的辅助函数
function runTask() {}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

# 2. 构造函数constructor(executor)

参照ECMAScript Promise规范对构造函数的定义,实现构造函数还是相对比较简单的,代码如下:

  constructor(executor) {
    // 必须使用new调用
    if(new.target !== Promise) {
      throw new TypeError('Promise constructor cannot be invoked without new operator');
    }

    // executor必须为函数
    if (typeof executor !== 'function') {
      throw new TypeError(`Promise resolver ${executor} is not a function`)
    }

    this.state = 'pending' // 初始状态
    this.value = undefined // 兑现终值
    this.reason = undefined // 拒绝原因
    this.onFulfilledCallbacks = [] // 存储成功回调
    this.onRejectedCallbacks = [] // 存储失败回调

    // resolve和reject函数
    function resolve(value){}
    function reject(reason){}

    try {
      // 同步执行executor
      executor(resolve, reject) 
    } catch (error) { 
      // executor执行异常中断,promise需要被reject
      reject(error)
    }
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# 3. resolve和reject函数

以下runTask是一个执行异步任务的函数,在后文定义

// resolve函数
function resolve(value) {
  // 如果value是promise,则需要等待promise执行完成,再执行then
  if (value instanceof Promise) {
    return value.then(resolve, reject)
  }

  // resolve回调必须是异步调用的
  runTask(() => {
    if (this.state !== 'pending') return // 状态只能改变一次
    this.value = value // 设置终值
    this.state = 'fulfilled' // 状态标记为fulfilled
    // 遍历fulfilled回调队列,以当前终值为参数按顺序调用所有的兑现回调函数
    this.onFulfilledCallbacks.forEach(fn => fn(this.value))
  })
}

// reject函数
function reject(value) {
  runTask(() => {
    if (this.state !== 'pending') return // 状态只能改变一次
    this.reason = reason // 设置拒绝原因
    this.state = 'rejected' // 状态标记为rejected
    // 遍历rejected回调队列,以当前拒因为参数按顺序调用所有的拒绝回调函数
    this.onRejectedCallbacks.forEach(fn => {
      fn(this.reason)
    })
  })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

resolve和reject的核心实现是改变状态和保存兑现的值或者拒绝的原因,然后开启一个异步任务,将回调函数添加到回调队列中。同时应该保证,Promise的状态只能被改变一次。

# 4. then实例方法

then方法是整个Promise的核心,只要then方法实现了,Promise上的各种常见的静态方法都可以基于它来实现。then方法接收两个参数,第一个参数是onFulfilled,表示兑现后要执行的回调函数,第二个参数是onRejected,表示拒绝后要执行的回调函数。我们参照规范,逐步来实现then方法。

then(onFulfilled, onRejected):

首先,then方法必须返回一个新的Promise实例。

then() {
  const newPromise = new Promise((resolve, reject) => {

  })
  return newPromise
}
1
2
3
4
5
6

其次,then方法必须接受两个参数,onFulfilled和onRejected。它们都是可选的,如果不传入函数则忽略

 
 
 







then(onFulfilled, onRejected) {
  onFulfilled = isFunction(onFulfilled) ? onFulfilled : undefined
  onRejected = isFunction(onRejected) ? onRejected : undefined

  const newPromise = new Promise((resolve, reject) => {

  })
  return newPromise
}
1
2
3
4
5
6
7
8
9

接着就是onFulfilled和 onRejected的处理程序了。规范要求,如果promise处于pending状态,那么onFulfilled和onRejected必须被添加到promise的onFulfilledCallbacks和onRejectedCallbacks数组中。同时同一个promise的then方法是可以被多次调用的,它们应该被按顺序添加到对应的数组中。如果promise的状态已经改变,那么onFulfilled或onRejected应该被执行。因此,then方法中的新Promise的执行主体应该包含三种情况来处理。

const newPromise = new Promise((resolve, reject) => {
  if(this.state === 'pending') { 
    // 存储回调
    return 
  }
  if(this.state === 'fulfilled'){ 
    // 执行兑现回调
    return 
  }
  if(this.state === 'rejected'){ 
    // 执行拒绝回调
    return 
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14

然后根据then函数处理onFulfilled和 onRejected的以下几点要求,我们继续完善代码:

  • onFulfilled和 onRejected是异步执行的,所以需要将onFulfilled和 onRejected函数放入异步任务队列中执行。
  • onFulfilled和onRejected必须被作为普通函数调用
  • 如果onFulfilled或onRejected返回一个值x时,调用resolvePromise函数进行解析;否则如果它们抛出异常,则新的Promise需要被拒绝。
  • 如果onFulfilled或onRejected不是函数,且promise的状态已经改变,那么then方法返回的新Promise的状态应该和原Promise一样(也叫回调穿透)
// 用于执行回调的辅助函数
function call(fn) {
  try {
    fn()
  } catch (error) {
    //  onFulfilled或onRejected执行抛出异常需要将新Promise拒绝
    reject(error)
  }
}
// fulfilled状态直接执行兑现回调
if (this.state === 'fulfilled') {
  runTask(() => { // 回调需要异步调用
    call(() => {
      // 返回了一个值,则进入resolvePromise解析流程
      let x = onFulfilled(this.value)
      resolvePromise(newPromise, x, resolve, reject)
    })
  })
  return
}

// rejected状态直接执行拒绝回调
if (this.state === 'rejected') {
  // 与fulfilled状态处理同理
  runTask(() => {
    call(() => {
      let x = onRejected(this.reason)
      resolvePromise(newPromise, x, resolve, reject)
    })
  })
  return
}

// 如果是pending状态,则将回调函数添加到回调队列中
if (this.state === 'pending') {
  // 此处存储的回调最终会被resolve或者reject遍历执行,已经是异步执行的了
  // 因此此处只要保证把执行异常捕获了就可以
  onFulfilled && this.onFulfilledCallbacks.push(() => {
    call(() => {
      let x = onFulfilled(this.value)
      resolvePromise(newPromise, x, resolve, reject)
    })
  })

  onRejected && this.onRejectedCallbacks.push(() => {
    call(() => {
      let x = onRejected(this.reason)
      resolvePromise(newPromise, x, resolve, reject)
    })
  })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

以上是then方法的核心实现,但是上面标准中的提到的回调穿透并没有实现。回调穿透指的是当上一个promise返回的终值或错误在下一个then回调中没有被处理时,那么下一个then回调中的onFulfilled或onRejected回调会被执行,直到末端。如果未被捕获,则会抛出一个UnhandledPromiseRejectionWarning警告(此处的警告不是规范的内容,但是常见的宿主环境在实现时都会做此处理)。目前为止,如下代码不能按照规范如期执行:

let p = new Promise(resolve => resolve('success'))
p.then().then(a, b) // 不会接收到'success'

let p2 = new Promise(resolve => reject('fail'))
p.then().catch(err => console.log(err)) // 不会打印err
1
2
3
4
5

这是因为,第一个then返回的已经是一个新的Promise实例,后续的then和catch都会被添加到这个实例上,而不是p上。由于第一个then没有对p状态改变后的值进行传递,所以后续的then和catch都不会被执行。要解决此问题,只需要在onFulfilled和onRejected中不为函数的时候,将其重置为一个仅返回当前终值或者抛出当前异常的函数即可。将以下两处代码做如下修改:

then(onFulfilled, onRejected) {
  - onFulfilled = isFunction(onFulfilled) ? onFulfilled : undefined // [!code --]
  - onRejected = isFunction(onRejected) ? onRejected : undefined // [!code --]
  onFulfilled = isFunction(onFulfilled) ? onFulfilled : value => value // [!code ++]
  onRejected = isFunction(onRejected) ? onRejected : reason => { throw reason } // [!code ++]

  const newPromise = new Promise((resolve, reject) => {
    // ...
    onFulfilled && this.onFulfilledCallbacks.push(() => { // [!code --]
    this.onFulfilledCallbacks.push(() => { // [!code ++]
      ...
    })

    onRejected && this.onRejectedCallbacks.push(() => { // [!code --]
    this.onRejectedCallbacks.push(() => { // [!code ++]
      //...
    })
  })
  return newPromise
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 5. resolvePromise

resolvePromise是解析then方法中返回的值的核心方法,其也是保证Promise链式调用的关键,也是保证不同实现的thenable对象之间能够相互操作的关键。接下来,我们就照着Promise/A+的规范来实现resolvePromise函数。

我们把一个promise记为promise1,其then方法返回的新的promise记为promise2。

resolvePromise(promise, x, resolve, reject):

/**
 * @param {Promise} promise promise1.then返回的promise2
 * @param {any} x promise1的onFulfilled或onRejected的返回值
 * @param {Function} resolve promise2的resolve
 * @param {Function} reject promise2的reject
 */
function resolvePromise(promise, x, resolve, reject) {
  // 1. 如果promise和x指向同一个对象,以TypeError为据因拒绝执行promise,防止循环引用
  if (promise === x) {
    return reject(new TypeError('Chaining cycle detected for promise'))
  }

  let called = false // 处理thenable时,确保resolve和reject只被调用一次

  // 2. 如果x是一个promise,则让promise接受x的状态
  if (x instanceof Promise) {
    // 2.1 如果x处于pending状态,promise必须保持pending状态,直到x被fulfilled或rejected
    if (x.state === 'pending') {
      x.then(
        value => resolvePromise(promise, value, resolve, reject),
        reason => reject(reason)
      )
    } else { 
      // 2.2 如果x处于fulfilled或rejected状态,则用相同的value执行promise
      x.then(resolve, reject)
    }
  } else if (isFunction(x) || typeof x === 'object' && x !== null) { 
    // 3. 如果x是一个对象或函数
    try {
      let then = x.then // 读取其then属性
      if (isFunction(then)) { // 3.1 x上具有then方法(thenable对象)
        // 3.1.1 将x作为this绑定,执行then方法
        then.call(
          x,
          y => {
            if (called) return // resolve只能被执行一次
            called = true
            resolvePromise(promise, y, resolve, reject)
          },
          r => {
            if (called) return // reject只能被执行一次
            called = true
            reject(r)
          }
        )
      } else { 
        // 3.2 x上没有then方法(普通对象),以其值兑现
        resolve(x)
      }
    } catch (e) { 
      // 检索x.then失败,则需要拒绝promise2
      if (called) return
      called = true
      reject(e)
    }
  } else { 
    // 4. 如果x是一个原始值
    resolve(x)
  }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61

可以看到resolvePromise的实现还是比较复杂的,要彻底的理解Promise的运行机制,就必须要理解resolvePromise的实现,你可以多阅读几次该函数,好好理解和梳理其中的细节。

# 6. runTask辅助函数

Promise/A+要求resolve、reject和then方法都是异步执行的,所以我们需要将resolve和reject方法封装成一个runTask函数。实现的方式可以使用宏任务,也可以使用微任务:

  • setTimeout:宏任务
  • setImmediate:宏任务
  • process.nextTick:微任务
  • MutationObserver:微任务
  • ...

这里我们使用setTimeout来实现该异步任务函数

function runTask(task) {
  setTimeout(task, 0)
}
1
2
3

到这里为止,其实Promise/A+规范的内容就已经全部实现完了,如果用这个实现去跑一下Promise/A+社区的测试用例,应该就可以全部通过啦。至于Promise其它的实例方法和静态方法,你可以简单的理解为它们大部分是then的语法糖或多个Promise实例的组合方法。下面我们就来实现这些方法。

# 7. catch与finally实例方法

(1)catch实例方法:

catch方法的实现非常简单,只需要将onRejected作为then的第二个参数传入即可。

catch(onRejected) {
  return this.then(undefined, onRejected)
}
1
2
3

(2)finally方法:

finally方法是只要Promise完成(无论成功或失败)都会执行的操作,因此你可能会想到这么实现:

finally(onFinally) {
  return this.then(onFinally, onFinally)
}
1
2
3

但其实finally的实现是有要求的,它应该满足以下特点:

  • finally方法必须返回一个Promise实例
  • finally方法无论Promise的状态如何,都会执行
  • finally方法返回值会被忽略
  • finally方法的回调函数不收任何参数
  • finally``Promise实例的结果会被传递至下一个then链

如下:

new Promise(resolve => {
     resolve(123)   
}).then() .finally(res => {
    console.log("finally", res)
    return 456
}).then(res => {
    console.log("then2", res)
})
// 输出 
// finally undefined
// then2 123
1
2
3
4
5
6
7
8
9
10
11

因此,我们可以将onFinally使用Promise.resolve包裹后来实现以上特点(你可以先阅读Promise.resolve的实现再回来看这里)

finally(onFinally) {
  return this.then(
    value => Promise.resolve(onFinally()).then(() => value),
    reason => Promise.resolve(onFinally()).then(() => { throw reason })
  )
}
1
2
3
4
5
6

# 8. 各种静态方法

ECMAScript 6中提供了all、allSettled、race、any四个组合静态方法以及resolve、reject、withResolves等几个静态方法。

(1)Promise.all方法

Promise.all() 静态方法接受一个 Promise 可迭代对象作为输入,并返回一个 Promise。当所有输入的 Promise 都被兑现时,返回的 Promise 也将被兑现(即使传入的是一个空的可迭代对象),并返回一个包含所有兑现值的数组。如果输入的任何 Promise 被拒绝,则返回的 Promise 将被拒绝,并带有第一个被拒绝的原因。

实现的基本思路是遍历传入的Promise迭代对象,并记录每个对象的执行结果,当所有Promise都被兑现时,将结果数组作为参数调用resolve方法。只要有一个Promise被拒绝,则将拒绝原因作为参数调用reject方法。

static all(promises) {
  return new Promise((resolve, reject) => {
    const result = [] // 存储结果
    let count = 0 // 记录当前兑现的个数
    for (let i = 0; i < promises.length; i++) {
      const p = promises[i]
      p.then(value => {
        count++
        result[i] = value
        if (count === promises.length) { // 已全部兑现
          resolve(result)
        }
      }).catch(reject)
    }
  })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

(2)Promise.allSettled方法

Promise.allSettled()静态方法将一个 Promise 可迭代对象作为输入,并返回一个单独的 Promise。当所有输入的 Promise 都已敲定(无论拒绝还是兑现)时(包括传入空的可迭代对象时),返回的 Promise 将被兑现,并带有描述每个 Promise 结果的对象数组。

实现的基本思路是遍历传入的Promise迭代对象,并记录每个对象的执行结果(每一项为一个包含{status, value | reason}的对象),当所有Promise都被敲定时,将结果数组作为参数调用resolve方法。

static allSettled(promises) {
  return new Promise((resolve, reject) => {
    const results = [] //结果
    let count = 0 // 当前敲定个数
    for (let i = 0; i < promises.length; i++) {
      const p = promises[i]
      p.then(value => {
        results[i] = { status: 'fulfilled', value } // 记录兑现结果
      }).catch(reason => {
        results[i] = { status: 'rejected', reason } // 记录拒绝原因
      }).finally(() => {
        count++
        // 已全部敲定
        if (count === promises.length) {
          resolve(results)
        }
      })
    }
  })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

(3)Promise.race方法

Promise.race()静态方法接受一个 promise 可迭代对象作为输入,并返回一个 Promise。这个返回的 promise 会随着第一个 promise 的敲定而敲定。

static race(promises) {
  return new Promise((resolve, reject) => {
    for (let i = 0; i < promises.length; i++) {
      const p = promises[i]
      p.then(resolve).catch(reject)
    }
  })
}
1
2
3
4
5
6
7
8

(4)Promise.any方法

Promise.any()静态方法将一个 Promise 可迭代对象作为输入,并返回一个 Promise。当输入的任何一个 Promise 兑现时,这个返回的 Promise 将会兑现,并返回第一个兑现的值。当所有输入 Promise 都被拒绝(包括传递了空的可迭代对象)时,它会以一个包含拒绝原因数组的 AggregateError 拒绝。

static any(promises) {
  return new Promise((resolve, reject) => {
    const errors = []
    let count = 0
    for (let i = 0; i < promises.length; i++) {
      const p = promises[i]
      p.then(resolve).catch(reason => {
        count++
        errors[i] = reason
        if (count === promises.length) {
          reject('none resolved', new AggregateError(errors))
        }
      })
    }
  })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

(5)Promise.resolve方法

  static resolve(value) {
    // 如果value本身是promise则直接返回
    if (value instanceof Promise) return value
    // 之所以返回then创建的新promise,是为了让Promise.resolve(thenable)能够正常工作
    return new Promise(resolve => resolve(value)).then(value => value)
  }
1
2
3
4
5
6

(6)Promise.reject方法

static reject(reason) {
  return new Promise((resolve, reject) => reject(reason))
}
1
2
3

(7)Promise.withResolves方法

Promise.withResolvers() 静态方法返回一个对象,其包含一个新的 Promise 对象和两个函数,用于解决或拒绝它,对应于传入给 Promise() 构造函数执行器的两个参数。

static withResolvers() {
  let resolve, reject
  const promise = new Promise((_resolve, _reject) => {
    resolve = _resolve
    reject = _reject
  })
  return {
    promise,
    resolve,
    reject
  }
}
1
2
3
4
5
6
7
8
9
10
11
12

# 五、测试

Promise测试通常是借助promises-aplus-tests (opens new window)这个测试工具。其覆盖了完整的Promise/A+规范的测试用例,测试通过率达到100%才算完整实现了Promise/A+规范。

首先安装测试工具:

npm i -g promises-aplus-tests

1
2

然后是添加一个适配器:测试工具要求实现的Promise库暴漏一个包含以下三个的API:

  • resolve(value)(可选):创建一个resolved的Promise
  • reject(reason)(可选):创建一个rejected的Promise
  • deferred():创建一个延迟对象,并返回一个对象,包含promise和resolve、reject方法

其中resolve(value)和reject(reason)已经实现了,如果没有提供,则测试工具会使用deferred对象自动创建。而deferred其实就是Promise.withResolvers方法,也可以使用以下方式再添加:

Promise.deferred = function () { // 延迟对象
  let defer = {};
  defer.promise = new Promise((resolve, reject) => {
    defer.resolve = resolve;
    defer.reject = reject;
  });
  return defer;
}
1
2
3
4
5
6
7
8

提示

jQuery的异步操作也是借助其Deferred对象实现的。

把Promise导出

try {
  module.exports = Promise
} catch (e) {

}
1
2
3
4
5

最后执行测试:

promises-aplus-tests promise.js
1

测试结果是全部通过的,如下:

promise-test-result

至此,我们已经把Promise/A+规范以及ECMAScript Promises所有的静态方法都实现了。完整的代码请点击这里查看 (opens new window)

最近更新: 2024/06/19, 17:54
call、apply和bind实现

← call、apply和bind实现

最近更新
01
Axios源码解读
07-29
02
基于React16的Webpack升级与构建速度优化
07-09
03
Vue-Router源码解读
07-09
更多文章>
Theme by Vdoing | Copyright © 2024-2024 深海鱼 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式