Promise: 给我一个承诺,我还你一个承诺

(165) 2024-03-23 13:01:01

为何产生promise?

在 Promise 出现以前,我们处理一个异步网络请求,需求大概是这样:我们需要根据第一个网络请求的结果,再去执行第二个网络请求,再根据第二个网络请求的结果去执行第三个请求~,需求是永无止境的,于是乎出现了如下代码:

请求1(function(){
    // 一些其他操作
    请求2(function(请求1结果){
        // 一些其他操作
        请求3(function(请求2结果){
            // 一些其他操作
            请求4(function(请求3结果){
                // 一些其他操作
                请求5(function(请求4结果){
                    // 一些其他操作
                    请求6(function(请求5结果){
                        // 一些其他操作
                        ...
                    })
                })
            })
        })
    })
})

这就是所谓的回调地狱,回调地狱带来的负面作用有以下几点:

  • 代码臃肿。
  • 可读性差。
  • 耦合度过高,可维护性差。
  • 代码复用性差。
  • 容易滋生 bug。
  • 只能在回调里处理异常。

这时,就有人思考了,能不能用一种更加友好的代码组织方式,解决异步嵌套的问题。于是 Promise 规范诞生了,promise采用链式调用,很好地解决了回调地狱的痛点。

我们使用Promise常规写法来实现上面的异步网络请求,代码如下:

new Promise(请求1)
    .then(请求2(请求结果1))
    .then(请求3(请求结果2))
    .then(请求4(请求结果3))
    .then(请求5(请求结果4))
    .then(请求6(请求结果5))
    .catch(处理异常(异常信息))

我们不难发现,Promise 的写法更为直观,并且能够在外层捕获异步函数的异常信息。

什么是promise?

Promise 是异步编程的一种解决方案,比传统的解决方案【回调函数】和【事件】更合理、更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。Promise说得通俗一点就是一种写代码的方式,并且是用来写JavaScript编程中的异步代码的。

promise三种状态

三种状态:

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

只有异步操作的结果才能确定当前处于哪种状态,任何其他操作都不能改变这个状态,这也是Promise(承诺)的由来。


Promise对象的状态改变,只有两种可能:

  • 从pending变为fulfilled
  • 从pending变为rejected

这两种情况只要发生,状态就凝固了,不会再变了,这时就称为resolved(已定型)

promise缺点

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

promise API

Promise: 给我一个承诺,我还你一个承诺 (https://mushiming.com/)  第1张

我们先把Promise打印出来,会发现Promise是一个构造函数,自己身上有all、reject、resolve、race等方法,原型上有then、catch、finally等方法。

【1】Promise.prototype.constructor()

它的基本用法如下:

    let promise = new Promise((resolve, reject) => {
      // 在这里执行异步操作
      if (/*异步操作成功*/) {
        resolve(success)
      } else {
        reject(error)
      }
    })

Promise接收一个函数作为参数,函数里有resolve和reject两个参数: 

  1.  resolve方法的作用是将Promise的pending状态变为fulfilled,在异步操作成功之后调用,可以将异步返回的结果作为参数传递出去。
  2. reject方法的作用是将Promise的pending状态变为rejected,在异步操作失败之后调用,可以将异步返回的结果作为参数传递出去。

他们之间只能有一个被执行,不会同时被执行,因为Promise只能保持一种状态。

【2】Promise.prototype.then()

Promise实例确定后,可以用then方法分别指定fulfilled状态和rejected状态的回调函数。它的基本用法如下:

    promise.then((success) => {
      // 异步操作成功在这里执行
      // 对应于上面的resolve(success)方法
    }, (error) => {
      // 异步操作失败在这里执行
      // 对应于上面的reject(error)方法
    })

    // 还可以写成这样 (推荐使用这种写法)
    promise.then((success) => {
      // 异步操作成功在这里执行
      // 对应于上面的resolve(success)方法
    }).catch((error) => {
      // 异步操作失败在这里执行
      // 对应于上面的reject(error)方法
    })

then(onfulfilled,onrejected)方法中有两个参数,两个参数都是函数,第一个参数执行的是resolve()方法(即异步成功后的回调方法),第二参数执行的是reject()方法(即异步失败后的回调方法)(第二个参数可选)。它返回的是一个新的Promise对象。

【3】Promise.prototype.catch()

catch方法是.then(null,onrejected)的别名,用于指定发生错误时的回调函数。作用和then中的onrejected一样,不过它还可以捕获onfulfilled抛出的错,这是onrejected所无法做到的:

    function createPromise(p, arg) {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          if (arg === 0) {
            reject(p + ' fail')
          } else {
            resolve(p + ' ok')
          }
        }, 0);
      })
    }

    createPromise('p1', 1).then((success) => {
      console.log(success) // p1 ok
      return createPromise('p2', 0)
    }).catch((error) => {
      console.log(error) // p2 fail
    })

    createPromise('p1', 1).then((success) => {
      console.log(success) // p1 ok
      return createPromise('p2', 0)
    }, (error) => {
      console.log(error) // Uncaught (in pomise) p2 fail
    })

 

