Promise对象的使用

208 阅读5分钟

1. Promise是什么?

Promise是ES6中提出的一个新型的异步编程方案之一。相比于传统的回调函数+事件,Promise更强大、代码也更优雅。

那么,到底什么是Promise呢?

准确的来说,Promise是一个对象。简单来理解的话,它更像是一个”承诺“的容器,里面包含一个未来某个时间会完成的任务(通常是一个异步任务)以及完成后的结果。

列位,到这里大家可能还是一头雾水,咱别急,接着往下看。

2. 创建一个Promise对象

首先,咱们先创建一个Promise对象。像这样,

let promise = new Promise(() => {
  // some code
})
  • 通过构造函数Promise来创建一个promise对象
  • 此时,需要给Promise构造函数传入一个回调函数executor
    • executor 是 同步回调。当new Promise时会立即执行
    • executor 会接收两个参数,均为函数类型
    • executer 第一个参数 为 resolve(当然名字可以随便改),接收一个参数value,当该函数调用时会将Promise对象的状态修改为”fulfilled“,即已解决状态也就是我们理解的成功状态,同时指定参数value为成功的值。
    • executor 第二个参数 为 reject(也可以为其他名字),接收一个参数reason,当该函数调用时会将Promise对象的状态修改为”rejected“,即拒绝的状态也就是我们理解的失败状态,同时指定参数reason为失败的原因。
    • executor 的返回值 会被忽略
    • 如果在该 executor 中抛出一个错误,该 promise 将被拒绝。

接下来,咱们举个栗子:假设1s后生成一个随机数,如果值大于0.6,那么就将Promise对象状态修改为成功的,同时将随机数作为成功值;否则修改Promise对象状态设置为失败,失败的原因设为”这个数太小了。“

  let p = new Promise((resolve, reject) => {
    setTimeout(() => {
      let val = Math.random()
      if(val > 0.6){
        resolve(val);
      } else {
        reject('这个数太小了。')
      }
    }, 1000)
  })

3. 获取完成后的结果

Promise对象具有三种状态

  • Pending 初始状态,表示异步任务正在进行中
  • Fulfilled 已完成的,表示异步任务最终是成功的
  • Rejected 被拒绝的,表示异步任务最终是失败的

这里注意,一旦Promise对象状态由Pending变为Fulfilled或者Rejected中一种后表示Promise状态为已敲定的settled,即不可以也不会在发生改变。

那么,我们如何获取最终结果呢?

Promise对象具有一个then()方法,可以通过回调函数的方式获取到结果

  • then()方法具有两个参数,类型为函数
  • 第一个参数为 onFulfiled,当Promise对象状态变为成功时调用,并接收成功的值
  • 第二个参数为 onRejected,当Promise对象状态变为失败时调用,并接收失败的原因(异常)

因此,要处理成功的值或者拿到失败原因,我们可以像下面代码那样做:

  let p = new Promise((resolve, reject) => {
    setTimeout(() => {
      let val = Math.random()
      if(val > 0.6){
        resolve(val);
      } else {
        reject('这个数太小了。')
      }
    }, 1000)
  });
  
  p.then(funciton onFulfilled(value){
    alert(value)
  }, function onRejected(reason){
    console.log(reason);
  })

Promise对象也含有一个方法,叫做catch(onRejected),可以用来处理Promise对象失败的情况。

因此,配合箭头函数上面代码可以写的更优雅:

  let p = new Promise((resolve, reject) => {
    setTimeout(() => {
      let val = Math.random()
      if(val > 0.6){
        resolve(val);
      } else {
        reject('这个数太小了。')
      }
    }, 1000)
  });
  
  p
    .then( (value) => {
      alert(value)
    })
    .catch( (reason) => {
      alert(reason)
    });

4. 回调地狱

那么,到此请问列位:“为什么Promise比回调+事件实现异步编程更强大更优雅呢?”

这里就要简述一下,回调+事件的方式存在一个严重的弊端--回调地狱。

假设,现在有三个异步任务A、B、C,但是有严格的执行顺序:

  • 当A成功后,在执行B
  • 当B成功后,在执行C

代码这里,使用setTimeout方法做异步任务演示:

  // 这里封装函数,发送http请求
  // 参数为 url请求地址, success成功回调
  function request(url, success){
    // 模拟请求,1s后请求成功
    setTimeout(() => {
       success(url)
    }, 1000)
  }
  
  // 接下来,执行任务A
  request('A', () => {
    // 这里执行时,表明A任务成功,执行任务B
    request('B', () => {
      // 这里执行时,表明B任务成功,执行任务C
       request('C', () => {
        // 这里执行时,表明C任务成功,执行任务D
        // ...
        // 这里就会一直嵌套回调函数,此现象就被叫做回调地狱
      })
    })
  })

为什么实际开发时,我们要尽量杜绝这种现象呢?因为回调地狱 会 极大的降低代码的可读性,进而影响代码的维护难度。

那再问,Promise可以解决吗?!当然Of Course!!!

来看代码,

  // 改造request实现,使其基于Promise实现异步任务
  var request = function(url){
    return new Promise((res, rej) => {
      setTimeout(() => {
        res(url)
      }, 1000)
    })
  }
  
  request('A').then(aVal => {
    // A成功
    console.log(aVal)
    return request('B')
  }).then(bVal => {
     // B成功
     console.log(bVal)
     return request('C')
  }).then(cVal => {
    // C成功
    console.log(bVal)
    // return request('D')
  })
  // ...

上面代码,then方法回调中可以返回一个新的Promise对象,然后通过then方法的链式调用就可以在不嵌套回调函数的方式即可实现异步任务按照固定顺序依次执行。

因此,最终Promise对象完美解决传统异步编程的弊端。

当然在ES7中,通过async/await + promise会更完美更优雅的方式去解决毁掉地狱的问题,这里就不在叙述了。