Skip to content

一. 异步代码的困境


  • ES6 出来之后,有很多关于 Promise 的讲解、文章,也有很多经典的书籍讲解 Promise

    • 虽然等你学会 Promise 之后,会觉得 Promise 不过如此
    • 但是在初次接触的时候都会觉得这个东西不好理解
  • 那么这里从一个实际的例子来作为切入点:

    • 我们调用一个函数,这个函数中发送网络请求(我们可以用定时器来模拟)

    • 如果发送网络请求成功了,那么告知调用者发送成功,并且将相关数据返回过去

    • 如果发送网络请求失败了,那么告知调用者发送失败,并且告知错误信息

      js
      function requestData(url, successCallback, failureCallback) {
        setTimeout(() => {
          if (url === 'http://later.org') {
            successCallback('发送成功了')
          } else {
            failureCallback('请求失败了')
          }
        }, 1000)
      }
      
      requestData('http://aaa/org', function(successMsg) {
        console.log(successMsg)
      }, function(failureMsg) {
        console.log(failureMsg)
      }) // 请求失败了
      
      requestData('http://later.org', function(successMsg) {
        console.log(successMsg)
      }, function(failureMsg) {
        console.log(failureMsg)
      }) // 发送成功了

二. 认识 Promise 作用


  • 在上面的解决方案中,我们确确实实可以解决请求函数得到结果之后,获取到对应的回调,但是它存在两个主要的问题:

    • 第一,我们需要自己来设计回调函数、回调函数的名称、回调函数的使用等
    • 第二,对于不同的人、不同的框架设计出来的方案是不同的,那么我们必须耐心去看别人的源码或者文档,以便可以理解它这个函数到底怎么用
  • Promise 是通过链式调用的方式让回调函数变得可控,统一处理异步的方案

  • 我们来看一下 PromiseAPI 是怎么样的:

    • Promise 是一个类,可以翻译成:承诺、许诺、期约
    • 当我们需要的时候,给予调用者一个承诺:待会儿我会给你回调数据时,就可以创建一个 Promise 的对象
    • 在通过 new 创建 Promise 对象时,我们需要传入一个回调函数,我们称之为 executor(执行者)
      • 这个回调函数会被立即执行,并且给传入另外两个回调函数 resolvereject
      • 当我们调用 resolve 回调函数时,会执行 Promise 对象的 then 方法传入的回调函数
      • 当我们调用 reject 回调函数时,会执行 Promise 对象的 catch 方法传入的回调函数
  • 那么有了Promise,我们就可以将之前的代码进行重构了:

    js
    function request(url) {
      const promise = new Promise((resolve, reject) => {
        setTimeout(() => {
          if (url === 'http://later.org') {
            resolve('发送成功了')
          } else {
            reject('请求失败了')
          }
        }, 1000)
      })
      return promise
    }
    
    request('http://aaa/org').then( res => {
      console.log(res)
    }).catch( err => {
      console.log(err)
    }) // 请求失败了
    
    request('http://later.org').then( res => {
      console.log(res)
    }).catch( err => {
      console.log(err)
    }) // 发送成功了

三. Promise 基本使用


