Skip to content

Promise 对象

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

Promise 对象有以下两个特点。

参考:https://es6.ruanyifeng.com/#docs/promise

(1)对象的状态不受外界影响。

Promise 对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是 Promise 这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。

(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。

Promise 对象的状态改变,只有两种可能:从 pending 变为 fulfilled 和从 pending 变为 rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对 Promise 对象添加回调函数,也会立即得到这个结果。

下面通过手写一个符合Promises/A+规范的 Promise 来深入理解 Promise。

构造函数的实现

ES6 规定,Promise 对象是一个构造函数,用来生成 Promise 实例。

分析

参考下面原生的 Promise 写法,可以得出结论:

js
const p = new Promise((resolve, reject) => {
  resolve()
})
  1. Promise 构造函数接收一个函数(假设命名为:fn)作为参数。
  2. fn 的两个参数分别是resolvereject,它们同样也是函数。
  3. 在 Promise 中 fn 是同步执行的。

使用class关键字定义一个MyPromise类。接受一个 executor 方法并调用(调用的原因:上述分析中的第 3 条)。

js
class MyPromise {
  constructor(executor) {
    executor()
  }
}

可以在constructor中创建resolvereject函数。

js
class MyPromise {
  constructor(executor) {
    const resolve = () => {} 
    const reject = () => {} 
    executor() 
    executor(resolve, reject) 
  }
}
探讨resolvereject函数定义的位置

constructor中创建resolvereject函数,每次实例化的时候都会创建两个新的函数。但是不能把这两个函数放在原型上面,使每一个 Promise 对象都共用这两个方法。下面是错误的做法:

js
class MyPromise {
  constructor(executor) {
    executor(this.resolve, this.reject)
  }
  resolve() {
    // this
  }
  reject() {}
}

原因就在于如果这样写的话,line 6 的 this 指向会有问题。因为函数中 this 的指向取决于该函数的调用方式。而resolve函数是在外部被调用的,因此 this 指向全局对象 window(node 环境中是 global)。

如果一定要在原型上定义resolvereject函数。那就需要下面这种写法:

js
class Mypromise {
  constructor(executor) {
    executor(this.resolve, this.reject) 
    executor(this.resolve.bind(this), this.reject.bind(this)) 
  }
  resolve() {}
  reject() {}
}

但是这样做的话每次调用 bind()函数也会返回一个新的函数,还不如直接在constructor中定义这两个方法。

resolve函数可以接收一个参数,不传就是 undefined。
reject函数也接收一个错误对象作为参数,表示错误的原因。

js
class MyPromise {
  constructor(executor) {
    const resolve = (res) => {}
    const reject = (err) => {}
    executor(resolve, reject)
  }
}

resolve函数的作用是:

  • 将 Promise 对象的状态从“未完成”变为“成功”(即从 pending 变为 fulfilled)。
  • 返回当前 Promise 的结果。
示例图

resolve

因此在 Promise 中需要定义一个状态(state)属性和一个结果(result)属性。

js
class MyPromise {
  constructor(executor) {
    this._state = 'pending' 
    this._result = undefined 
    const resolve = (res) => {}
    const reject = (err) => {}
    executor(resolve, reject)
  }
}
定义私有属性

_state_result使用下划线_的原因是这两个属性是内部属性,不希望外部访问。这种写法只是通过命名规范进行一个轻量级的约束。这种写法在外部依然可以访问到这两个属性。

参考这篇文章。可以像下面这样,通过#来定义私有属性。

js
class MyPromise {
  #state = 'pending' 
  #result = undefined 

  constructor(executor) {
    this._state = 'pending' 
    this._result = undefined 
    const resolve = (res) => {}
    const reject = (err) => {}
    executor(resolve, reject)
  }
}

补充resolve函数和reject函数要做的事情。

js
class MyPromise {
  #state = 'pending'
  #result = undefined

  constructor(executor) {
    const resolve = (res) => {
      this.#state = 'fulfilled' 
      this.#result = res 
    }
    const reject = (err) => {
      this.#state = 'rejected' 
      this.#result = err 
    }
    executor(resolve, reject)
  }
}

根据 Promise 的第二个特点,需要在调用resolvereject方法的时候添加状态是否已被修改的判断。如果状态被修改了,则什么也不做。

js
constructor(executor) {
  const resolve = (res) => {
    if (this.#state !== 'pending') return 
    this.#state = 'fulfilled'
    this.#result = res
  }
  const reject = (err) => {
    if (this.#state !== 'pending') return 
    this.#state = 'rejected'
    this.#result = err
  }
  executor(resolve, reject)
}

resolvereject方法中的操作提取成一个私有函数changeState

js
class MyPromise {
  #state = 'pending'
  #result = undefined
  constructor(executor) {
    const resolve = (res) => {
      this.#changeState('fulfilled', res) 
    }
    const reject = (err) => {
      this.#changeState('rejected', err) 
    }
  }

  #changeState(state, result) {
    if (this.#state !== 'pending') return
    this.#state = state
    this.#result = result
  }
}
避免硬编码(hard code)

定义PENDINGFULFILLEDREJECTED常量。并在使用到pendingfulfilledrejcted的地方进行替换。

js
const PENDING = 'pending' 
const FULFILLED = 'fulfilled' 
const REJECTED = 'rejcted' 

#state = 'pending' 
#state = PENDING 

const resolve = (res) => {
  this.#changeState('fulfilled', res) 
  this.#changeState(FULFILLED, res) 
}
const reject = (err) => {
  this.#changeState('rejected', err) 
  this.#changeState(REJECTED, err) 
}

#changeState(state, result) {
  if (this.#state !== 'pending') return 
  if (this.#state !== PENDING) return 
}

executor函数中的错误处理。

示例图

throw-error

示例图中可以看到如果executor函数执行发生错误的话,Promise 的状态会被改变为rejected,并且会返回错误信息。 将executor函数的调用放入try catch中。

js
class MyPromise {
  constructor(executor) {
    const resolve = (res) => {}
    const reject = (err) => {}
    executor(resolve, reject) 
    
    try {
      executor(resolve, reject)
    } catch (err) {
      reject(err)
    }
    
  }
}

注意

此处的try catch只能捕获同步错误。对于异步错误,目前官方的 Promise 也无法捕获异步错误,因此异步错误无法影响 Promise 的状态。

示例图

async-error

手写 Promise ——构造函数的完整代码
js
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class MyPromise {
  #state = PENDING
  #result = undefined

  constructor(executor) {
    const resolve = (res) => {
      this.#changeState(FULFILLED, res)
    }
    const reject = (err) => {
      this.#changeState(REJECTED, err)
    }

    try {
      executor(resolve, reject)
    } catch (error) {
      reject(error)
    }
  }

  #changeState(state, result) {
    if (this.#state !== PENDING) return
    this.#state = state
    this.#result = result
  }
}

then 的回调(onFulfilled、onRejected)执行时机

Promises/A+规范中着重强调了then方法: Promises/A+A promise must provide a then method to access its current or eventual value or reason.

分析

通过阅读 Promises/A+规范中对then方法的描述,可以得出以下结论:

  1. Promise 的 then 方法接收两个可选参数,onFulfilledonRejected
    • 如果onFulfilled是一个函数:
      它必须在 Promise 状态是 fulfilled 之后调用,并以 Promise 的值作为第一个参数。
      它不能被多次调用。
    • 如果onRejected是一个函数:
      它必须在 Promise 状态是 rejected 之后调用,并以 Promise 的原因作为第一个参数。
      它不能被多次调用。
  2. then 可能会针对同一个 Promise 被多次调用。
  3. then 必须返回一个 Promise。

定义then函数签名。接收两个可选参数:onFulfilledonRejected,并返回一个 Promise。

js
class MyPromise {
  then(onFulfilled, onRejected) {
    return new MyPromise(resolve, reject)
  }
}

onFulfilled调用时机:当 Promise 状态为 fulfilled 时(pending ——> fulfilled)调用,并将结果#result传入。
onRejected调用时机:当 Promise 状态为 rejected 时(pending ——> rejected)调用,并将结果#result传入。

js
class MyPromise {
  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      if (this.#state === FULFILLED) {
        onFulfilled(this.#result)
      } else if (this.#state === REJECTED) {
        onRejected(this.#result)
      }
    })
  }
}
js
const p = new MyPromise((resolve, reject) => {
  resolve(111)
})
p.then((res) => {
  console.log('promise 完成', res) // promise 完成 111
})
js
const p = new MyPromise((resolve, reject) => {
  reject(222)
})
p.then((err) => {
  console.log('promise 失败', err) // promise 失败 222
})

但是这个then方法此时并不能处理异步的情况,如下所示:

js
const p = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(111)
  }, 1000)
})
p.then(
  (res) => {
    console.log(res) // 不会输出任何内容
  },
  (err) => {
    console.log(err) // 不会输出任何内容
  }
)

