Promise使用详解

126 阅读10分钟

1. 异步代码存在困境

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

    • 需要自己来设计回调函数、回调函数的名称、回调函数的使用等;
    • 对于不同的人、不同的框架设计出来的方案是不同的,就必须耐心去看别人的源码或者文档,以便可以理解它这个函数到底怎么用。
    // 1.设计这样的一个函数
    function execCode(counter, successCallback, failureCallback) {
      // 异步任务
      setTimeout(() => {
        if (counter > 0) { // counter可以计算的情况 
          let total = 0
          for (let i = 0; i < counter; i++) {
            total += i
          }
          // 在某一个时刻只需要回调传入的函数
          successCallback(total)
        } else { // 失败情况, counter有问题
          failureCallback(`${counter}值有问题`)
        }
      }, 3000)
    }
    
    // 2.ES5之前,处理异步的代码都是这样封装
    execCode(100, (value) => {
      console.log("本次执行成功了:", value)
    }, (err) => {
      console.log("本次执行失败了:", err)
    })
    

2. Promise

  • Promise是一个类,可以翻译成 承诺、许诺 、期约

    • 当需要的时候,给予调用者一个承诺:待会儿我会给你回调数据时,就可以创建一个Promise的对象

    • 通过new创建Promise对象时,需要传入一个回调函数,称之为executor

      • 这个回调函数会被立即执行,并且给传入另外两个回调函数resolve、reject
      • 当调用resolve回调函数时,会执行Promise对象的then方法传入的回调函数
      • 当调用reject回调函数时,会执行Promise对象的catch方法传入的回调函数
    function execCode(counter) {
      const promise = new Promise((resolve, reject) => {
        // 异步任务
        setTimeout(() => {
          if (counter > 0) { // counter可以计算的情况 
            let total = 0
            for (let i = 0; i < counter; i++) {
              total += i
            }
            // 成功的回调
            resolve(total)
          } else { // 失败情况, counter有问题
            // 失败的回调
            reject(`${counter}有问题`)
          }
        }, 3000)
      })
      
      return promise
    }
    
    const promise = execCode(100)
    promise.then((value) => {
      console.log("成功有了结果: ", value)
    })
    promise.catch((err) => {
      console.log("失败有了错误: ", err)
    })
    
    // const promise2 = execCode(-100)
    // promise2.then(value => {
    //   console.log("成功:", value)
    // })
    // promise2.catch(err => {
    //   console.log("失败:", err)
    // })
    
    // 执行一次
    execCode(255).then(value => {
      console.log("成功:", value)
    }).catch(err => {
      console.log("失败:", err)
    })
    

3. Promise三种状态

  • Promise划分成三个状态:

    • 待定(pending): 初始状态,既没有被兑现,也没有被拒绝

      • 当执行executor中的代码时,处于该状态
    • 已兑现(fulfilled): 意味着操作成功完成

      • 执行了resolve时,处于该状态,Promise已经被兑现
    • 已拒绝(rejected): 意味着操作失败

      • 执行了reject时,处于该状态,Promise已经被拒绝
  • Executor是在创建Promise时需要传入的一个回调函数,这个回调函数会被立即执行,并且传入两个参数:

    • 通常会在Executor中确定Promise的状态:

      • 通过resolve,可以兑现(fulfilled)Promise的状态,我们也可以称之为已决议(resolved)
      • 通过reject,可以拒绝(reject)Promise的状态
    const promise = new Promise((resolve, reject) => {
      // 注意: Promise的状态一旦被确定下来, 就不会再更改, 也不能再执行某一个回调函数来改变状态
      // 1.待定状态 pending
      console.log("111111")
      console.log("222222")
      console.log("333333")
    
      // 2.兑现状态 fulfilled
      resolve()
    
      // 3.拒绝状态 rejected
      reject()
    })
    
    promise.then(value => {
      console.log("成功的回调")
    }).catch(err => {
      console.log("失败的回调")
    })
    
  • 注意!!!

    • Promise状态只能从pending->rejected 或者pending ->fulfilled
    • Promise的状态一旦被确定下来就不会再更改, 也不能再执行某一个回调函数来改变状态

