[TOC] Async/Await是ECMAScript新引入的语法,能够极大地简化异步程序的编写,下面主要来介绍一下Async/Await的用法以及与传统方式的对比,通过具体的例子来体现Async/Await的优势。

async函数是Generator函数的语法糖。使用关键字async来表示,在函数内部使用await来表示异步。是真正意义上去解决异步回调的问题,同步流程表达异步操作。

const fs = require('fs');

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

const foo = function* () {
  const f1 = yield readFile('/src/lib');
  const f2 = yield readFile('/src/utils');

  console.log(f1.toString());
  console.log(f2.toString());
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

把上面代码的Generator函数foo可以写成async函数,就是这样:

const asyncReadFile = async function () {
  const f1 = await readFile('/src/lib');
  const f2 = await readFile('/src/utils');

  console.log(f1.toString());
  console.log(f2.toString());
};
1
2
3
4
5
6
7

相较于Generator,async函数的改进在于下面几点:

  1. 内置执行器Generator函数的执行必须依靠执行器,所以才有了Thunk函数和co模块。而async函数自带执行器,不需要像Generator去调用next方法,遇到await等待,当前的异步操作完成就往下执行。调用方式跟普通函数的调用一样
  2. 更好的语义:async关键字取代Generator函数的星号*,await取代Generator的yield,async和await相较于* 和yield更加语义化;async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
  3. 更广的适用性:co模块约定,yield命令后面只能是Thunk函数或Promise对象。而async函数的await命令后面则可以是Promise或者原始类型的值(Number,string,boolean,但这时等同于同步操作);
  4. 返回值是Promiseasync函数返回值是Promise对象(默认是成功态,并且把该函数的返回值传给then的第一个参数),比Generator函数返回的Iterator对象方便,可以直接使用then()方法进行下一步调用。
  5. 代码逻辑更清晰async/await的优势在于能更好的处理then链,特别是有多个Promise组成的then链的时候,优势就体现出来了。

async是ES7新出的特性,表明当前函数是异步函数,不会阻塞线程导致后续代码停止运行。

const asyncFn = async () => {
    return '我后执行'
}
asyncFn().then(result => {
    console.log(result);
})
console.log('我先执行');
// 运行结果:
我先执行
我后执行
1
2
3
4
5
6
7
8
9
10

虽然是上面asyncFn()先执行,但是已经被定义为异步函数了,不会影响后续代码的执行。

const asyncFn = async () => {
    return '我后执行'
};
console.log(asyncFn());
1
2
3
4

运行结果:

Promise {<resolved>: "我后执行"}
1

需要注意:async函数返回的是一个Promise对象。async函数(包含函数语句、函数表达式、Lambda表达式)会返回一个Promise对象,如果在函数中return一个基本类型值,async会把这个基本类型值通过Promise.resolve()封装成Promise对象。该Promise对象,最终resolve的值就是在函数中return的基本类型值的内容。

async和await

从字面意思来理解,async是异步的简写,而await可以认为是async wait(异步等待)的简写。所以应该很好理解,async用于声明一个函数是异步的,而await用于等待一个异步方法执行完成。

async-await和Promise的关系

async/await是Promise和generator的语法糖。只是为了让我们书写代码时更加流畅,当然也增强了代码的可读性。简单来说:async-await是建立在promise机制之上的,并不能取代其地位。

基本语法

async function fn() {
    const result = await Math.random();
    console.log(result);
}
fn();
1
2
3
4
5

async

async用来声明函数是异步的,定义的函数会返回一个Promise对象,可以使用then方法添加回调函数。

async function fn(params) {
    return 'demo1';
}

fn().then(data => {
    console.log(data); // 'demo1'
});
1
2
3
4
5
6
7
async function fn(params) {
    console.log('111');
}
fn().then(v => console.log(v));
1
2
3
4

运行结果:

111
undefined
1
2

若async定义的函数有返回值,return 123;相当于Promise.resolve(123),没有声明式的return则相当于执行了Promise.resolve(undefined);

await(理解为async wait)

await可以理解为是async wait(异步等待)的简写。await关键字只能出现在async函数内部,不能单独使用。任何async函数都会默认返回Promise,并且这个Promise解析的值都将会是这个函数的返回值,而async函数必须等到内部所有的await命令的Promise对象执行完,才会发生状态改变。

await后面可以跟任何的JS表达式。虽然说await可以等很多类型的东西,但是它最主要的意图是用来等待Promise对象的状态被resolved。如果await的是Promise对象会造成异步函数停止执行并且等待Promise的解决,如果等的是正常的表达式则立即执行。

简单应用

async function sendRequest(url) {
    return new Promise((resolve, reject) => {
        $.ajax({
            url,
            type: 'GET',
            success: data => resolve(data),
            error: error => reject(error)
        });
    });
}

async function getNews(url) {
    const res = await sendRequest(url);
    // 再根据新闻内容返回值来获取新闻的评论
    const res2 = await sendRequest(res.commentsUrl);
    console.log(res, res2);
}

getNews('http://www.example.com/news?newsID=123'); // 先获取新闻内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

Async函数的错误处理

let a;

const testFn = async () => {
    await Promise.reject('error');
    a = await 1; // 这行代码不会被执行
}

testFn().then(() => console.log(a));
1
2
3
4
5
6
7
8

如上面代码所示:当async函数中只要一个await出现reject状态,则后面的await都不会被执行。解决办法:可以添加try/catch。

let a;

const testFn = async () => {
    try {
        await Promise.reject('error');
    } catch(error) {
        console.error(error);
    }
    a = await 1;
}

testFn().then(() => console.log(a));
1
2
3
4
5
6
7
8
9
10
11
12
const testError = async () => {
    throw new Error('has Error');
}
testError()
    .then(success => console.log('成功', success))
    .catch(error => console.log('失败', error)); // 失败 Error: has Error
1
2
3
4
5
6

demo

基础示例

const axios = require('axios');

const getZhihuColumn = async id => {
	// 获取知乎小管家的专栏信息
    const url = `https://zhuanlan.zhihu.com/api/columns/${id}`;
    const response = await axios({
        url: url,
        method: 'GET'
    });
    console.log(`Name: ${response.data.name}`);
    console.log(`Intro: ${response.data.intro}`);
}

getZhihuColumn('zhihuadmin');
1
2
3
4
5
6
7
8
9
10
11
12
13
14

将任意函数定义成async风格

// 将函数的结果做为一个Promise返回
const axios = require('axios');

const getZhihuColumn = async (id) => {
    const url = `https://zhuanlan.zhihu.com/api/columns/${id}`;
    return await axios({
        url: url,
        method: 'GET'
    });
}

getZhihuColumn('zhihuadmin')
    .then(response => {
        console.log(`Name: ${response.data.name}`);
        console.log(`Intro: ${response.data.intro}`);
    });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const axios = require('axios');

class ApiClient {
    // 将class中的函数定义成async风格
    async getZhihuColumn(id) {
        const url = `https://zhuanlan.zhihu.com/api/columns/${id}`;
        return await axios({
            url: url,
            method: 'GET'
        });
    }
}
// 将立即执行函数定义为async风格
(async () => {
    const client = new ApiClient();
    client.getZhihuColumn('zhihuadmin')
        .then(response => {
            console.log(`Name: ${response.data.name}`);
            console.log(`Intro: ${response.data.intro}`);
            console.log(`Description: ${response.data.description}`)
        });
})();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

async函数中的错误处理

const axios = require('axios');

const getZhihuColumn = async id => {
    const url = `https://zhuanlan.zhihu.com/api/columns/${id}`;
    const response = await axios({
        url: url,
        method: 'GET'
    });
    console.log(response.status, response.statusText); // 200 'OK'
    if (response.status !== 200) {
        throw new Error(response.statusText);
    }
    return await response;
}

// 处理async函数中的错误
const showColunmInfo = async id => {
    try {
        const response = await getZhihuColumn(id);
        console.log(`Name: ${response.data.name}`);
        console.log(`Intro: ${response.data.intro}`);
    } catch (err) {
        console.error(err);
    }
}

showColunmInfo('zhihuadmin11');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

多个await串行

const axios = require('axios');

const sleep = (timeout = 2000) => new Promise(resolve => {
    setTimeout(resolve, timeout);
});

const getZhihuColumn = async (id) => {
    await sleep(3000);
    const url = `https://zhuanlan.zhihu.com/api/columns/${id}`;
    return await axios({
        url: url,
        method: 'GET'
    });
}

// 正确处理多个await操作的串行
const showColumnInfo = async () => {
    console.time('showColumnInfo');
    const feweekly = await getZhihuColumn('feweekly');
    const response = await getZhihuColumn('zhihuadmin');

    console.log(`NAME: ${feweekly.data.name}`);
    console.log(`INTRO: ${feweekly.data.intro}`);

    console.log(`NAME: ${response.data.name}`);
    console.log(`INTRO: ${response.data.intro}`);
    console.timeEnd('showColumnInfo');
}

showColumnInfo();
// 运行结果:
NAME: 前端周刊
INTRO: 在前端领域跟上时代的脚步,广度和深度不断精进
NAME: 知乎小管家说
INTRO: 知乎社区管理团队官方专栏,不定期更新社区管理工作…
showColumnInfo: 6653.686ms
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

多个await并行

const fetch = require('node-fetch');
const sleep = (timeout = 2000) => {
    return new Promise(resolve => setTimeout(resolve, timeout));
};
const getZhihuColumn = async id => {
    // 延迟2秒
    await sleep(2000);
    const url = `https://zhuanlan.zhihu.com/api/columns/${id}`;
    // 获取到专栏数据
    const response = await fetch(url);
    // 进行错误处理
    if (response.status !== 200) {
        throw new Error(response.statusText);
    }
    // 将获取到的数据转为json
    // async函数的返回值是一个Promise
    return await response.json();
}
// 处理async函数中的错误
const getColumnInfo = async () => {
    console.time('getColumnInfo');
    try {
        // 多个await并行
        // 先发送请求再await
        const feweeklyPromise = getZhihuColumn('feweekly');
        const toolingtipsPromise = getZhihuColumn('toolingtips');
        const column = await feweeklyPromise;
        const toolingtips = await toolingtipsPromise;
        console.log(`Title: ${column.title}`);
        console.log(`Intro: ${column.intro}`);

        console.log(`Title: ${toolingtips.title}`);
        console.log(`Intro: ${toolingtips.intro}`);
    } catch (error) {
        console.error(error);
    }
    console.timeEnd('getColumnInfo');
};

getColumnInfo();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
总结:对于多个请求,一般采用并行,即先发送请求在await,加快响应速度。

使用Promise.all实现多个await并行

const fetch = require('node-fetch');
const sleep = (timeout = 2000) => {
    return new Promise(resolve => setTimeout(resolve, timeout));
};
const getZhihuColumn = async id => {
    // 延迟2秒
    await sleep(2000);
    const url = `https://zhuanlan.zhihu.com/api/columns/${id}`;
    // 获取到专栏数据
    const response = await fetch(url);
    // 进行错误处理
    if (response.status !== 200) {
        throw new Error(response.statusText);
    }
    // 将获取到的数据转为json
    // async函数的返回值是一个Promise
    return await response.json();
}
// 处理async函数中的错误
const getColumnInfo = async () => {
    console.time('getColumnInfo');
    try {
        // 使用Promise.all实现多个await并行
        const feweeklyPromise = getZhihuColumn('feweekly');
        const toolingtipsPromise = getZhihuColumn('toolingtips');
        const [column, toolingtips] = await Promise.all([feweeklyPromise, toolingtipsPromise]);
        console.log(`Title: ${column.title}`);
        console.log(`Intro: ${column.intro}`);

        console.log(`Title: ${toolingtips.title}`);
        console.log(`Intro: ${toolingtips.intro}`);
    } catch (error) {
        console.error(error);
    }
    console.timeEnd('getColumnInfo');
};

getColumnInfo();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

实现一个sleep每隔1秒输出 1, 2, 3, 4, 5

实现一个sleep,每隔1秒输出1,2,3,4,5。

function sleep(time) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve();
        }, time);
    });
}