原因

在上面使用了 setTimeout(() => {resolve(111)}, 1000)的情况下,得到的 Promise p 是挂起状态。1000ms 之后才会完成。当运行到 p.then()方法时,由于 Promise 是挂起状态,所以既不会执行onFulfilled回调也不会执行onRejected回调。1000ms 之后也不会执行,因为 then()方法已经执行结束了,不会有机会再次执行onFulfilledonRejected

then方法中无法获取到当前 Promise 什么时候完成,什么时候拒绝。但是#changeState方法可以知道,因为#changeState方法的作用就是改变 Promise 的状态,而#changeState方法中又无法使用then方法的onFulfilledonRejected回调。

因此,可以在then方法中只把onFulfilledonRejected,以及 return 的resolvereject记录到某个私有属性中,而不是直接处理逻辑。

then方法中去调用一个私有方法#run,来处理上一步记录到私有属性中的那些函数。

示例图

then-method

js
class MyPromise {
  #handler = undefined

  #changeState(state, result) {
    // ...
    this.#run()
  }
  #run() {}

  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      this.#handler = {
        onFulfilled,
        onRejected,
        resolve,
        reject,
      }
      this.#run()
    })
  }
}

注意

由于then方法可以被多次调用,因此#handler属性存放的应该是一个数组。

