Promise

  • 使用Promise时,有三种状态

    • pending:进行中
    • fulfilled:已成功
    • reject:已失败

      基本使用

  • Promise是一个构造函数,用来生成Promise实例

    • 构造函数有两个参数,分别是resolvereject。它们是两个函数,由 JS 引擎提供
      • resolve函数的作用是,将Promise对象的状态从pending变为fulfilled。在异步操作成功时调用,并将异步操作的结果,作为参数传递出去
      • reject函数的作用是,将Promise对象的状态从pending变为reject。在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去 ```javascript // 案例一 const promise = new Promise(function(resolve, reject) { // … some code

    if (/ 异步操作成功 /){ resolve(value); } else { reject(error); } }); ```

    Promise 实例上的方法

  • Promise.prototype.then((result) => {}, (reason) => {})

    • 第一个参数是fulfilled状态的回调函数
    • 第二个参数是rejected状态的回调函数
    • 该方法返回的是一个新的Promise实例,方便链式调用 ```javascript // 案例一 let random = Math.floor(Math.random() * 100); const promise = new Promise((resolve, reject) => { if (random % 2 === 0) { resolve(‘偶数’) console.log(123) // resolve()执行后,之后的代码还是会执行 } else { reject(‘奇数’) console.log(456) // reject()执行后,之后的代码还是会执行 } })

// 链式调用 promise.then( (result) => { console.log(result); return result + ‘1’; }, (reason) => { console.log(reason); return reason + ‘1’; }, ).then( (result) => console.log(result), // 上一个then里,return后都到这个回调函数里 (reason) => console.log(reason) // 这个函数不会被用到 )

  1. - `Promise.prototype.catch(error => {})`
  2. - 作用:`.then(null, rejection)``.then(undefined, rejection)`的别名,用于指定发生错误时的回调函数
  3. - `catch()`方法返回的还是一个 Promise 对象,因此后面还可以接着调用`then()`方法
  4. ```javascript
  5. // 案例二
  6. let random = Math.floor(Math.random() * 100);
  7. const promise = new Promise((resolve, reject) => {
  8. if (random % 2 === 0) {
  9. resolve('偶数')
  10. } else {
  11. reject('奇数')
  12. }
  13. })
  14. promise.then(
  15. (result) => { console.log(result); throw new Error(result); },
  16. ).then(
  17. (result) => console.log(result),
  18. ).catch(err => console.log(err))
  • Promise.prototype.finally(()=>{})
    • 不管 Promise 对象最后状态如何,都会执行的操作
    • finally本质上是then方法的特例
    • 该方法是 ES2018 引入标准的 ```javascript const promise = new Promise((resolve, reject)=> …);

promise .then(result => {···}) .catch(error => {···}) .finally(() => {···});

  1. ```javascript
  2. // Promise.prototype.finally的实现
  3. Promise.prototype.finally = function (callback) {
  4. let P = this.constructor;
  5. return this.then(
  6. value => P.resolve(callback()).then(() => value),
  7. reason => P.resolve(callback()).then(() => { throw reason })
  8. );
  9. };

