ES8 async & await

分享

简单谈谈

async 是什么?一句话,它就是 Generator 函数的语法糖。

既然是语法糖,那么先来对比下 async 函数和 Generator 函数的区别。以下是分别用 asyncGenerator 写的异步操作:

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 是什么的可以查看上一节

上诉代码,主要有以下几点区别:

类型 Generator async
函数标识 * async
暂停语句 yield await
执行方法 调用自动执行方法(autoGen) 直接调用

如果能理解 Generator 函数在异步中的使用的话,那么我相信,async 函数对于我们来说,简直简单的不能在简单了。那么既然用 Generator 函数能解决的东西,为什么又要多一个 async

和大多数语法糖出现一样,async 主要是为了更好的语义和更广的适用性。主要表现在以下几点:

  1. asyncawait ,比起 *yield ,语义更清楚了。 async 表示函数里有异步操作, await 表示紧跟在后面的表达式需要等待结果。
  2. yield 命令后面只能是 Thunk 函数或 Promise 对象,而 async 函数的 await 命令后面,可以是 Promise 对象或原始类型的值(数值、字符串和布尔值,等同于同步操作)。
  3. 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 对象。如果不是,会被转成一个立即 resolvePromise 对象。
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

使用注意点

  1. await 命令后面的 Promise 对象,运行结果可能是 rejected ,所以最好把 await 命令放在 try...catch 代码块中,或直接后面跟一个 catch 方法进行异常捕捉。
  2. 多个 await 命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。
  3. await 命令只能用在 async 函数之中,如果用在普通函数,就会报错。

关于第二点,由于 async 函数的设计就是以一种同步的写法写异步函数,所以 await 是一个一个执行的,有顺序的关系,所以当一个函数体内有两个互相无关的异步请求的话,直接写 await 是达不成同时进行异步操作的效果的,所以得先使用 Promise.all 进行一层包装。

let [foo, bar] = await Promise.all([getFoo(), getBar()]);

阅读更多

ES6 Proxy

简单谈谈 Proxy:在目标对象之前架设一层 拦截 ,当对该对象进行 访问/赋值 时,必须先通过这层拦截,从而实现对属性进行 访问/赋值 时的过滤和改写。 一个简单的例子 let obj = new Proxy({}, { get(target, key, receiver) { console.log(`getting ${key}!`); return Reflect.get(target, key, receiver); }, set(target, key, value, receiver) { console.log(`setting ${key}!`); return Reflect.set(target, key, value, receiver); } }); obj.count

By Breeze

ES6 变量解构

简单说说 假设有这样一个场景:你需要向你的服务器获取一些基本的数据(config 对象)来初始化页面。当你需要去获取其中的配置项时, ES5 是这么写的: var config = { params1: 520, params2: 1314 }; // 为了确保下方的代码不依赖于 config 对象,重新赋值为一个更有意义的名称。 var params1 = config.params1, params2 = config.params2 console.log(params1 + ' !! ' + params2) // 520 !! 1314 看着代码量也不是很大,但这只有两个配置项,万一要有几十个呢,不得一直 congif.xxx 了。不得不得这的确很烦,那有没更简单的方式呢? ES6 提出了以下方式: var config = { params1: 520, params2:

By Breeze

ES6 Array

简单谈谈 ES5 中原本就提供了数组,这里谈谈数组下都有哪些方法新增,哪些方法进行了变更。 Array.from 在 javascript 中有这样一类的存在:类数组,它不是数组,却能像数组那么操作,如下便是一个类数组结构。 let arrayLike = { "0": "a", "1": "b", "2": "c", length: 3 }; 类数组结构:可以像数组那样取值、 for 循环,但是却不能适用数组 forEach/splice 等数组特定的方法,在 ES5 中必须使用一些特殊的方法来将一个类数组结构转化成数组,而在 ES6 中,

By Breeze