示例图

handler-arr

js
class MyPromise {
  #handlers = []

  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      this.#handlers.push({
        onFulfilled,
        onRejected,
        resolve,
        reject,
      })
      this.#run()
    })
  }
}

实现#run方法:

  1. 过滤掉 pending 状态。
  2. #handlers数组中依次取出每一项。
    • 如果当前状态是 fulfilled,则判断onFulfilled是否是函数,是则执行,并传入结果。
    • 如果当前状态是 rejected,则判断onRejected是否是函数,是则执行,并传入结果。
js
class MyPromise {
  #run() {
    if (this.#state === PENDING) return
    while (this.#handlers.length) {
      const { onFulfilled, onRejected, resolve, reject } =
        this.#handlers.shift()
      if (this.#state === FULFILLED) {
        if (typeof onFulfilled === 'function') {
          onFulfilled(this.#result)
        }
      } else {
        if (typeof onRejected === 'function') {
          onRejected(this.#result)
        }
      }
    }
  }
}

至此,then 的回调(onFulfilled、onRejected)执行时机就解决了。

then 的返回值

情况一:对应的回调不是函数。

  1. 状态是 fulfilled,但是传入的onFulfilled不是函数。
  2. 状态是 rejected,但是传入的onRejected不是函数。
js
p.then(111, (err) => {})
js
p.then((res) => {}, 222)

这种情况下then返回的 Promise 应该和 p 的结果一致。

js
#run() {
  if (this.#state === PENDING) return
  while (this.#handlers.length) {
    const { onFulfilled, onRejected, resolve, reject } = this.#handlers.shift()
    if (this.#state === FULFILLED) {
      if (typeof onFulfilled === 'function') {
        onFulfilled(this.#result)
      } else {
        resolve(this.#result) 
      }
    }
    // ...
  }
}
p.then(111, (err) => {})
js
#run() {
  if (this.#state === PENDING) return
  while (this.#handlers.length) {
    const { onFulfilled, onRejected, resolve, reject } = this.#handlers.shift()
    if (this.#state === FULFILLED) {
      // ...
    } else {
      if (typeof onRejected === 'function') {
        onRejected(this.#result)
      } else {
        reject(this.#result) 
      }
    }
  }
}
p.then((res) => {}, 222)

情况二:传入的onFulfilledonRejected是函数。需要考虑函数执行的结果。因此包裹在try catch中执行。

js
#run() {
  // ...
  if (this.#state === FULFILLED) {
    if (typeof onFulfilled === 'function') {
      try {
        const res = onFulfilled(this.#result)
        resolve(res)
      } catch (error) {
        reject(error)
      }
    }
  }
}
js
#run() {
  // ...
  if (this.#state === REJECTED) {
    if (typeof onRejected === 'function') {
      try {
        const res = onRejected(this.#result)
        resolve(res)
      } catch (error) {
        reject(error)
      }
    }
  }
}

有重复代码,提取成一个#runOne函数。

js
#runOne(callback, resolve, reject) {
  if (typeof callback !== 'function') {
    const handler = this.#state === FULFILLED ? resolve : reject
    handler(this.#result)
    return
  }
  try {
    const data = callback(this.#result)
    resolve(data)
  } catch (error) {
    reject(error)
  }
}
js
#run() {
  if (this.#state === FULFILLED) {
    this.#runOne(onFulfilled, resolve, reject) 
  } else {
    // ...
  }
}
js
#run() {
  if (this.#state === FULFILLED) {
    // ...
  } else {
    this.#runOne(onRejected, resolve, reject) 
  }
}

情况三:回调的返回结果是一个 Promise。

  1. #runOne方法中的try catch中的 callback()返回的 data 是一个 Promise。
  2. 如何判断返回值 data 是一个 Promise?
  3. 运行then的回调callback()应该放在微队列中。

PromiseLike(thenable)和微队列

  1. 判断返回值 data 是一个 Promise。
    • 只需要判断 data 是否符合Promises/A+规范。
    • 可以通过判断 data 中是否含有 then 属性,且该属性是一个 function。

定义一个辅助函数#isPromise来判断是否是 Promise。并在try catch中获取到 callback()的返回值 data 后进行判断。

js
#isPromise(value) {
  if (
    value !== null && (typeof value === 'object' ||
    typeof value === 'function')
  ) {
    return typeof value.then === 'function'
  }
  return false
}
js
#runOne(callback, resolve, reject) {
  // ...
  try {
    const data = callback(this.#result)
    if (this.#isPromise(data)) {
      data.then(resolve, reject)
    } else {
      resolve(data)
    }
  } catch (error) {
    reject(error)
  }
}
js
const p = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(1)
  }, 1000)
})
p.then((res) => {
  console.log(res)
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(res * 2)
    }, 1000)
  })
}).then((res) => {
  console.log(res)
})
// 1000ms后输出:1
// 再1000ms后输出:2
js
function delay(duration = 1000) {
  return new MyPromise((resolve) => {
    setTimeout(resolve, duration)
  })
}