async function say() {
    for (let i = 0; i < 5; i++) {
        await sleep(1000);
        console.log(i + 1);
    }
}

say();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

实现一个红绿灯

实现一个红绿灯:红灯2秒,黄灯1秒,绿灯3秒

/**
 * 🚥效果(红绿灯)
*/

function sleep(time) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve();
        }, time);
    });
}

async function changeColor(color, time) {
    console.log('当前颜色为:', color);
    await sleep(time);
}

async function say() {
    await changeColor('红色', 2000);
    await changeColor('黄色', 1000);
    await changeColor('绿色', 3000);
}

say();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

使用async实现Promise.all的效果

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

// 写法二
let fooPromise = getFoo();
let barPromise = getBar();

let foo = await fooPromise;
let foo = await barPromise;
1
2
3
4
5
6
7
8
9

上面两种写法,getFoo 和 getBar 都是同时触发的,这样就会缩短程序的执行时间。

async/await练习

console.log(2);
async function fn() {
    console.log(1);
    // await会把其下面的代码变成微任务,是异步的
    await '等待';
    console.log(3);
}
fn();
console.log(4);
// 运行结果:
2
1
4
3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function bar() {
    console.log(5);
}
console.log(2);
async function fn() {
    console.log(1);
    // await会把下面的代码变成微任务,是异步的
    await bar(); // bar函数会同步执行
    console.log(3);
}
fn();
console.log(4);
// 运行结果:
2
1
5
4
3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function bar() {
    return new Promise((resolve, reject) => {
        console.log(5);
    });
}
console.log(2);
async function fn() {
    console.log(1);
    // await会把下面的代码变成微任务,是异步的
    await bar(); // 一直处于等待状态
    console.log(3);
}
fn();
console.log(4);
// 运行结果:
2
1
5
4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function bar() {
    return new Promise((resolve, reject) => {
        console.log(5);
        resolve();
    });
}
console.log(2);
async function fn() {
    console.log(1);
    // await会把下面的代码变成微任务,是异步的
    await bar();
    // await下面的代码受bar函数返回的Promise实例的状态影响
    console.log(3);
}
fn();
console.log(4);
// 运行结果:
2
1
5
4
3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

