async await promise try...catch

一、几者之间联系

  1. 简单介绍下这几个的关系

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    async function buildData(name) {
    try {
    let response1 = await axios.get('/api/user?name=' + name);
    let userInfo = response1.data;

    let response2 = await axios.get('/api/topics?user_id' + userInfo._id);
    let posts = response2.data;
    // i got it.
    } catch(err) {
    console.log(err);
    }
    }

    buildData('xiaoming');
  2. async

    在函数声明前使用async关键词修饰 说明函数中有异步操作

  3. await

    等待 后面的代码执行完毕 再继续向下执行

  4. promise

    Promise 是一个对象,从它可以获取异步操作的消息,知道异步函数是完成了还是出错了。
    axios返回的结果就是一个promise

  5. try catch

    try catch JavaScript的异常捕获机制,凡是在try语句块中的代码出错了,都会被catch捕获。

    上面的代码就是说

    1. buildData 这个函数被 async 修饰 说函数中有异步操作
    2. await 等待异步操作结果
    3. 如果有错误发生 使用try catch 捕获异常

二、详解

(一)promise

从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

  1. 三个状态

    Promise字面上讲,是一个承诺。这个承诺有三个状态

    • pending 进行中(悬而未决)
    • fulfilled 已成功(以满足)
    • rejected 已失败(已拒绝)
  2. 两个特点

    1. 对象的状态不受外界的影响。只有异步操作的结果可以决定当前是哪种状态。
    2. 状态一旦改变 就不会再变,任何时候都可以得到这个结果。promise对象状态的改变只有两种情况,
    • pending 到 fulfilled
    • pending 到 rejected

    只要这两种情况发生,状态就凝固了,不会再改变了,会一直保持这个结果,这时就定型了 resolved。
    如果改变已经发生了,任何时候添加回调函数,得到的都是这个结果。

    因为创建Promise对象时,回调函数中有resolve和reject两个参数,后续的resolved统一指的是fulfilled状态,不包括rejected状态

    一个Promise对象一旦状态确定了,它的使命也就结束了。后面的代码都不应该再执行了,最好return resolve();
    如果状态已经resolve了,再在后面抛出错误也是无效的,也不会改变状态为rejected,后面有异常也不会抛出

  3. Promise对象如何知道异步操作结果又如何传递(resolve、reject)

    Promise对象如何知道异步操作的结果呢,那就是回调函数了,一个表示成功resolve,一个表示失败reject,

    ES6 规定,Promise对象是一个构造函数,用来生成Promise实例。

    • Promise实例

      1
      2
      3
      4
      5
      6
      7
      8
      9
      var promise = new Promise(function(resolve, reject) {
      // ... some code
      console.log(我一创建就执行了)
      if (/* 异步操作成功 */){
      resolve(value);
      } else {
      reject(error);
      }
      });

      Promise新建后,就会立即执行,返回一个Promise对象。
      Promise构造函数需要一个函数作为参数,这个函数有两个参数,分别是resolve和reject,
      它们是两个函数,由 JavaScript 引擎提供,不用自己部署

    • resolve: 把Promise状态 从 pending 变为 resolved ,在异步操作成功时调用,并将异步操作的结果,作为参数传递出去

    • reject: 把Promise状态 从 pending 变为 reject ,在异步操作失败时调用,并把错误作为参数传递出去
      此外,reject方法的作用,也等同于抛出异常。reject的参数会被catch捕获。即便没有调用reject,如果执行过程中出错了,也会被catch捕获。

    所以:

    1. 异步操作成功时,把结果告诉resolve回调函数 异步操作失败 把错误告诉reject回调函数
    2. resolve 和 reject 回调函数 还有一个作用就是把 结果传递出去
  4. Promise对象如何使用异步函数的执行结果(then、catch)

    • then(指定了resolve状态和reject的回调函数)

      1
      2
      3
      4
      5
      promise.then(function(value) {
      // success
      }, function(error) {
      // failure
      });

      then方法的参数是两个回调函数,都接受Promise对象传出的值作为参数:

      • 第一个参数是resolve状态的回调函数
      • 第二个参数是reject的回调函数, 这个参数是可选的

      通常这个参数也不写,因为Promise实例还有一个方法叫catch是专门用来捕获异常的。

    • catch(专门用来捕获Promise对象产生的错误)

      catch 是 .then(null, rejection)的别名,用于指定发生错误时的回调函数。

      一旦catch前面的任何一个Promise发生异常,都会被catch捕获,包括Promise函数创建的Promise,还有.then返回的Promise,甚至catch前面如果还有一个catch在这个catch抛出的异常也会被后一个catch捕获。

      也就是说:
      Promise对象的错误具有冒泡性质,会一直向后传递,直到被捕获为止,也即是说,错误总会被下一个catch语句捕获。

      所以,既然这个catch这么厉害,then函数中的第二个参数常常被省略了,然后被这个catch方法替代。

      所以通常这么写:
      promise.then().catch()
      promise.then().then().catch()
      promise.then().then().catch().then().catch()

      所以下面例子第二种写法好些。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      // bad
      promise
      .then(function(data) {
      // success
      }, function(err) {
      // error
      });

      // good
      promise
      .then(function(data) { //cb
      // success
      })
      .catch(function(err) {
      // error
      });

      一般总是建议,Promise 对象后面要跟catch方法,这样可以处理 Promise 内部发生的错误。catch方法返回的还是一个 Promise 对象,因此后面还可以接着调用then方法和catch方法。