async function test() {
  await delay()
  console.log(111)
}
test()
// 1000ms后输出:111
  1. callback()方法加入到微队列中执行。

定义一个辅助函数#runMicroTask,该函数接收一个 func 入参,作用是将 func 添加到微队列。

注意

微队列是环境赋予的能力,比如 node 环境中的事件循环,浏览器环境中的事件循环。事件循环是环境能力而非语言能力。脱离了环境提供的 API 的话,是无法用标准库中的代码模拟微队列的。

方案

  1. node 环境中,可以通过调用process.nextTick(func)方法将 func 添加到 node 事件循环的微队列中。
  2. 浏览器环境中:
    2.1 使用MutationObserver API。通过调用构造函数MutationObserver(func),创建并返回一个新的 MutationObserver 它会在指定的 DOM 发生变化时被调用。并且会将 func 放入微队列。
    2.2 使用queueMicrotask()方法。
js
#runMicroTask(func) {
  if (typeof process === 'object' && typeof process.nextTick === 'function') {
    process.nextTick(func)
  } else if (typeof MutationObserver === 'function') {
    const ob = new MutationObserver(func)
    const textNode = document.createTextNode('1')
    ob.observe(textNode, {
      characterData: true,
    })
    textNode.data = '2'
  } else {
    setTimeout(func, 0)
  }
}
js
#runMicroTask(func) {
  if (typeof process === 'object' && typeof process.nextTick === 'function') {
    process.nextTick(func)
  } else if (typeof queueMicrotask === 'function') {
    queueMicrotask(func)
  } else {
    setTimeout(func, 0)
  }
}
js
function runOne(callback, resolve, reject) {
  this.#runMicroTask(() => {
    // ...
  })
}
js
setTimeout(() => {
  console.log(1)
}, 0)

