ES6之async函数学习笔记

《ECMAScript6 入门教程》http://es6.ruanyifeng.com/

Posted by wangtiegang on February 22, 2020

在学习 async 之前,必须了解 PromiseGenerator,这两个是基础。

Promise简单回顾

Promise 是异步编程的一种解决方案,比传统的解决方案(回调函数和事件)更合理和更强大。 Promise 简单来说就是一个容器,里面保存着一个未来才会结束的事件(通常是个异步操作)的结果,Promise 对象有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。Promise 的的状态改变后会立即调用回调函数,回调函数通过 then() 指定,then() 会返回一个新的 Promise 对象实例,因此可以链式调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const promise = new Promise(function(resolve, reject) {
  // ... 异步操作

  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

// Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。
promise.then(function(value) {
  // 状态从pending(进行中)-> fulfilled(已成功),value 为 resolve 的参数
}, function(error) {
  // 状态从pending(进行中)-> rejected(已失败),error 为 reject 的参数
  // 此回调函数可以省略
}).then(() => {
  // 链式调用,等待上一个 then 完成后触发
  // 采用箭头函数写法,更简洁
}, () => {

});

Generator简单回顾

Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。可以把它理解为一个状态机,封装了多个内部状态。执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// function关键字与函数名之间有一个星号,函数体内部使用yield表达式
function* helloWorldGenerator() {
  yield 'hello';
  const end = yield 'world';
  return end;
}

// 调用生成器函数返回一个指向内部状态的遍历器对象(Iterator Object)
var hw = helloWorldGenerator();

// 调用next(),函数从头部执行到第一个 yield 表达式,返回一个状态
hw.next(); // { value: 'hello', done: false }
// 调用next(),执行到第二个 yield 表达式
hw.next(); // { value: 'world', done: false }
// 调用next(),传入到参数会作为上一个 yield 表达式到返回值,也即是 end 会被赋值 'ending'
hw.next('ending'); // { value: 'ending', done: true }
// 状态变成 true 之后,再调用 next(),都会返回 { value: undefined, done: true }
hw.next(); // { value: undefined, done: true }

从应用上,可以把生产器函数当作分步执行到函数,每次到 yield 表达式就暂停,返回一个状态,然后手动next触发执行下一步,直到完成。

async函数

ES2017 标准引入了 async 函数,使得异步操作变得更加方便,async 函数是什么?一句话,它就是 Generator 函数的语法糖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 生成器函数
const genRead = function* () {
  const f1 = yield readFile('/etc/fstab');
  const f2 = yield readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};

// async函数
const asyncRead = async function () {
  const f1 = await readFile('/etc/fstab');
  const f2 = await readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};

语法上一比较就会发现,async函数就是将 Generator 函数的星号(*)替换成 async,将 yield 替换成 await,仅此而已。

但是 async 函数对 生成器函数的改进体现在以下四点

  • 内置执行器

    Generator 函数的执行必须靠执行器,调用 next() 一步步直到完成,而 async 函数跟普通函数调用方式一致,内置执行器,会自动运行到最后结果。

  • 更好到语义

    async和await,比起星号和yield,语义更清楚了。

  • 更广的适用性
  • 返回值是 Promise

    async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    // 定义一个异步函数
    async function getStockPriceByName(name) {
      const symbol = await getStockSymbol(name);
      const stockPrice = await getStockPrice(symbol);
      return stockPrice;
    }
    // 调用异步函数,会返回一个 Promise ,可以使用 then 添加回调函数
    getStockPriceByName('goog').then(function (result) {
      console.log(result);
    });
    

async函数返回一个 Promise 对象,async函数内部return语句返回的值,会成为then方法回调函数的参数。

1
2
3
4
5
async function f() {
  return 'hello world';
}

f().then(v => console.log(v)) // "hello world"

async函数内部抛出错误,会导致返回的 Promise 对象变为reject状态。抛出的错误对象会被catch方法回调函数接收到。

1
2
3
4
5
6
7
8
9
async function f() {
  throw new Error('出错了');
}

f().then(
  v => console.log(v),
  e => console.log(e)
)
// Error: 出错了

正常情况下,await命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。

async 使用注意点

  • await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令放在try…catch代码块中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
    async function myFunction() {
      try {
        await somethingThatReturnsAPromise();
      } catch (err) {
        console.log(err);
      }
    }
    // 另一种写法
    async function myFunction() {
      await somethingThatReturnsAPromise()
      .catch(function (err) {
        console.log(err);
      });
    }
    
  • 多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    // 写法一,使用 Promise 到 all 函数
    let [foo, bar] = await Promise.all([getFoo(), getBar()]);
    
    // 写法二,将 getFoo和getBar 的返回对象 Promise 赋值给变量,两个会立即执行
    let fooPromise = getFoo();
    let barPromise = getBar();
    // await 直接等待 Promise 执行结果
    let foo = await fooPromise;
    let bar = await barPromise;
    
  • await命令只能用在async函数之中,如果用在普通函数,就会报错。

以上是对 async 函数的简单笔记,理解了就能进行基本的应用,足够我理解 antd pro 里面的代码了,如果要深入理解,还需详细阅读教程文档。