Promise错误具有"冒泡"的性质,如果不被捕获会一直往外抛,直到被捕获为止;而无法捕获在他们后面的Promise抛出的错。

【4】Promise.prototype.finally()

finally方法用于指定不管Promise对象最后状态如何,都会执行的操作。该方法是 ES2018 引入的标准:

    createPromise('p1', 0).then((success) => {
      console.log(success)
    }).catch((error) => {
      console.log(error) // p1 fail
    }).finally(() => {
      console.log('finally') // finally
    })

    createPromise('p1', 1).then((success) => {
      console.log(success) // p1 ok
    }).catch((error) => {
      console.log(error)
    }).finally(() => {
      console.log('finally') // finally
    })

finally方法不接受任何参数,故可知它跟Promise的状态无关,不依赖于Promise的执行结果。

【5】Promise.all()

Promise.all方法接受一个数组作为参数,但每个参数必须是一个Promise实例。Promise的all方法提供了并行执行异步操作的能力,并且在所有异步操作都执行完毕后才执行回调,只要其中一个异步操作返回的状态为rejected那么Promise.all()返回的Promise即为rejected状态,此时第一个被reject的实例的返回值,会传递给Promise.all的回调函数:

    function createPromise(p, arg) {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          if (arg === 0) {
            reject(p + ' fail')
          } else {
            resolve(p + ' ok')
          }
        }, 0);
      })
    }

    // test: 两个Promise都成功
    Promise.all([createPromise('p1', 1), createPromise('p2', 1)])
      .then((success) => {
        console.log(success) // ['p1 ok', 'p2 ok']
      }).catch((error) => {
        console.log(error)
      })

    // test: 其中一个Promise失败
    Promise.all([createPromise('p1', 0), createPromise('p2', 1)])
      .then((success) => {
        console.log(success)
      }).catch((error) => {
        console.log(error) // p1 fail 
      })

    // test: 两个Promise都失败
    Promise.all([createPromise('p1', 0), createPromise('p2', 0)])
      .then((success) => {
        console.log(success)
      }).catch((error) => {
        console.log(error) // p1 fail 只打印第一个失败的异步操作信息
      })

【6】Promise.race()

Promise的race方法和all方法类似,都提供了并行执行异步操作的能力。顾名思义,race就是赛跑的意思,意思就是说Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态,以下就是race的执行过程:

    let p1 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('success')
      }, 1000)
    })

    let p2 = new Promise((resolve, reject) => {
      setTimeout(() => {
        reject('failed')
      }, 500)
    })

    Promise.race([p1, p2]).then((success) => {
      console.log(success)
    }).catch((error) => {
      console.log(error)  // failed
    })

【7】Promise.resolve()

有时需要将现有对象转为 Promise 对象Promise.resolve()方法就起到这个作用。

Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))

【8】Promise.reject()

Promise.reject()方法也会返回一个新的 Promise 实例,该实例的状态为rejected。

const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))

例子

Promise 翻译成中文为“承诺, 诺言”, 例如: 你承诺这个月挣钱了给你老婆买一个包, 那么你先去挣钱, 等挣钱了就立刻给老婆买包, 实现你的诺言, 没挣到钱就立马道歉。换成代码就是:

  // 买包就是一个Promise,Promise的意思就是承诺
  // 这时候老公给老婆一个承诺
  // 在未来的一个月,不管挣没挣到钱,都会给老婆一个答复
  
  let buyBag = new Promise((resolve, reject) => {
    // Promise 接受两个参数
    // resolve: 异步事件成功时调用(挣到钱)
    // reject: 异步事件失败时调用(没挣到钱)

    // 模拟挣钱概率事件
    let result = function makeMoney() {
      return Math.random() > 0.5 ? '挣到钱' : '没挣到钱'
    }

    // 下面老公给出承诺,不管挣没挣到钱,都会给老婆一个答复
    if (result == '挣到钱')
      resolve('我买包了')
    else
      reject('不好意思,我这个月没挣到钱')
  })


  buyBag().then(res => {
    // 返回 "我买包了"
    console.log(res)
  }).catch(err => {
    // 返回 "不好意思,我这个月没挣到钱"
    console.log(err)
  })

解释一下

第一段调用了Promise构造函数,第二段是调用了promise实例的.then方法

1. 构造实例

  • 构造函数接受一个函数作为参数
  • 调用构造函数得到实例buyBag的同时,作为参数的函数会立即执行
  • 参数函数接受两个回调函数参数resolve和reject
  • 在参数函数被执行的过程中,如果在其内部调用resolve会将buyBag的状态变成fulfilled,或者调用reject会将buyBag的状态变成rejected

2. 调用.then

  • 调用.then可以为实例buyBag注册两种状态回调函数
  • 当实例buyBag的状态为fulfilled,会触发第一个回调函数执行
  • 当实例buyBag的状态为rejected,则触发第二个回调函数执行

 

THE END

发表回复