Promise 构造函数上的方法

  • Promise.resolve(any)
    • 作用:将现有对象any转为 Promise 对象,状态为fulfilled
    • 参数any
      • 参数是一个 Promise 实例
        • 将不做任何修改、原封不动地返回这个实例
      • 参数是一个thenable对象(指的是具有then方法的对象)
        • 将这个对象转为 Promise 对象,然后就立即执行thenable对象then()方法
      • 参数不是具有then()方法的对象,或根本就不是对象
        • 返回一个新的 Promise 对象,状态为fulfilled
      • 不带有任何参数
        • 直接返回一个fulfilled状态的 Promise 对象
          1. // 案例一
          2. Promise.resolve('foo')
          3. // 等价于
          4. new Promise(resolve => resolve('foo'))
          ```javascript // 案例二:有then方法的对象 let thenable = { then: function(resolve, reject) { resolve(42); } };

let p1 = Promise.resolve(thenable); p1.then(function (value) { console.log(value); // 42 });

  1. - `Promise.reject(reason)`
  2. - 返回一个新的 Promise 实例,该实例的状态为`rejected`
  3. - 参数`any`
  4. - 参数是一个 Promise 实例
  5. - 将不做任何修改、原封不动地返回这个实例
  6. - 参数是一个`thenable对象`(指的是具有`then`方法的对象)
  7. - 将这个对象转为 Promise 对象,然后就立即执行`thenable对象``then()`方法
  8. - 参数不是具有`then()`方法的对象,或根本就不是对象
  9. - 返回一个新的 Promise 对象,状态为`rejected`
  10. - 不带有任何参数
  11. - 直接返回一个`rejected`状态的 Promise 对象
  12. <a name="Yecff"></a>
  13. ## 手写 Promise
  14. Promise 的实现标准是[ Promise/A+](https://promisesaplus.com/),该标准只规定了 then,没有规定 all、catch、race,也没有规定构造函数<br />Promise/A+ 的实现参考文献汇总 [Promise Implementations](https://promisesaplus.com/implementations)<br />我们从 promise 的使用方式开始,一步步实现 promise
  15. <a name="uXHK1"></a>
  16. ### new Promise()
  17. ```javascript
  18. // new MyPromise()
  19. new MyPromise((resolve, reject) => {
  20. resolve(123)
  21. // reject(456)
  22. })

一个 promise 实例必须是这三种状态其中之一:pending、fulfilled、rejected
2.1.1 当 promise 实例状态是 pending 时
2.1.1.1 promise 实例状态可以转变为 fulfilled、rejected 其中之一
2.1.2 当 promise 实例状态是 fulfilled 时
2.1.2.1 promise 实例不可以转变为其他状态
2.1.2.2 promise 实例必须有一个 value,且不可以改变
2.1.3 当 promise 实例状态是 rejected 时
2.1.2.1 promise 实例不可以转变为其他状态
2.1.2.2 promise 实例必须有一个 reason,且不可以改变

  1. class MyPromise {
  2. // Promise 三个状态
  3. static PENDING = 0
  4. static FULLFILLED = 1
  5. static REJECTED = 2
  6. transition(state, value) {
  7. // 状态 state 只能从 PENDING ——> FULLFILLED 或 PENDING ——> REJECTED
  8. if (this.state === state
  9. || this.state === MyPromise.FULLFILLED
  10. || this.state === MyPromise.REJECTED) {
  11. return false
  12. }
  13. this.state = state
  14. this.value = value
  15. return true
  16. }
  17. // 实现 resolve
  18. resolve(data) {
  19. // 状态变化: PENDING ——> FULLFILLED
  20. this.transition(MyPromise.FULLFILLED, data)
  21. }
  22. // 实现 reject
  23. reject(reason) {
  24. // 状态变化: PENDING ——> REJECTED
  25. this.transition(MyPromise.REJECTED, reason)
  26. }
  27. constructor(executor) {
  28. this.state = MyPromise.PENDING
  29. this.value = undefined
  30. try {
  31. executor(this.resolve.bind(this), this.reject.bind(this))
  32. } catch(error) {
  33. this.state = MyPromise.REJECTED
  34. console.error(error)
  35. }
  36. }
  37. }
  1. const promise1 = new MyPromise((resolve, reject) => {
  2. resolve(123)
  3. })
  4. const promise2 = new MyPromise((resolve, reject) => {
  5. reject(456)
  6. })
  7. // 能被内部 try catch 捕获
  8. const promise3 = new MyPromise((resolve, reject) => {
  9. throw new Error('I am Error')
  10. })
  11. // 不能被内部 try catch 捕获
  12. const promise4 = new MyPromise((resolve, reject) => {
  13. setTimeout(() => {
  14. throw new Error('I am Error')
  15. })
  16. })

