ES8 async & await
简单谈谈
async 是什么?一句话,它就是 Generator 函数的语法糖。
既然是语法糖,那么先来对比下 async 函数和 Generator 函数的区别。以下是分别用 async 和 Generator 写的异步操作:
const fs = require('fs');
const readFile = function (fileName) {
    return new Promise((resolve, reject) => {
        fs.readFile(fileName, (error, data) => {
            if (error) return reject(error);
            resolve(data);
        });
    });
};
// Generator 的写法 & 执行
const gen = function* () {
    const f1 = yield readFile('/etc/fstab');
    const f2 = yield readFile('/etc/shells');
    console.log(f1.toString());
    console.log(f2.toString());
}
autoGen(gen())
// async 的写法 & 执行
const asyncReadFile = async function () {
    const f1 = await readFile('/etc/fstab');
    const f2 = await readFile('/etc/shells');
    console.log(f1.toString());
    console.log(f2.toString());
}
asyncReadFile();
**注:**不清楚 autoGen 是什么的可以查看上一节。
上诉代码,主要有以下几点区别:
类型Generatorasync函数标识*async暂停语句yieldawait执行方法调用自动执行方法(autoGen)直接调用
如果能理解 Generator 函数在异步中的使用的话,那么我相信,async 函数对于我们来说,简直简单的不能在简单了。那么既然用 Generator 函数能解决的东西,为什么又要多一个 async ?
和大多数语法糖出现一样,async 主要是为了更好的语义和更广的适用性。主要表现在以下几点:
async和await,比起*和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。yield命令后面只能是Thunk函数或Promise对象,而async函数的await命令后面,可以是Promise对象或原始类型的值(数值、字符串和布尔值,等同于同步操作)。async函数的返回值是Promise对象,这比Generator函数的返回值是Iterator对象方便多了。可以用then方法指定下一步的操作。
基本用法
async
async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。
async function asy(){
    return 'this is async fun';
};
asy().then(res => console.log(res));
// this is async fun
上述例子可见 async 函数最终返回 Promise 其结果为函数的返回值。
async 函数有多种形式(与基本函数的表现形式一致):
// 函数声明
async function foo() {};
// 函数表达式
const foo = async function () {};
// 对象的方法
let obj = { async foo() {} };
obj.foo().then(/* ... */);
// Class 的方法
class Storage {
    constructor() {
        this.cachePromise = caches.open('avatars');
    }
    
    async getAvatar(name) {
        const cache = await this.cachePromise;
        return cache.match(`/avatars/${name}.jpg`);
    }
}
const storage = new Storage();
storage.getAvatar('jake').then(/* ... */);
// 箭头函数
const foo = async () => {};
async 函数的错误处理
当 async 内部有未捕捉的异常时, async 函数返回的 Promise 将会是 reject 状态。
async function f() {
    throw new Error('出错了');
}
f().then(
    v => console.log(v),
    e => console.log(e)
);
// Error: 出错了
await
正常情况下,await 命令后面是一个 Promise 对象。如果不是,会被转成一个立即 resolve 的 Promise 对象。
async function f() {
    let a = await 123;
    return a;
}
// 等同于
async function f() {
    let a = await Promise.resolve(123);
    return a;
}
f().then(v => console.log(v));
// 123
当 await 命令后跟的 Promise 状态变更为 fulfilled(resolve) 时,a 会被赋值,a 的值即为异步处理的结果。
当然如果 await 命令后的 Promise 的状态变更为 reject 并且没有对该 Promise 进行 catch 处理,那么 async 函数结束执行返回的 Promise 也将会变成 reject 状态。
async function f() {
    await Promise.reject('出错了');
    await Promise.resolve('hello world');   // 不会执行
}
f();
.then(v => console.log(v));
.catch(e => console.log(e));
// 出错了
错误处理
有两种方式可以在不影响 async 函数执行的情况下进行捕捉异常:
try...catch
async function f() {
  try {
    await Promise.reject('出错了');
  } catch(e) {
  }
  return await Promise.resolve('hello world');
}
f();
.then(v => console.log(v));
// hello world
Promise.catch
async function f() {
  await Promise.reject('出错了').catch(e => console.log(e));
  return await Promise.resolve('hello world');
}
f();
.then(v => console.log(v));
// 出错了
// hello world
使用注意点
await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令放在try...catch代码块中,或直接后面跟一个catch方法进行异常捕捉。- 多个 
await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。 await命令只能用在async函数之中,如果用在普通函数,就会报错。
关于第二点,由于 async 函数的设计就是以一种同步的写法写异步函数,所以 await 是一个一个执行的,有顺序的关系,所以当一个函数体内有两个互相无关的异步请求的话,直接写 await 是达不成同时进行异步操作的效果的,所以得先使用 Promise.all 进行一层包装。
let [foo, bar] = await Promise.all([getFoo(), getBar()]);