4. resolve不同的值

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

  • 如果resolve中传入的是另外一个Promise,那么这个新Promise会决定原Promise的状态

  • 如果resolve中传入的是一个对象,并且这个对象有实现then方法,那么会执行该then方法,并且根据then方法的结果来决定Promise的状态

    const p = new Promise((resolve) => {
      // setTimeout(resolve, 2000)
      setTimeout(() => {
        resolve("p的resolve")
      }, 2000)
    })
    
    const promise = new Promise((resolve, reject) => {
      // 1.普通值
      // resolve([
      //   {name: "macbook", price: 9998, intro: "有点贵"},
      //   {name: "iPhone", price: 9.9, intro: "有点便宜"},
      // ])
    
      // 2.resolve(promise)
      // 如果resolve的值本身Promise对象, 那么当前的Promise的状态会有传入的Promise来决定
      // resolve(p)
    
      // 3.resolve(thenable对象)
      resolve({
        name: "kobe",
        then: function(resolve) {
          resolve(11111)
        }
      })
    })
    
    promise.then(res => {
      console.log("then中拿到结果:", res)
    })
    

5. 实例方法

5.1 then方法

then方法是Promise对象上的一个方法(实例方法):其实是放在Promise的原型上的 Promise.prototype.then

  • 接受两个参数

    • fulfilled的回调函数:当状态变成fulfilled时会回调的函数
    • reject的回调函数:当状态变成reject时会回调的函数 (等同于 promise.catch)
  • 多次调用

    • 一个Promise的then方法是可以被多次调用的:每次调用都可以传入对应的fulfilled回调
    • 当Promise的状态变成fulfilled的时候,这些回调函数都会被执行
    const promise = new Promise((resolve, reject) => {
      resolve("success")
      // reject("failure")
    })
    
    // promise.then(res => {
    // }).catch(err => {
    // })
    
    // 1.then参数的传递方法: 可以传递两个参数
    // 这种写法也是可以的
    // promise.then(res => {
    //   console.log("成功回调~", res)
    // }, err => {
    //   console.log("失败回调~", err)
    // })
    
    promise.then(res => {
      console.log("成功回调~", res)
    })
    promise.then(res => {
      console.log("成功回调~", res)
    })
    promise.then(res => {
      console.log("成功回调~", res)
    })
    promise.then(res => {
      console.log("成功回调~", res)
    })
    
  • 返回值:返回一个Promise

    • 当then方法中的回调函数本身在执行的时候,它处于pending状态
    • 当then方法中的回调函数返回一个结果时,它处于fulfilled状态,并且会将结果作为resolve的参数
      • 情况一:返回一个普通的值
      • 情况二:返回一个Promise
      • 情况三:返回一个thenable值
    • 当then方法抛出一个异常时,那么它处于reject状态
    const promise = new Promise((resolve, reject) => {
      resolve("aaaaaaa")
      // reject()
    })
    
    // 1.then方法是返回一个新的Promise, 这个新Promise的决议是等到then方法传入的回调函数有返回值时, 进行决议
    // Promise本身就是支持链式调用
    // then方法是返回一个新的Promise, 链式中的then是在等待这个新的Promise有决议之后执行的
    // promise.then(res => {
    //   console.log("第一个then方法:", res)
    //   return "bbbbbbbb"
    // }).then(res => {
    //   console.log("第二个then方法:", res)
    //   return "cccccccc"
    // }).then(res => {
    //   console.log("第三个then方法:", res) // cccccccc
    // })
    
    // promise.then(res => {
    //   console.log("添加第二个then方法:", res)
    // })
    
    // 2.then方法传入回调函数的返回值类型
    const newPromise = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve("kobe")
      }, 3000)
    })
    
    promise.then(res => {
      console.log("第一个Promise的then方法:", res)
      // 1.普通值
      // return "bbbbbbb"
      // 2.新的Promise
      // return newPromise
      // 3.thenable的对象
      return {
        then: function(resolve) {
          resolve("thenable")
        }
      }
    }).then(res => {
      console.log("第二个Promise的then方法:", res) // undefined
    })
    