通过上述3个例子可以看出:如果bar函数返回的是一个非Promise值,则微任务中代码会立即执行;如果返回Promise实例,则会等到Promise状态变化后(变为成功态)才会执行。

function bar() {
    return new Promise((resolve, reject) => {
        console.log(5);
        setTimeout(() => {
            resolve();
        }, 1000);
    });
}
console.log(2);
async function fn() {
    console.log(1);
    // await会把下面的代码变成微任务,是异步的
    await bar();
    // 如果bar函数执行中有异步,那么await下面的代码会等到函数中的异步执行完毕后,才会执行这个微任务
    console.log(3); // 隔1秒后再输出3
}
fn();
console.log(4);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function bar(time) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve();
        }, time);
    });
}
console.log(2);
async function fn() {
    console.log(1);
    // await会把下面的代码变成微任务,是异步的
    await bar(1000);
    await bar(3000);
    // 4秒后才会输出3
    // 如果bar函数执行中有异步,那么await下面的代码会等到函数中的异步执行完毕后,才会执行这个微任务
    console.log(3);
}
fn();
console.log(4);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

bluebird使用

const bluebird = require('bluebird');

//结合 await 和任意兼容 .then() 的代码
const main = async () => {
  console.log('waiting...');
  await bluebird.delay(2000);
  console.log('done!');
}