:::info 为什么 MyPromise 中的异步事件 setTimeout 报错,MyPromise 内部的 trycatch 捕获不到?
答:因为 try catch 的执行是同步的,异步事件执行出错 catch 捕获不到 :::

  1. try {
  2. setTimeout(()=>{
  3. throw new Error('error')
  4. })
  5. } catch (error) {
  6. console.error(error)
  7. }
  1. class MessageCenter {
  2. constructor() {
  3. this.center = {}
  4. }
  5. // 注册事件
  6. subscribe(type, callback) {
  7. if (!this.center[type]) this.center[type] = [callback]
  8. else this.center[type].push(callback)
  9. }
  10. // 注销事件
  11. unSubscribe(type, callback) {
  12. const index = this.center[type].indexOf(callback)
  13. if (index !== -1) this.center[type].splice(index, 1)
  14. }
  15. // 发布消息
  16. public(type, ...args) {
  17. if (this.center[type]) this.center[type].forEach(callback => callback(...args))
  18. }
  19. }
  20. const messageCenter = new MessageCenter()
  21. try {
  22. function changeHandler(...args) {
  23. console.log(...args)
  24. throw Error('I am Error')
  25. }
  26. messageCenter.subscribe('change', changeHandler)
  27. } catch (error) {
  28. console.error(error)
  29. }
  30. messageCenter.public('change', 456)

then()

  1. new Promise((resolve, reject) => {
  2. resolve(123)
  3. })
  4. .then(
  5. data => console.log(data),
  6. )
  7. .then(
  8. 'data',
  9. reason => console.error(reason)
  10. )
  11. .then(
  12. data => console.log(data),
  13. reason => console.error(reason)
  14. )

根据 Promise/A+ 的 then 方法标准
一个 Promise 实例提供一个 then 方法,用于接收它 现在或最终的 值 或 错误原因

  1. promise.then(onFulfilled, onRejected)

2.2.1 onFulfilledonRejected都是可选参数

  1. 如果 onFulfilled不是函数,就忽略
  2. 如果 onRejected不是函数,就忽略

2.2.2 如果onFulfilled是函数

  1. 它必须在 promise 状态变为 fulfilled 时才能执行,并且将 promise的值作为它第一个参数
  2. 在 promise 状态变为 fulfilled 之前,它不能被执行
  3. 它只能被执行一次

2.2.3 如果onRejected是函数

  1. 它必须在 promise 状态变为 rejected 时才能执行,并且将 promise的 错误原因 作为它第一个参数
  2. 在 promise 状态变为 rejected 之前,它不能被执行
  3. 它只能被执行一次