5.2 catch方法

catch方法也是Promise对象上的一个方法(实例方法):也是放在Promise的原型上的 Promise.prototype.catch

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

    • 每次调用都可以传入对应的reject回调
    • 当Promise的状态变成reject的时候,这些回调函数都会被执行
    const promise = new Promise((resolve, reject) => {
      reject("failure")
    })
    
    // 需要捕获异常,否则会报错  Uncaught (in promise) failure
    promise.then(res => {
      console.log("成功的回调:", res)
    }).catch(err => {
      console.log("失败的回调:", err)
    })
    
    promise.catch(err => {
      console.log("失败的回调:", err)
    })
    promise.catch(err => {
      console.log("失败的回调:", err)
    })
    promise.catch(err => {
      console.log("失败的回调:", err)
    })
    promise.catch(err => {
      console.log("失败的回调:", err)
    })
    
  • catch方法也是会返回一个Promise对象的,所以catch方法后面可以继续调用then方法或者catch方法

    • 如果希望后续继续执行catch,那么需要抛出一个异常
    const promise = new Promise((resolve, reject) => {
      // reject("error: aaaaa")
      resolve("aaaaaa")
    })
    
    // 1.catch方法也会返回一个新的Promise
    // promise.catch(err => {
    //   console.log("catch回调:", err)
    //   return "bbbbb"
    // }).then(res => {
    //   console.log("then第一个回调:", res)
    //   return "ccccc"
    // }).then(res => {
    //   console.log("then第二个回调:", res)
    // })
    
    // 2.catch方法的执行时机
    promise.then(res => {
      console.log("then第一次回调:", res)
      // throw new Error("第二个Promise的异常error") 
      return "bbbbbb"
    }).then(res => {
      console.log("then第二次回调:", res)
      throw new Error("第三个Promise的异常error")
    }).then(res => {
      console.log("then第三次回调:", res)
    }).catch(err => {
      console.log("catch回调被执行:", err)
    })
    

5.4 finally方法(ES9)

  • finally是在ES9(ES2018)中新增的一个特性:表示无论Promise对象无论变成fulfilled还是rejected状态,最终都会被执行的代码。

  • 注意:finally方法是不接收参数的

    const promise = new Promise((resolve, reject) => {
      // pending
    
      // fulfilled
      resolve("aaaa")
    
      // rejected
      // reject("bbbb")
    })
    
    promise.then(res => {
      console.log("then:", res)
      // foo()
    }).catch(err => {
      console.log("catch:", err)
      // foo()
    }).finally(() => {
      console.log("哈哈哈哈")
      console.log("呵呵呵呵")
    })
    

6. 类方法

6.1 resolve

  • 如果希望将一个已知数据转成Promise来使用,这个时候可以使用 Promise.resolve 方法来完成

    • Promise.resolve的用法相当于new Promise,并且执行resolve操作
  • resolve参数的形态:

    • 参数是一个普通的值或者对象
    • 参数本身是Promise
    • 参数是一个thenable
    // 类方法
    const studentList = []
    const promise = Promise.resolve(studentList)
    
    promise.then(res => {
      console.log("then结果:", res)
    })
    

6.2 reject

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

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

  • Promise.reject传入的参数无论是什么形态,都会直接作为reject状态的参数传递到catch的

    const promise = Promise.reject("rejected error")
    promise.catch(err => {
      console.log("err:", err)
    })
    

