JavaScript之Promise

写东西真是难啊,一是因为肚子里没多少货,二是因为掌握的东西也太细碎表面。好吧,这也是为什么要写的原因,希望能借此倒逼一下自己。

本来想着一个主题接一个主题的来写,但是发现好难。好吧,那就从自己熟悉的东西开始写。

Promise长啥样

先看大致的代码

new Promise(function (resolve, reject) {
  // 执行异步操作......

  // 判断异步操作的结果
  if (/* success */) {
    resolve(value);
  } else {
    reject(err);
  }
}).then(res => {
  // 成功,执行后续操作
}).catch(err => {
  // 失败,处理错误
})

Promise的字面意思就是承诺,承诺表示表示将要做但是还未做的事情,而且承诺还伴随着这件事情是成功或者失败的反馈。所以当new一个Promise的时候,就相当于告诉js引擎去帮我们执行一些操作(同步或者异步的都可以),执行完后再告诉我们结果,然后我们再根据结果的成功与否去做之后的操作。

举个栗子

console.log('start...')
new Promise(function (resolve, reject) {
  console.log('start promise...')
  // 大约0.5秒后执行
  setTimeout(() => {
    console.log('start timeout...')
    // 随机生成一个num,1 <= num <= 10
    const num = parseInt(Math.random() * 10) + 1
    // 大于5算成功,否则算失败
    if (num > 5) {
      resolve('success')
    } else {
      reject('failed')
    }
    console.log('end timeout...')
  }, 500)
  console.log('end promise...')
}).then(res => {
  console.log(res)
}).catch(err => {
  console.log(err)
})
console.log('end...')

运行结果:

start...
start promise...
end promise...
end...
start timeout...
end timeout...
failed/success

这段代码,首先会打印出start...字符串,然后当我们new Promise()的时候,引擎会立刻执行Promise里的代码,所以打印出start promise...。紧接着,遇到setTimeout,所以直接跳到end priomise...处打印该字符串。这个时候,Promise里的代码暂时执行完毕,引擎将切换到Promise外部执行后续的同步代码,打印出end...。大约在0.5秒后,引擎将切回Primise内部执行setTimeout的回调函数,于是打印出start timeout...,但是不管resolve还是reject,只要没有return语句,都将打印出end promise...,最后才是执行then或者catch里的回调函数。

Promise的3种状态

Promise对象一共有pedding(进行中),fulfilled(已成功)和rejected(已失败)3种状态。从创建Promise到resolve函数执行之前,都是pedding状态,resolve()执行后变成fulfilled状态,reject()执行后变成rejected状态。

Promise的then方法

Promise的then方法是在Promise的状态改变时调用,接收两个参数,第一个参数是成功时的回调函数,第二个参数(可选)是失败时的回调函数。

then方法会返回一个新的Promise实例,于是我们可以继续在then后面写then(catch方法同样返回Promise实例,可以链式调用)。这个特性挺有用的,下面用示例说一下。

首先,先创建一个函数,该函数返回一个Promise实例,该Promise成功或失败的概率各50%。

const isMoreThanFive = function () {
  return new Promise(function (resolve, reject) {
    setTimeout(() => {
      const num = parseInt(Math.random() * 10) + 1
      if (num > 5) {
        resolve('success')
      } else {
        reject('failed')
      }
    }, 500)
  })
}

一个then函数的情况:

isMoreThanFive()
.then((res) => {
  console.log(res)
})
.catch(err => {
  console.log(err)
})
// 0.5秒后输出success或者failed

两个then函数的情况:

isMoreThanFive()
.then((res) => {
  console.log('first ' + res);
  return isMoreThanFive()
})
.then(res => {
  console.log('second ' + res);
})
.catch(err => {
  console.log(err)
})

这种情况,如果第一个isMoreThanFive执行失败,将直接执行catch回调,如果成功,会执行第一个then方法,then方法里又通过isMoreThanFive返回了一个新的Promise实例,这时,如果新的Promise成功将执行第二个then方法,如果失败将执行catch方法。

末尾的catch方法会捕获到前面Promise出现的所有异常,包括reject返回的失败状态和代码执行过程中发生的错误,所以一般会省略then方法的第二个参数(处理失败时的回调),而直接在最后添加catch方法统一处理前面发生的错误。
在调用接口时,如果B接口依赖A接口返回的参数,这时,then的链式调用将非常方便。

Promise的all方法

先看代码

const p1 = isMoreThanFive()
const p2 = isMoreThanFive()
Promise.all([p1, p2])
.then(res => {
  console.log(res)
})
.catch(err => {
  console.log(err)
})  
// 输出 failed 或者 ['success', 'success']

all方法接受一个数组,数组各项都是Promise实例。Promise.all会返回一个新的Promise实例,只有当数组内所有的Promise实例都成功时,all才返回成功状态,否则只要有一个失败都返回失败状态。如果all执行成功,将返回一个数组,数组的值和Promise实例的返回值一一对应。

注意,传给all方法的所有Promise实例都是同时执行的,也就是说同时执行多个异步操作,如果某个实例失败,则all方法直接返回失败状态,返回值由失败的Promise实例提供。

这在前端渲染页面时非常有用,利用Promise.all方法将可以同时请求多个接口,提升页面的渲染速度。

特殊情况,当Promise实例有自己的catch方法时:

const p1 = isMoreThanFive()
const p2 = isMoreThanFive()
          .catch(err => {
            return err
          })
Promise.all([p1, p2])
.then(res => {
  console.log(res)
})
.catch(err => {
  console.log(err)
})  
// 输出 failed 或者 ['success', 'success'] 或者 [ 'success', 'failed' ]

如果传入的Promise实例有自己的catch方法,就不会触发Promise.all的catch方法。p2如果返回失败,会被自己的catch方法捕获,经过catch处理后返回一个resolved成功的Promise实例,返回值是失败状态返回的err,最后Promise.all也就返回成功状态。

最后

单独使用Promise已经很方便很直观了,但是如果配合上async函数的话,写异步代码完全可以使用同步的写法,下一篇将会写关于async的东西。

关于Promise,大致就是这些,没有面面俱到,主要是把自己觉得经常用到的和重要的写了下来。如果想了解更多的细节和原理可以看阮一峰老师的ES6教程。

JavaScript之Promise

相关推荐