main();
1
2
3
4
5
6
7
8
9
10

在循环中使用await

串行

const fetch = require('node-fetch');
const bluebird = require('bluebird');

async function getZhihuColumn(id) {
  await bluebird.delay(1000);
  const url = `https://zhuanlan.zhihu.com/api/columns/${id}`;
  const response = await fetch(url);
  return await response.json();
}

const getColumnInfo = async () => {
  console.time('getColumnInfo');
  const names = ['feweekly', 'toolingtips'];
  //在 for 循环中正确的使用 await
  // 串行
  for (const name of names) {
    const column = await getZhihuColumn(name);
    console.log(`Name: ${column.title}`);
    console.log(`Intro: ${column.intro}`);
  }
  console.timeEnd('getColumnInfo');
};

getColumnInfo();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

并行

const fetch = require('node-fetch');
const bluebird = require('bluebird');

async function getZhihuColumn(id) {
  await bluebird.delay(1000);
  const url = `https://zhuanlan.zhihu.com/api/columns/${id}`;
  const response = await fetch(url);
  return await response.json();
}

const getColumnInfo = async () => {
  console.time('getColumnInfo');
  const names = ['feweekly', 'toolingtips'];
  const promises = names.map(x => getZhihuColumn(x));
  // 在for循环中正确的使用await
  // 并行,先发送请求再await
  for (const promise of promises) {
    const column = await promise;
    console.log(`Name: ${column.name}`);
    console.log(`Intro: ${column.intro}`);
  }
  console.timeEnd('getColumnInfo');
};