new MyPromise((resolve) => {
  resolve(2)
}).then((res) => {
  console.log(res)
})

console.log(3)
// 3
// 2
// 1

这样就完整的实现了一个符合 Promises/A+ 规范的 Promise 代码。

完整代码
js
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class MyPromise {
  #state = PENDING
  #result = undefined
  #handlers = []

  constructor(executor) {
    const resolve = (res) => {
      this.#changeState(FULFILLED, res)
    }
    const reject = (err) => {
      this.#changeState(REJECTED, err)
    }
    try {
      executor(resolve, reject)
    } catch (error) {
      reject(error)
    }
  }
  #changeState(state, result) {
    if (this.#state !== PENDING) return
    this.#state = state
    this.#result = result
    this.#run()
  }
  #isPromise(value) {
    if (
      value !== null &&
      (typeof value === 'object' || typeof value === 'function')
    ) {
      return typeof value.then === 'function'
    }
    return false
  }
  #runMicroTask(func) {
    if (typeof process === 'object' && typeof process.nextTick === 'function') {
      process.nextTick(func)
    } else if (typeof queueMicrotask === 'function') {
      queueMicrotask(func)
    } else {
      setTimeout(func, 0)
    }
  }
  // use MutationObserver
  // #runMicroTask(func) {
  //   if (typeof process === 'object' && typeof process.nextTick === 'function') {
  //     process.nextTick(func)
  //   } else if (typeof MutationObserver === 'function') {
  //     const ob = new MutationObserver(func)
  //     const textNode = document.createTextNode('1')
  //     ob.observe(textNode, {
  //       characterData: true,
  //     })
  //     textNode.data = '2'
  //   } else {
  //     setTimeout(func, 0)
  //   }
  // }
  #runOne(callback, resolve, reject) {
    this.#runMicroTask(() => {
      if (typeof callback !== 'function') {
        const handler = this.#state === FULFILLED ? resolve : reject
        handler(this.#result)
        return
      }
      try {
        const data = callback(this.#result)
        if (this.#isPromise(data)) {
          data.then(resolve, reject)
        } else {
          resolve(data)
        }
      } catch (error) {
        reject(error)
      }
    })
  }
  #run() {
    if (this.#state === PENDING) return
    while (this.#handlers.length) {
      const { onFulfilled, onRejected, resolve, reject } =
        this.#handlers.shift()
      if (this.#state === FULFILLED) {
        this.#runOne(onFulfilled, resolve, reject)
      } else {
        this.#runOne(onRejected, resolve, reject)
      }
    }
  }

  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      this.#handlers.push({
        onFulfilled,
        onRejected,
        resolve,
        reject,
      })
      this.#run()
    })
  }
}