2.2.4 当执行上下文栈仅包含平台代码(platform code)时,onFulfilledonRejected才能被执行 :::info platform code 指的是:JS 解析引擎、环境 和 promise 的实现代码。
在实践中,这个要求能保证在事件循环执行到 then函数时,onFulfilledonRejected的执行是异步的,并使用一个新的栈。
这个 platform code 的实现可以用宏任务机制,比如[setTimeout](https://html.spec.whatwg.org/multipage/webappapis.html#timers)setImmediate,也可以用微任务机子来实现,比如[MutationObserver](https://dom.spec.whatwg.org/#interface-mutationobserver)[process.nextTick](https://nodejs.org/api/process.html#process_process_nexttick_callback) ::: 2.2.5 onFulfilledonRejected必须作为函数被调用
2.2.6 then方法可以在同一个 promise 实例中,多次调用

  1. promise的状态是 fulfilled,所有 onFulFilled回调函数,必须按照它们各自所在then函数的顺序,依次执行
  2. promise的状态是 rejected,所有 onRejected回调函数,必须按照它们各自所在then函数的顺序,依次执行

2.2.7 then必须返回一个 promise 实例

  1. 不论是 onFulfilled还是 onRejected返回了一个值 x,执行 Promise 决议程序,即[[Resolve]](promise2, x)
  2. 不论是 onFulfilled还是 onRejected抛出一个异常 epromise2必须以 e作为错误原因变为 rejected 状态
  3. 如果 onFulfilled不是函数,并且 promise1状态为 fulfilled。promise2必须以 promise1的value 作为 value,并变为 fulfilled 状态
  4. 如果 onRejected不是函数,并且 promise1状态是 rejected。promise2必须以 promise2的 reason 作为 reason,并变为 rejected 状态
    1. promise2 = promise1.then(onFulfilled, onRejected);
    :::info Promise Resolution Procedure:Promise 决议程序是一个以输入为一个 promise 和 一个 value 的抽象操作,我们用 [[Resolve]](promise, x)来描述这一操作。如果 x是一个 thenable 对象,它会假设 x的行为有点像一个 promise 实例,并试图让 promise采用 x 的状态。否则,它将以 x作为 promise的 value,并将 promise状态变为 fulfilled。

在执行 [[Resolve]](promise, x)时,按照以下步骤执行
2.3.1 如果 promisex指向同一个对象,那么以 TypeError作为理由拒绝 promise
2.3.2 如果 x是一个 promise,采用它的状态

  1. 如果 x是 pending 状态,promise必须保留 pending 状态直到,x状态变为 fulfilled/rejected
  2. 如果 x是 fulfilled 状态,将 promise状态变为 fulfilled,并返回相同的 value
  3. 如果 x是 rejeceted 状态,将 promise状态变为 rejected,并返回相同的 reason

2.3.3 否则,如果 x是一个对象或函数

  1. then等于 x.then
  2. 如果检索 x.then这个属性而导致抛出异常 e,则将 promise状态变为 rejected,并以 e作为 reason
  3. 如果 then是一个函数,那么将它的 this指向 x,第一个参数为 resolvePromise,第二个参数为 rejectPromise
    1. 如果 resolvePromie以一个y作为 value 被调用,则执行 [[Resolve]](promise, y)
    2. 如果 rejectPromise以一个 r作为 reason 被调用,以 r为理由返回 rejected 状态的 promise
    3. 如果resolvePromierejectPromise同时被调用了,或者对其中一个多次调用,那么以第一次调用为主,忽略之后的调用
    4. 如果调用 then方法抛出异常 e
      1. 如果resolvePromierejectPromise被调用过了,忽略它
      2. 否则,以 e为理由返回 rejected 状态的 promise
  4. 如果 then 不是一个函数,则将promise状态变为 fulfilled,并以 x为值

2.3.4 如果 x 不是对象或函数,则将promise状态变为 fulfilled,并以 x为值 ::: 按照 Promise/A+ 规范的要求,我们先来实现 then 函数的一部分功能,第 2.2.1 ~ 2.2.6 点,测试用例如下

  1. const promise1 = new MyPromise((resolve, reject) => {
  2. reject(123)
  3. })
  4. promise1.then(
  5. data => console.log(data, 1),
  6. reason => {
  7. console.log(reason + 'error1')
  8. return 555
  9. }
  10. )
  11. promise1.then(
  12. 'data',
  13. reason => console.log(reason + 'error2')
  14. )
  1. class MyPromise {
  2. // Promise 三个状态
  3. static PENDING = 0
  4. static FULLFILLED = 1
  5. static REJECTED = 2
  6. transition(state, value) {
  7. // 状态 state 只能从 PENDING ——> FULLFILLED 或 PENDING ——> REJECTED
  8. if (this.state === state
  9. || this.state === MyPromise.FULLFILLED
  10. || this.state === MyPromise.REJECTED) {
  11. return false
  12. }
  13. this.state = state
  14. this.value = value
  15. return true
  16. }
  17. // 实现 resolve
  18. resolve = (data) => {
  19. // 状态变化: PENDING ——> FULLFILLED
  20. const stateChange = this.transition(MyPromise.FULLFILLED, data)
  21. if (stateChange) {
  22. while (this.onFulfilledQueue.length) {
  23. const onFulfilled = this.onFulfilledQueue.shift()
  24. onFulfilled(this.value)
  25. }
  26. }
  27. }
  28. // 实现 reject
  29. reject = (reason) => {
  30. // 状态变化: PENDING ——> REJECTED
  31. const stateChange = this.transition(MyPromise.REJECTED, reason)
  32. if (stateChange) {
  33. while (this.onRejectedQueue.length) {
  34. const onRejected = this.onRejectedQueue.shift()
  35. onRejected(this.value)
  36. }
  37. }
  38. }
  39. constructor(executor) {
  40. this.state = MyPromise.PENDING
  41. this.value = undefined
  42. this.onFulfilledQueue = []
  43. this.onRejectedQueue = []
  44. try {
  45. // executor 的执行时机需要比 then 晚,then 方法才能先注册 onFulfilled, onRejected 事件
  46. // 这里使用 setTimeout 来将 executor 放入宏任务队列中等待执行
  47. // 当主线程中的 then 方法执行完成后,再触发 executor
  48. const timer = setTimeout(() => {
  49. executor(this.resolve, this.reject)
  50. clearTimeout(timer)
  51. })
  52. } catch (error) {
  53. this.state = MyPromise.REJECTED
  54. console.error(error)
  55. }
  56. }
  57. then(onFulfilled, onRejected) {
  58. if (typeof onFulfilled === 'function') this.onFulfilledQueue.push(onFulfilled)
  59. if (typeof onRejected === 'function') this.onRejectedQueue.push(onRejected)
  60. }
  61. }

:::warning ⭐注意:上面代码用了 setTimeout 来让 executor 执行时机比 then 晚,但这会导致两个问题。

  1. try catch 是同步的,不能捕获 executor 执行时抛出的错误。怎么解决我们下文再说。
  2. 原生 Promise 的 executor 是同步执行的,而我们实现的 executor 现在变成异步执行的 ::: 目前,由于 then 没有返回任何东西,如果我们像下面这样调用 then 函数的话,会有问题。 ```javascript const promise1 = new MyPromise((resolve, reject) => { reject(123) })

const promise2 = promise1.then( ‘data’, reason => reason + ‘error’ )

promise2.then( data => console.log(data), reason => console.error(reason) )

  1. 接下来,我们需要实现 Promise/A+ 规范的第 2.2.7 点。
  2. > Promise then 方法链式调用,为什么不能直接返回 this
  3. > 答:根据 Promise/A+ 规范的第 2.2.22.2.3 点,`onFulFilled``onRejected`只能在 promise 实例状态由 pending 变为 fulfilled/rejected 时,才能被调用。直接返回 thispromise 状态不对
  4. ```javascript
  5. class MyPromise {
  6. // Promise 三个状态
  7. static PENDING = 0
  8. static FULLFILLED = 1
  9. static REJECTED = 2
  10. transition(state, value) {
  11. // 状态 state 只能从 PENDING ——> FULLFILLED 或 PENDING ——> REJECTED
  12. if (this.state === state
  13. || this.state === MyPromise.FULLFILLED
  14. || this.state === MyPromise.REJECTED) {
  15. return false
  16. }
  17. this.state = state
  18. this.value = value
  19. return true
  20. }
  21. // 实现 resolve
  22. resolve = (data) => {
  23. // 状态变化: PENDING ——> FULLFILLED
  24. const stateChange = this.transition(MyPromise.FULLFILLED, data)
  25. if (stateChange) {
  26. while (this.onFulfilledQueue.length) {
  27. const onFulfilled = this.onFulfilledQueue.shift()
  28. onFulfilled(this.value)
  29. }
  30. }
  31. }
  32. // 实现 reject
  33. reject = (reason) => {
  34. // 状态变化: PENDING ——> REJECTED
  35. const stateChange = this.transition(MyPromise.REJECTED, reason)
  36. if (stateChange) {
  37. while (this.onRejectedQueue.length) {
  38. const onRejected = this.onRejectedQueue.shift()
  39. onRejected(this.value)
  40. }
  41. }
  42. }
  43. constructor(executor) {
  44. this.state = MyPromise.PENDING
  45. this.value = undefined
  46. this.onFulfilledQueue = []
  47. this.onRejectedQueue = []
  48. try {
  49. executor(this.resolve, this.reject)
  50. } catch (error) {
  51. this.state = MyPromise.REJECTED
  52. this.reject(error)
  53. }
  54. }
  55. then(onFulfilled, onRejected) {
  56. if (typeof onFulfilled !== 'function') onFulfilled = data => data
  57. if (typeof onRejected !== 'function') onRejected = reason => { throw reason }
  58. const promise1 = this
  59. // return 的就是 promise2
  60. const promise2 = new MyPromise((resolve, reject) => {
  61. // 1. executor 执行时机要比 then 晚
  62. // 2. then 中的回调函数执行时机要比 executor 晚
  63. const onFulfilledTask = () => {
  64. const timer = setTimeout(() => {
  65. try {
  66. const x = onFulfilled(promise1.value)
  67. MyPromise.PromiseResolution(promise2, x, resolve, reject)
  68. clearTimeout(timer)
  69. } catch (error) {
  70. reject(error)
  71. clearTimeout(timer)
  72. }
  73. })
  74. }
  75. const onRejectedTask = () => {
  76. const timer = setTimeout(() => {
  77. try {
  78. const x = onRejected(promise1.value)
  79. MyPromise.PromiseResolution(promise2, x, resolve, reject)
  80. clearTimeout(timer)
  81. } catch (error) {
  82. reject(error)
  83. clearTimeout(timer)
  84. }
  85. })
  86. }
  87. try {
  88. if (promise1.state === MyPromise.FULLFILLED) {
  89. onFulfilledTask()
  90. } else if (promise1.state === MyPromise.REJECTED) {
  91. onRejectedTask()
  92. } else if (promise1.state === MyPromise.PENDING) {
  93. promise1.onFulfilledQueue.push(onFulfilledTask)
  94. promise1.onRejectedQueue.push(onRejectedTask)
  95. }
  96. } catch (error) {
  97. // 按照 Promise/A+ 的 2.2.7 的 b 点
  98. // 不论是 onFulfilled 还是 onRejected 抛出一个异常 e,promise2 必须以 e 作为错误原因变为 rejected 状态
  99. reject(error)
  100. }
  101. })
  102. return promise2
  103. }
  104. // Promise Resolution Procedure
  105. static PromiseResolution(promise2, x, resolve, reject) {
  106. if (promise2 === x) reject(new TypeError())
  107. else if (x === null) resolve(x)
  108. // 不论 x 是 promise 实例,还是用户自定义 thenable 对象/函数,都执行如下逻辑
  109. else if (['object', 'function'].includes(typeof x)) {
  110. let then
  111. try {
  112. then = x.then
  113. } catch (error) {
  114. reject(error)
  115. }
  116. if (typeof then !== 'function') {
  117. resolve(x)
  118. } else {
  119. // 根据 2.3.3 的 第 iii 点,使用 firstCall
  120. // 使得只执行第一次 resolvePromise/rejectPromise 调用
  121. let firstCall = true
  122. try {
  123. then.call(x,
  124. // resolvePromise
  125. (y) => {
  126. if (!firstCall) return
  127. firstCall = false
  128. MyPromise.PromiseResolution(promise2, y, resolve, reject)
  129. },
  130. // rejectPromise
  131. (r) => {
  132. if (!firstCall) return
  133. firstCall = false
  134. reject(r)
  135. })
  136. }
  137. catch (error) {
  138. if (!firstCall) return
  139. reject(error)
  140. }
  141. }
  142. }
  143. else {
  144. resolve(x)
  145. }
  146. }
  147. }
  148. module.exports = MyPromise

测试 then 方法是否合乎标准

使用 promises-aplus-tests 库,测试手写的 Promise 是否合乎 Promise/A+ 规范

  1. // 添加 deferred 方法,promises-aplus-tests 会去调用 MyPromise 执行测试用例
  2. MyPromise.deferred = function () {
  3. var result = {};
  4. result.promise = new MyPromise(function (resolve, reject) {
  5. result.resolve = resolve;
  6. result.reject = reject;
  7. });
  8. return result;
  9. }
  1. {
  2. "name": "MyPromise",
  3. "version": "1.0.0",
  4. "description": "",
  5. "main": "index.js",
  6. "scripts": {
  7. "test": "promises-aplus-tests index.js"
  8. },
  9. "keywords": [],
  10. "author": "",
  11. "license": "ISC",
  12. "devDependencies": {
  13. "promises-aplus-tests": "^2.1.2"
  14. }
  15. }

执行完 npm run test,看到类似下面情况,说明测试用例都通过了
image.png