getColumnInfo();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const sleep = (time = 100) => new Promise(resolve => {
    setTimeout(resolve(time + 200), timeout);
});

const step1 = async (time) => {
    console.log(`step1 with ${time}`);
    return sleep(time);
}

const step2 = async (time) => {
    console.log(`step2 with ${time}`);
    return sleep(time);
}

const step3 = async (time) => {
    console.log(`step3 with ${time}`);
    return sleep(time);
}

function test() {
    console.log('test start');
    console.time('test');
    const time1 = 500;
    step1(time1)
        .then(time2 => step2(time2))
        .then(time3 => step2(time3))
        .then(res => {
            console.log(`result is ${res}`);
            console.timeEnd('test');
        });
}

test();
// 运行结果:
test start
step1 with 500
step2 with 700
step2 with 900
result is 1100
test: 3.451ms
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
const sleep = (time = 100) => new Promise(resolve => {
    setTimeout(resolve(time + 200), timeout);
});

const step1 = async (time) => {
    console.log(`step1 with ${time}`);
    return sleep(time);
}

const step2 = async (time) => {
    console.log(`step2 with ${time}`);
    return sleep(time);
}

const step3 = async (time) => {
    console.log(`step3 with ${time}`);
    return sleep(time);
}
// async和await方式更加清晰
const test = async () => {
    console.log('test start111');
    console.time('test');
    const time1 = 500;
    const time2 = await step1(time1);
    const time3 = await step1(time2);
    const res = await step1(time3);
    console.log(`result is ${res}`);
    console.timeEnd('test');
}

test();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

async实现原理

async函数原理就是Generator函数和自动执行器包装了一下,简而言之就是自带执行器的Generator函数,而Generator函数需要调用next方法才能执行。

就是将Generator函数和自动执行器,包装在一个函数里。

async function fn(args) {
    // ...
}
function fn(args) {
    return spawn(function* () {
        // ...
    })
}
1
2
3
4
5
6
7
8

所有的async函数都可以写成上面的第二种形式,其中spawn函数就是自动执行器。

function spawn(genF) {
  return new Promise(function(resolve, reject) {
    const gen = genF();
    function step(nextF) {
      let next;
      try {
        next = nextF();
      } catch(e) {
        return reject(e);
      }
      if(next.done) {
        return resolve(next.value);
      }
      Promise.resolve(next.value).then(function(v) {
        step(function() { return gen.next(v); });
      }, function(e) {
        step(function() { return gen.throw(e); });
      });
    }
    step(function() { return gen.next(undefined); });
  });
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

参考文档

  1. 理解 async/await
  2. ES6系列文章 异步神器async-await
  3. 一次性让你懂async/await,解决回调地狱
  4. 理解 JavaScript 的 async/await
  5. 为什么说Async/Await让代码更简洁?
  6. async 函数的含义和用法
  7. ES6 系列之我们来聊聊 Async