提示

ES6 中的 Promise 不仅满足 Promises/A+ 规范,还包含了很多内容,比如 catchfinallyresolve/rejectallanyrace 等等。

扩展

尝试实现 catch()、finally()、resolve()、reject()方法。

1. Promise.prototype.catch()

Promise 实例的 catch() 方法用于注册一个在 promise 被拒绝时调用的函数。它会立即返回一个等效的 Promise 对象,这可以允许你链式调用其他 promise 的方法。此方法是 Promise.prototype.then(undefined, onRejected) 的一种简写形式。

catch() 方法内部会调用当前 promise 对象的 then() 方法,并将 undefinedonRejected 作为参数传递给 then()。该调用的返回值直接被返回。

js
class MyPromise {
  // ...
  catch(onRejected) {
    return this.then(undefined, onRejected)
  }
}
js
const p = new MyPromise((resolve, reject) => {
  reject(123)
})

p.catch((err) => {
  console.log(err)
})

// 123

2. Promise.prototype.finally()

Promise 实例的 finally() 方法用于注册一个在无论 promise 成功或者失败时都会调用的函数。它会立即返回一个等效的 Promise 对象,这可以允许你链式调用其他 promise 方法。

这可以让你避免在 promise 的 then()catch() 处理器中重复编写代码。

finally() 方法类似于调用 then(onFinally, onFinally)。然而,有几个不同之处:

  • 创建内联函数时,你可以只将其传入一次,而不是强制声明两次或为其创建变量。
  • onFinally 回调函数不接收任何参数。这种情况恰好适用于你不关心失败原因或成功数值的情况,因此无需提供它。
  • finally() 调用通常是透明的,不会更改原始 promise 的状态。例如:
    • Promise.resolve(2).then(() => 77, () => {}) 不同,它返回一个最终结果为值 77 的 promise,而 Promise.resolve(2).finally(() => 77) 返回一个最终结果为值 2 的 promise。
    • 类似地,与 Promise.reject(3).then(() => {}, () => 88) 不同,它返回一个最终结果为值 88 的 promise,而 Promise.reject(3).finally(() => 88) 返回一个最终以原因 3 拒绝的 promise。
js
class MyPromise {
  finally(onFinally) {
    return this.then(
      (res) => {
        onFinally()
        return res
      },
      (err) => {
        onFinally()
        throw err
      }
    )
  }
}
js
const p = new MyPromise((resolve, reject) => {
  resolve(123)
})

p.finally(() => {
  console.log('finally')
})

// finally

3. Promise.resolve()

Promise.resolve() 静态方法将给定的值转换为一个 Promise。如果该值本身就是一个 Promise,那么该 Promise 将被返回;如果该值是一个 thenable 对象,Promise.resolve() 将调用其 then() 方法及其两个回调函数;否则,返回的 Promise 将会以该值兑现(fulfill)。

js
class MyPromise {
  static resolve(value) {
    if (value instanceof MyPromise) return value
    let _resolve, _reject
    const p = new MyPromise((resolve, reject) => {
      _resolve = resolve
      _reject = reject
    })
    if (p.#isPromise(value)) {
      value.then(_resolve, _reject)
    } else {
      _resolve(value)
    }
    return p
  }
}
js
const p = new MyPromise((resolve, reject) => {
  resolve(123)
})

console.log(MyPromise.resolve(p) === p)

MyPromise.resolve(p).then((res) => {
  console.log('fulfilled', res)
})

MyPromise.resolve(111).then((res) => {
  console.log(res)
})

// true
// fulfilled 123
// 111

4. Promise.reject()

Promise.reject() 静态方法返回一个已拒绝(rejected)的 Promise 对象,拒绝原因为给定的参数。

js
class MyPromise {
  static reject(reason) {
    return new MyPromise((resolve, reject) => {
      reject(reason)
    })
  }
}
js
MyPromise.reject(12345).catch((err) => {
  console.log(err)
})

// 12345