(二)try catch

try catch是JavaScript的异常处理机制,把可能出错的代码放在try语句块中,如果出错了,就会被catch捕获来处理异常。如果不catch 一旦出错就会造成程序崩溃。

  • 如果有多个await命令,可以将其都放在try catch结构中,如果执行出错,catch会去捕获异常

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    async function f() {
    try {
    await Promise.reject('出错了');
    console.log('上面已经出错了');
    return await Promise.resolve('hello world');
    } catch(e) {
    console.log(e);
    }
    }

    f()
    .then(v => console.log(v))

catch会去捕获try代码块中的错误,只要有一个抛出了异常,就不会继续执行,所以上面的代码不会打印上面已经出错了也不会执行return await Promise.resolve(‘hello world’);
因为使用了trycatch 所以 async 是顺利执行完成的,其中的报错 被 try catch处理了,所以异常不会被async返回的Promise的catch捕获,因此async返回的Promise对象状态是resolved。

(三)Promise.all()

  • Promise.all可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    let p1 = new Promise((resolve, reject) => {
    resolve('成功了')
    })

    let p2 = new Promise((resolve, reject) => {
    resolve('success')
    })

    let p3 = Promse.reject('失败')

    Promise.all([p1, p2]).then((result) => {
    console.log(result) //['成功了', 'success']
    }).catch((error) => {
    console.log(error)
    })

    Promise.all([p1,p3,p2]).then((result) => {
    console.log(result)
    }).catch((error) => {
    console.log(error) // 失败了,打出 '失败'
    })
  • Promse.all在处理多个异步处理时非常有用,比如说一个页面上需要等两个或多个ajax的数据回来以后才正常显示,在此之前只显示loading图标

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    let wake = (time) => {
    return new Promise((resolve, reject) => {
    setTimeout(() => {
    resolve(`${time / 1000}秒后醒来`)
    }, time)
    })
    }

    let p1 = wake(3000)
    let p2 = wake(2000)

    Promise.all([p1, p2]).then((result) => {
    console.log(result) // [ '3秒后醒来', '2秒后醒来' ]
    }).catch((error) => {
    console.log(error)
    })

需要特别注意的是,Promise.all获得的成功结果的数组里面的数据顺序和Promise.all接收到的数组顺序是一致的,即p1的结果在前,即便p1的结果获取的比p2要晚。这带来了一个绝大的好处:在前端开发请求数据的过程中,偶尔会遇到发送多个请求并根据请求顺序获取和使用数据的场景,使用Promise.all毫无疑问可以解决这个问题。

(四)Promise.race()

  • 顾名思义,Promse.race就是赛跑的意思,意思就是说,Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    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((result) => {
    console.log(result)
    }).catch((error) => {
    console.log(error) // 打开的是 'failed'
    })

原理是挺简单的,但是在实际运用中还没有想到什么的使用场景会使用到。