6.3 all

  • 将多个Promise包裹在一起形成一个新的Promise

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

    • 所有的Promise状态变成fulfilled状态时,新的Promise状态为fulfilled,并且会将所有Promise的返回值组成一个数组
    • 有一个Promise状态为reject时,新的Promise状态为reject,并且会将第一个reject的返回值作为参数
    // 创建三个Promise
    const p1 = new Promise((resolve, reject) => {
      setTimeout(() => {
        // resolve("p1 resolve")
        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")
      }, 5000)
    })
    
    // all:全部/所有
    Promise.all([p1, p2, p3]).then(res => {
      console.log("all promise res:", res)
    }).catch(err => {
      console.log("all promise err:", err)
    })
    

6.4 allSettled

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

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

    • 该方法会在所有的Promise都有结果(settled),无论是fulfilled,还是rejected时,才会有最终的状态,并且这个Promise的结果一定是fulfilled
    • allSettled的结果是一个数组,数组中存放着每一个Promise的结果,并且是对应一个对象的
      • 这个对象中包含status状态,以及对应的value值:[{ status: , value/reason }]
    // 创建三个Promise
    const p1 = new Promise((resolve, reject) => {
      setTimeout(() => {
        // resolve("p1 resolve")
        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")
      }, 5000)
    })
    
    // 类方法: allSettled
    Promise.allSettled([p1, p2, p3]).then(res => {
      console.log("all settled:", res)
    })
    

6.5 race

  • 竞赛: 有一个有结果, 不管这个结果是fulfilled还是rejected

    const p1 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve("p1 resolve")
        // reject("p1 reject error")
      }, 3000)
    })
    
    const p2 = new Promise((resolve, reject) => {
      setTimeout(() => {
        // resolve("p2 resolve")
        reject("p2 reject error")
      }, 2000)
    })
    
    const p3 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve("p3 resolve")
      }, 5000)
    })
    
    
    // 特点: 会等到一个Promise有结果(无论这个结果是fulfilled还是rejected)
    Promise.race([p1, p2, p3]).then(res => {
      console.log("race promise:", res)
    }).catch(err => {
      console.log("race promise err:", err)
    })
    

6.6 any

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

    • any方法会等到一个fulfilled状态,才会决定新Promise的状态;

    • 如果所有的Promise都是reject的,那么也会等到所有的Promise都变成rejected状态;

  • 如果所有的Promise都是reject的,那么会报一个AggregateError的错误

  • 总结

    • 任意: 等到一个fulfilled, 如果都不是fulfilled生成一个错误对象
    const p1 = new Promise((resolve, reject) => {
      setTimeout(() => {
        // resolve("p1 resolve")
        reject("p1 reject error")
      }, 3000)
    })
    
    const p2 = new Promise((resolve, reject) => {
      setTimeout(() => {
        // resolve("p2 resolve")
        reject("p2 reject error")
      }, 2000)
    })
    
    const p3 = new Promise((resolve, reject) => {
      setTimeout(() => {
        // resolve("p3 resolve")
        reject("p3 reject error")
      }, 5000)
    })
    
    // 类方法: any方法
    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
    })
    

7. 总结

7.1 promise的特点

  • 对象的状态不受外界影响

    • promise有三种状态 pending(进行中) fulfilled(已成功) rejected(已失败),只有异步操作的结果,才可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
  • 一旦从等待状态变成为其他状态就永远不能更改状态

    • promise只有两种状态改变:
      • pending(进行中)--> fulfilled(已成功)
      • pending(进行中)--> rejected(已失败)

7.2 Promise的优点

  • 让回调函数变成了规范的链式写法,并且有一整套接口,可以实现许多强大的功能,比如同时执行多个异步操作,等到他们的状态都改变以后,在执行一个回调函数;再比如,为多个回调函数中抛出的错误,统一制定处理方法…

  • 他的状态一旦改变,无论何时查询,都能得到这个状态。这意味着无论何时为promise实例添加回调函数,该函数都能正确执行。

  • 传统写法的话都通过监听事件来执行回调函数,一旦错过了事件,再添加回调函数是不会执行的。

7.3 Promise的缺点

  • 一旦新建Promise就会立即执行,无法中途取消。
  • 如果不设置回调函数callback,Promise内部抛出的错误,就不会反应到外部。
  • 当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。