[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());
};
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());
};
2
3
4
5
6
7
相较于Generator,async函数的改进在于下面几点:
- 内置执行器:
Generator
函数的执行必须依靠执行器,所以才有了Thunk函数和co模块。而async函数自带执行器,不需要像Generator去调用next方法,遇到await等待,当前的异步操作完成就往下执行。调用方式跟普通函数的调用一样。 - 更好的语义:async关键字取代Generator函数的星号*,await取代Generator的yield,
async和await
相较于* 和yield
更加语义化;async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。 - 更广的适用性:co模块约定,
yield
命令后面只能是Thunk
函数或Promise
对象。而async
函数的await
命令后面则可以是Promise
或者原始类型的值(Number,string,boolean,但这时等同于同步操作); - 返回值是Promise:
async
函数返回值是Promise
对象(默认是成功态,并且把该函数的返回值传给then的第一个参数),比Generator
函数返回的Iterator
对象方便,可以直接使用then()
方法进行下一步调用。 - 代码逻辑更清晰:
async/await
的优势在于能更好的处理then
链,特别是有多个Promise
组成的then
链的时候,优势就体现出来了。
async是ES7新出的特性,表明当前函数是异步函数,不会阻塞线程导致后续代码停止运行。
const asyncFn = async () => {
return '我后执行'
}
asyncFn().then(result => {
console.log(result);
})
console.log('我先执行');
// 运行结果:
我先执行
我后执行
2
3
4
5
6
7
8
9
10
虽然是上面asyncFn()先执行,但是已经被定义为异步函数了,不会影响后续代码的执行。
const asyncFn = async () => {
return '我后执行'
};
console.log(asyncFn());
2
3
4
运行结果:
Promise {<resolved>: "我后执行"}
需要注意: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();
2
3
4
5
async
async用来声明函数是异步的,定义的函数会返回一个Promise
对象,可以使用then方法添加回调函数。
async function fn(params) {
return 'demo1';
}
fn().then(data => {
console.log(data); // 'demo1'
});
2
3
4
5
6
7
async function fn(params) {
console.log('111');
}
fn().then(v => console.log(v));
2
3
4
运行结果:
111
undefined
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'); // 先获取新闻内容
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));
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));
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
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');
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}`);
});
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}`)
});
})();
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');
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
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();
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
使用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();
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();
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();
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;
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
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
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
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
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);
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);
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();
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();
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();
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
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();
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* () {
// ...
})
}
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); });
});
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22