JavaScript异步解决方案之Async/Await

250 阅读4分钟

ES2017 引入了async函数,使得异步操作更方便。

概念

async函数是什么,就是Generator函数的语法糖。

和Generator的区别

// 使用Genrator函数 完成文件读取
const fs = require('fs');

const readFile = function (fileName) {
  return new Promise(function (resolve, reject) {
    fs.readFile(fileName, function(error, data) {
      if (error) return reject(error);
      resolve(data);
    });
  });
};

const gen = function* () {
  const f1 = yield readFile('/etc/fstab');
  const f2 = yield readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};
// 使用async/await
const asyncReadFile = async function () {
  const f1 = await readFile('/etc/fstab');
  const f2 = await readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};

区别主要在于: 把* 号换成async,把yield换成了await/

对Generator的改进

  1. 内置执行器

Generator函数的执行必须依赖co模块。
async函数不需要,调用方式和普通函数一致,后加一个括号即可。调用之后,就会自动执行,输出最后结果。

  1. 更好的语义

async和await,语义更加清楚了。async表示函数内有异步操作。 await 表示紧跟在后面的表达式需要等待结果。

  1. 返回值是Promise

Generator函数的返回值是遍历器对象,async函数的返回值是Promise。

  1. 更广的适用性

co模块规定,yield后只能是Thunk函数,或者Promise对象。
但是await之后可以是Promise和基本类型的值,基本类型的值会自动调用Promise.resolvd()转化为resolved状态的Promise。

基本用法

async函数返回一个Promise对象,可以适用then方法添加回调函数,当函数执行的时候,一旦遇到await就先返回,等到异步操作完成,再接着执行函数体内后面的语句。

async function getStockPriceByName(name) {
  const symbol = await getStockSymbol(name);
  const stockPrice = await getStockPrice(symbol);
  return stockPrice;
}

getStockPriceByName('goog').then(function (result) {
  console.log(result);
});

语法

async函数的语法相对来说简单,比较难的是错误处理机制。

返回Promise对象

async函数返回一个Promiseu对象,函数体内的return语句返回的值,会称为then方法的参数。
如果函数体内抛出错误,会导致返回的Promise对象变为reject状态,抛出的错误对象会被catch方法回调函数或者onRejected接收到。

Promise对象的状态变化

async函数返回的Promise对象,必须等到内部所有的await命令后面的Promise对象执行完,才会发生状态变化。
也是说async函数内部的异步操作执行完,才会执行then方法指定的回调函数。

await 命令

正常情况下 await后面是一个Promise对象,返回该对象的结果。如果不是Promise对象,就直接返回对应的值。
另一种情况,await命令后面是一个thenable对象,即定义then方法的对象,那么await会将其等同于Promise对象。
如果await命令后面的Promise对象如果变为rejected状态,则reject的参数会被catch方法的回调函数接收到。

async function f() {
  await Promise.reject('出错了');//这是一个rejected状态的 Promise 
  // 
}

f()
.then(v => console.log(v),e => console.log(e))
.catch(e => console.log(e))
// 出错了
// await语句没有return 但是reject方法的参数依然传入了catch方法,这里如果在await前面加上return
// 效果是一样的

任何一个await语句后的Promise对象变为reject状态,那么整个async函数都会被中断执行。
如何避免这种情况:我们希望 尽管出错,也要继续执行
使用try..catch语句包裹await语句,处理可能出现的错误

错误处理

如果await后面的异步操作出错,那么等同于async函数返回的Promise对象被reject。

async function f() {
  await new Promise(function (resolve, reject) {
    throw new Error('出错了');
  });
}
// f()执行之后 会返回一个Promise
console.log(f())
// throw出来的错误会导致catch方法后面的回调函数被调用,参数就是抛出的错误对象
f()
.then(v => console.log(v))
.catch(e => console.log(e))

浏览器中执行:

image.png

防止出错的方法,是使用try..catch语句。如果有多个命令,可以统一放在try..catch语句中。

使用注意点

  1. 最好将await命令使用try...catch包裹
  2. 多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。
  3. await命令只能用在async函数中。