1. Executor 执行者

  • executor是在创建 Promise 时需要传入的一个回调函数,该函数会被立即同步执行,因为该函数将在构造这个新 Promise 对象过程中,被 Promise 构造函数执行并且传入两个参数

    js
    new Promise(executor)
  • executor 是一段将输出与 promise 联系起来的自定义代码。executor 的函数签名应为:

    js
    new promise((resolutionFunc, rejectionFunc) => {...})
    • resolutionFuncrejectionFunc 两个参数类型都是函数,可以使用任何名字。这两个函数的签名很简单:接受任何类型的单个参数
    • executor 的返回值将被忽略
    • 如果在该 executor 中抛出一个错误,该 promise 将被拒绝
  • 通常我们会在 Executor 中确定我们的 Promise 状态:

    • 通过 resolve,可以兑现(fulfilledPromise 的状态,我们也可以称之为已决议(resolved
    • 通过 reject,可以拒绝(rejectedPromise 的状态

    注意:

    • 一旦状态被确定下来,Promise 的状态会被锁死,该 Promise 的状态之后是不可更改的
    • 在我们调用 resolve 的时候,如果 resolve 传入的值本身不是一个 Promise,那么会将该 Promise 的状态变成兑现(fulfilled

    • 在之后我们去调用 reject 时,已经不会有任何的响应了(并不是这行代码不会执行,而是无法改变 Promise 状态)

2. Promise 的状态变化

  • 下面 Promise 使用过程,可以划分为三个状态:

    • 待定(pending: 初始状态,既没有被兑现,也没有被拒绝
      • 当执行 executor 中的代码时,处于该状态
    • 已兑现(fulfilled: 意味着操作成功完成
      • 执行了 resolve 时,处于该状态,Promise 已经被兑现
    • 已拒绝(rejected: 意味着操作失败
      • 执行了reject时,处于该状态,Promise已经被拒绝
    js
    const promise = new Promse((resolveFunc, rejectFunc) => {
      resolve('已兑现fulfilled') // 调用resolve,如果Promise状态值变为fulfilled,那么then传入的回调会被执行
      reject('已拒绝rejected') // 调用reject,如果Promise状态值变为rejected,那么catch传入的回调函数会被执行
    })
    
    promise.then(res => {
      console.log(res)
    }).catch(err => {
      console.log(err)
    })

3. resolve 不同值的区别

  • 情况一:如果 resolve 传入一个普通的值或者对象,那么这个值会作为 then 回调的参数

    js
    new Promise((resolve, reject) => {
      // resolve('123')
      resolve({name: 'later'})
    }).then(res => {
      console.log(res) // {name: 'later'}
    })
  • 情况二:如果 resolve 中传入的是另外一个 Promise,那么这个新 Promise 会决定原 Promise 的状态

    js
    new Promise((resolve, reject) => {
      resolve(new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve('第二个Promise的resolve')
        }, 1000)
      }))
    }).then(res => {
      console.log(res) // 第二个Promise的resolve
    }).catch(err => {
      console.log(err)
    })
  • 情况三:如果 resolve 中传入的是实现了 then 方法的一个对象,那么会执行该对象内部的 then 方法,并且根据 then 方法的结果来决定 Promise 的状态

    js
    new Promise((resolve, reject) => {
      resolve({
        name: 'later',
        then: function(resolve, reject) {
          resolve('thenable value')
        }
      })
    }).then(res => {
      console.log(res) // thenable value
    })

四. Promise 实例方法


1. then 方法 - 接受两个参数

  • then 方法是 Promise 对象上的一个实例方法:

    • 它其实是放在 Promise 的原型上的 Promise.prototype.then
  • then 方法接受两个参数:

    • fulfilled 的回调函数:当状态变成 fulfilled 时会回调的函数
    • rejected 的回调函数:当状态变成 rejected 时会回调的函数
    js
    const p = new Promise((resolve, reject) => {
      resolve('已完成 fulfilled')
    })
    
    p.then(res => {
      console.log(res) // 已完成 fulfilled
    }, err => {
      console.log(err)
    })
    
    // 上面写法等价于下面这种写法
    p.then(res => {
      console.log(res) // 已完成 fulfilled
    }).catch(err => {
      console.log(err)
    })
  • 如果 promise 状态为 rejected,且 then 调用传了第二个参数,同时也调用了 catch,则只会执行 then 第二个参数中的代码

    js
    const p = new Promise((resolve, reject) => {
      reject('rejected')
    })
    
    p.then(res => {
      console.log(res)
    }, err => {
      console.log('then中的err: ', err) // then中的err:  rejected
    }).catch(err => {
      console.log('catch中的err: ', err)
    })

2. then 方法 - 多次调用

  • 一个 Promisethen 方法是可以被多次调用的:

    • 每次调用我们都可以传入对应的 fulfilled 回调
    • Promise 的状态变成 fulfilled 的时候,这些 then 方法中的回调函数都会被执行
    js
    const p = new Promise((resolve, reject) => {
      resolve('已完成 fulfilled')
    })
    
    p.then(res => {console.log(res)}) // 已完成 fulfilled
    p.then(res => {console.log(res)}) // 已完成 fulfilled
    p.then(res => {console.log(res)}) // 已完成 fulfilled

3. then 方法 - 返回值

  • then 方法本身是有返回值的,它的返回值是一个 Promise,所以可以进行如下的链式调用

    • 但是 then 方法返回的 Promise 到底处于什么样的状态呢?
  • then 方法返回的 Promise 有三种状态:

    • then 方法中的回调函数本身在执行的时候,那么它处于 pending 状态
    • then 方法中的回调函数返回一个结果时,默认返回 undefined
      • 情况一:返回一个原始值或一个普通对象,那么返回的 Promise 处于 fulfilled 状态,并且会将返回结果作为 resolve 的参数
      • 情况二:返回一个 Promise,根据该 promise 的状态决定返回的的 Promise 的状态
        • 返回一个已经是接受状态的 Promise,那么 then 返回的 Promise 也会成为接受状态,并且将那个 Promise 的接受状态的回调函数的参数值作为该被返回的 Promise 的接受状态回调函数的参数值
        • 返回一个已经是拒绝状态的 Promise,那么 then 返回的 Promise 也会成为拒绝状态,并且将那个 Promise 的拒绝状态的回调函数的参数值作为该被返回的 Promise 的拒绝状态回调函数的参数值
        • 返回一个未定状态(pendingPromise,那么 then 返回 Promise 的状态也是未定的,并且它的终态与那个 Promise 的终态相同;同时,它变为终态时调用的回调函数参数与那个 Promise 变为终态时的回调函数的参数是相同的
      • 情况三:返回一个 thenable 值(内部有实现 then 方法的对象),会调用 then 方法来决定状态
    • then 方法抛出一个异常时,那么它处于 reject 状态
    js
    const promise = new Promise((resolve, reject) => {
      resolve("111")
    })
    // then方法本身是返回一个新的Promise, 这个新Promise的决议是等到then方法传入的回调函数有返回值时, 进行决议,Promise本身就是支持链式调用
    promise.then(res => {
      console.log(res) // 111
      // 1. 返回普通值
      return "222"
    }).then(res => {
      console.log(res) // 222
      // 2. 返回已完成状态的promise
      return new Promise((resolve, reject) => {
        resolve('333')
      })
    }).then(res => {
      console.log(res) // 333
      // 3. 返回一个带有then方法的对象(thenable值)
      return {
        then: function(resolve) {
          resolve('444')
        }
      }
    }).then(res => {
      console.log(res) // 444
      // 4. 不显式指定返回值,默认返回undefined
    }).then(res => {
      console.log(res) // undefined
      // 5. 返回一个已拒绝状态的promise
      return new Promise((resolve, reject) => {
        reject('555')
      })
    }).then(res => {
      console.log(res) 
    }, err => {
      console.log(err) // 555
      // 6. 抛出一个异常 
      throw new Error('抛出异常')
    }).then(res => { 
      console.log(res)
    }, err => {
      console.log(err) // Error: 抛出异常
      // 7. 返回一个未定状态的promise
      return new Promise((resolve, reject) => {})
    }).then(res => { // 该回调函数不会被执行
      console.log(res)
    })

4. catch 方法 - 多次调用

  • catch 方法也是 Promise 对象上的一个实例方法:

    • 它也是放在 Promise 的原型上的 Promise.prototype.catch
  • 一个 Promisecatch 方法是可以被多次调用的:

    • 每次调用我们都可以传入对应的 rejected 回调
    • Promise 的状态变成 rejected 的时候,这些 catch 方法中的回调函数都会被执行
    js
    const p = new Promise((resolve, reject) => {
      reject('已拒绝 rejected')
    })
    
    p.then(res => {}, err => {console.log(err)}) // 已拒绝 rejected
    p.then(res => {}, err => {console.log(err)}) // 已拒绝 rejected
    p.then(res => {}, err => {console.log(err)}) // 已拒绝 rejected

    注意:

    • 如果一个 promise 被拒绝(rejected)并且没有可调用的拒绝处理器(处理器可以是 Promise.prototype.then()、Promise.prototype.catch() 或 Promise.prototype.finally() ),则拒绝事件由宿主环境来提供。在浏览器中,这将导致未处理的拒绝(unhandledrejection)事件。如果将一个处理器附加到一个已被拒绝,且已导致未处理的拒绝事件的 promise,将会触发 rejectionhandled 事件
    • 无法被 try...catch 捕获
    • 在被触发的事件回调中,可以通过 event.preventDefault() 来阻止默认行为(这里就是在浏览器控制台报错)

5. catch 方法 - 返回值

  • 事实上 catch 方法也是会返回一个 Promise 对象的,所以 catch 方法后面我们可以继续调用 then 方法或 catch 方法:

    js
    const promise = new Promise((resolve, reject) => {
      reject("111")
    })
    
    // catch方法也会返回一个新的Promise
    promise.catch(err => {
      console.log("第一个catch回调:", err) // catch回调: 111
      return "222"
    }).then(res => {
      console.log("第一个then回调:", res) // then回调: 222
    }).catch(err => {
      console.log("第二个catch回调:", err)
    }).then(res => {
      console.log("第二个then回调:", res) // 第二个then回调: undefined
    })
    • 上面的代码,我们发现第二个 catch 方法中的回调函数并没有执行,这是为什么呢?

      • 因为第一个 then 方法中的回调执行完的时候,此时第一个 then 方法返回的 promise 状态已变成 fulfilled,而第二个 catch 方法中传入的回调函数只有在 rejected 状态下才会被执行
    • 那为什么第二个 then 方法的回调函数会执行呢?

      • 因为第二个 catch 传入的回调并没有被执行,返回的 promise 状态默认依然是 fulfilled
    • 那么如果我们希望继续执行第二个 catch 方法中的回调函数,需要在前面抛出一个异常:

      js
      const promise = new Promise((resolve, reject) => {
        reject("111")
      })
      
      // catch方法也会返回一个新的romise
      promise.catch(err => {
        console.log("第一个catch回调:", err) // catch回调: 111
        return "222"
      }).then(res => {
        console.log("第一个then回调:", res) // then回调: 222
        throw new Error('11122') // 写在第一个catch方法回调中也行
      }).catch(err => {
        console.log("第二个catch回调:", err) // 第二个catch回调: Error: 11122
      }).then(res => {
        console.log("第二个then回调:", res) // 第二个then回调: undefined
      })

    补充:

    • 中断函数继续执行:

      1. return
      2. throw new Error()
      3. yield 暂停(暂时性的中断)
    • 上面几个方法针对普通函数,而非 promise

6. finally 方法

  • finally 是在 ES9(ES2018) 中新增的一个特性:表示 Promise 无论变成 fulfilled 还是 rejected 状态,最终都会执行传入的回调函数

  • finally() 方法返回一个 Promise

  • 这避免了同样的语句需要在 then()catch() 中各写一次的情况

  • finally 方法是不接收参数的,因为无论前面是 fulfilled 状态,还是 rejected 状态,它都会执行

    js
    const p = new Promise((resolve, reject) => {
      resolve('111')
    })
    
    p.then(res => {
      console.log(res) // 111
    }).catch(err => {
      console.log(err)
    }).finally(() => {
      console.log('执行finally方法传入的回调函数') // 执行finally方法传入的回调函数
    })

五. Promise 类方法


1. resolve 方法

  • 前面我们学习的 thencatchfinally 方法都属于 Promise 的实例方法,都是存放在 Promiseprototype 上的

    • 下面我们再来学习一下 Promise 的类方法
  • 语法:

    js
    Promise.resolve(value)
    • 返回一个带着给定值 value 解析过的 Promise 对象
  • resolve 参数的形态:

    • 情况一:参数是一个普通的值或对象
    • 情况二:参数本身是 Promise
    • 情况三:参数是一个 thenable
  • 有时候我们已经有一个现成的内容了,希望将其转成 Promise 来使用,这个时候我们可以使用 Promise.resolve 方法来完成

    • Promise.resolve 的用法相当于 new Promise,并且执行 resolve 操作

      js
      // const p = new Promise((resolve) => {
      //   resolve('111')
      // })
      
      const p = Promise.resolve('111') // 等价于上面的写法,更简洁的写法
      p.then(res => {
        console.log(res) // 111
      })

2. reject 方法

  • reject 方法类似于 resolve 方法,只是会将 Promise 对象的状态设置为 reject 状态

  • Promise.reject 的用法相当于 new Promise,只是会调用 reject

    js
    // const p = new Promise((_, reject) => {
    //   reject('111')
    // })
    
    const p = Promise.reject('111') // 等价于上面的写法,更简洁的写法
    p.catch(err => {
      console.log(err) // 111
    })
  • Promise.reject 传入的参数无论是什么形态,都会直接作为 reject 状态的参数传递到 catch

    js
    const p = Promise.reject(new Promise((_, reject) => {}))
    p.catch(err => {
      console.log('catch: ', err) // catch:  Promise {<pending>}
    })

3. Promise.all 方法

  • Promise.all() 方法接收一个 promiseiterable 类型(注:ArrayMapSet 都属于 ES6iterable 类型)的输入,并且返回一个 Promise 实例

    • 它的作用是将多个 Promise 包裹在一起形成一个新的 Promise

    • 新的 Promise 状态由包裹的所有 Promise 共同决定:

      • 当所有的 Promise 状态变成 fulfilled 状态时,新的 Promise 状态为 fulfilled,并且会将所有 Promise 的返回值组成一个数组
      • 当有一个Promise状态为reject时,新的Promise状态为reject,并且会将第一个reject的返回值作为参数
      js
      const p1 = new Promise((resolve, reject) => {
        setTimeout(() => { resolve("p1 resolve") }, 3000)
      })
      
      const p2 = new Promise((resolve, reject) => {
        setTimeout(() => { resolve("p2 resolve") }, 2000)
      })
      
      const p3 = new Promise((resolve, reject) => {
        setTimeout(() => { resolve("p3 resolve") }, 5000)
      })
      
      const p4 = new Promise((resolve, reject) => {
        setTimeout(() => { reject('p4 reject') }, 1000)
      })
      
      Promise.all([p1, p2, p3]).then(res => {
        console.log("all promise res:", res) // all promise res: ['p1 resolve', 'p2 resolve', 'p3 resolve']
      }).catch(err => {
        console.log("all promise err:", err)
      })
      
      Promise.all([p1, p2, p4]).then(res => {
        console.log("all promise res:", res)
      }).catch(err => {
        console.log("all promise err:", err) // all promise err: p4 reject
      })

4. Promise.allSettled 方法

  • all 方法有一个缺陷:当有其中一个 Promise 变成 reject 状态时,新 Promise 就会立即变成对应的 reject 状态

    • 那么对于 resolved 的,以及依然处于 pending 状态的 Promise,我们是获取不到对应的结果的
  • ES11(ES2020) 中,添加了新的 APIPromise.allSettled

    • 该方法会在所有的 Promise 都有结果(settled),无论是 fulfilled,还是 rejected 时,才会有最终的状态(终态)
    • 并且这个返回的 Promise 的结果一定是 fulfilled,也就是说如果有调用 then 方法,就一定会执行 then 方法中传入的回调函数
    js
    const p1 = new Promise((resolve, reject) => {
      setTimeout(() => { reject("p1 reject error") }, 3000)
    })
    
    const p2 = new Promise((resolve, reject) => {
      setTimeout(() => { resolve("p2 resolve") }, 2000)
    })
    
    const p3 = new Promise((resolve, reject) => {
      setTimeout(() => { resolve("p3 resolve") }, 1000)
    })
    
    Promise.allSettled([p1, p2, p3]).then(res => {
      console.log("all settled:", res) // all settled [{status: 'rejected', reason: 'p1 reject error'}, {status: 'fulfilled', value: 'p2 resolve'}, {status: 'fulfilled', value: 'p3 resolve'}]
    })
  • 我们来看一下打印的结果:

    • allSettled 的结果是一个数组,数组中存放着每一个 Promise 的结果,并且是对应一个对象的
    • 这个对象中包含 status 状态,以及对应的 value
    • 如果 status 的值为 fulfilled,则结果对象上存在一个 value 。如果值为 rejected,则存在一个 reasonvalue (或 reason )反映了每个 promise 决议(或拒绝)的值

5. Promise.race 方法

  • 如果有一个 Promise 有了结果,我们就希望决定最终新 Promise 的状态,那么可以使用 race 方法:

    • race 是竞技、竞赛的意思,表示多个 Promise 相互竞争,谁先有结果,那么就使用谁的结果
    • 返回一个 promise,一旦迭代器中的某个 promise 已决议或已拒绝,返回的 promise 就会已决议或已拒绝
    js
    const p1 = new Promise((resolve, reject) => {
      setTimeout(() => { resolve("p1 resolve") }, 3000)
    })
    
    const p2 = new Promise((resolve, reject) => {
      setTimeout(() => { reject("p2 reject error") }, 2000)
    })
    
    const p3 = new Promise((resolve, reject) => {
      setTimeout(() => { resolve("p3 resolve") }, 1000)
    })
    
    Promise.race([p1, p2, p3]).then(res => {
      console.log("race promise:", res) // race promise: p3 resolve
    }).catch(err => {
      console.log("race promise err:", err)
    })

6. Promise.any 方法

  • any 方法是 ES12 中新增的方法,和 race 方法是类似的:

    • any 方法只要等到其中一个 promise 变为 fulfilled 状态,就会返回那个已完成状态的 Promise
    • 如果所有的 Promise 都是 reject 的,就会返回一个失败的 promise,同时会报一个 AggregateError 的错误
    js
    const p1 = new Promise((resolve, reject) => {
      setTimeout(() => { reject("p1 reject error") }, 3000)
    })
    
    const p2 = new Promise((resolve, reject) => {
      setTimeout(() => { reject("p2 reject error") }, 2000)
    })
    
    const p3 = new Promise((resolve, reject) => {
      setTimeout(() => { reject("p3 reject error") }, 1000)
    })
    
    const p4 = new Promise((resolve, reject) => {
      setTimeout(() => { resolve("p4 resolve") }, 500)
    })
    
    Promise.any([p1, p2, p3]).then(res => {
      console.log("any promise res:", res)
    }).catch(err => {
      console.log("any promise err:", err) // any promise err: AggregateError: All promises were rejected
    })
    
    Promise.any([p1, p2, p4]).then(res => {
      console.log("any promise res:", res) // any promise res: p4 resolve
    }).catch(err => {
      console.log("any promise err:", err)
    })

7. Promise 类方法总结

  • Promise.resolve()
    • 相当于 new Promise(),并且执行 resolve 操作
  • Promise.reject()
    • 相当于 new Promise,只是会调用 reject
  • Promise.all()
    • 当所有传入的 Promise 状态变成 fulfilled 状态时,返回的新的 Promise 状态为 fulfilled,并且将所有 Promise 的返回值组成一个数组
    • 当有一个 Promise 状态为 reject 时,新的 Promise 状态为 reject,并且会将第一个 reject 的返回值作为参数
  • Promise.allSettled()
    • 在所有的 Promise 都有结果(settled),无论是 fulfilled,还是 rejected时,才会有最终的状态(终态),返回的 Promise 的结果一定是 fulfilled
  • Promise.race()
    • 返回第一个状态为 fulfilledrejectedpromise 所返回的结果
  • Promise.any()
    • 返回第一个为 fulfilled 状态的 promise 所返回的结果

Released under the MIT License.