JavaScript异步(三)async与await

本文最后更新于:8 个月前

async是asynchronous的缩写,意为异步。sync是synchronous的缩写,意为同步
async函数的返回值是什么?async/await组合的使用方式,await放在不同位置时代码的执行顺序有什么区别?await一定会阻塞代码执行吗?

JavaScript异步(三)async与await

async 和 await 其实就是 Generator 和 Promise 的语法糖。async 函数和普通 函数没有什么不同,他只是表示这个函数里有异步操作的方法,并返回一个 Promise 对象

1
2
3
4
5
6
7
8
9
10
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}
// Promise 写法
async function async1() {
console.log("async1 start");
Promise.resolve(async2()).then(() => console.log("async1 end"));
}

一、async

1.1、async函数

async作为一个关键字,放在函数前面,表示该函数是一个异步函数。这是aysnc关键字的用途。

async 函数是使用async关键字声明的函数。async 函数是 AsyncFunction 构造函数的实例,并且其中允许使用 await 关键字。asyncawait 关键字让我们可以用一种更简洁的方式写出基于 Promise 的异步行为,而无需刻意地链式调用 promise

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function resolveAfter2Seconds() {
return new Promise(resolve => {
setTimeout(() => {
resolve('resolved');
}, 2000);
});
}
async function asyncCall() {
const result = await resolveAfter2Seconds() //result直接获取了PromiseResult
console.log(result)
console.log('calling')
}
asyncCall()
//resolved
//calling

//同等情况下,如果使用Promise链式调用,代码如下
resolveAfter2Seconds().then(res=>{
console.log(res)
})
console.log('calling')

1.1、async函数返回值

async 函数的返回值是一个 promise 对象

async可以指定返回一个Promise对象。但是如果 async 函数没有指定返回的Promise对象,JavaScript也会隐式地用Promise.resolve方法将函数返回值包装成一个Promise对象

1
2
3
4
5
async function asyncFn(){
return 'hi'
}
var res = asyncFn()
res //Promise {<fulfilled>: 'hi'}
1
2
3
4
5
6
//函数没有指明返回值时,默认返回 undefined
async function asyncFn(param){
console.log(param)
}
var res = asyncFn('hi')
res //Promise {<fulfilled>: undefined}

1.3、async解决“回调地狱”

npm提供了async包,它包含了seriesparallel等方法。async - npm (npmjs.com)

场景示例:先后读取两个文本文件,然后按顺序显示

使用async.series()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var fs  = require('fs');
var async = require('async');

async.series([
function(cb){
fs.readFile('a-text-file.txt','utf8',cb);
},
function(cb){
fs.readFile('another-text-file.txt','utf8',cb);
}
],function(err,value){
if(err) console.error(err);
else{
console.log('File 1:',values[0]);
console.log('File 2:',values[1]);
}
});

再来看看仅仅使用回调的代码,与上面代码比较,此处代码的可读性差,串行读取效率低。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var fs  = require('fs');
fs.readFile(
'a-text-file.txt', //读取第一个文本
'utf8',
function(err,text) { //读取完成,回调
if (err) {
console.error(err);
} else {
console.log('First text file:',text); //显示第一段文本
fs.readFile(
'another-text-file.txt', //读取第二个文本
'utf8',
function(err,text) { //读取完成,回调
if (err) {
console.error(err);
} else {
console.log('Second text file:',text); //显示第二段文本
}
}
);
}
}
);

二、await关键字

await 关键字只能放到 async 函数里面

2.1、await表达式

await后面可以是Promise实例,异步函数、thenable、普通表达式

2.1.1 await+promise对象

await+promise对象表达式,会暂停整个async函数的执行进程并让出进程控制权,只有当其等待的基于Promise的异步操作(这一点很重要!)被resolve或reject后才恢复async函数的进程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function doubleAfter2seconds(num){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(2 * num)
},2000);
})
}
async function testResult(){
var result= await doubleAfter2seconds(10); //await+promise对象表达式,阻塞后面代码执行
console.log(result);
console.log('hi')
}
testResult()
//20
//hi

2.1.2 await+普通表达式

await后面接普通表达式的时候,并不会起到阻塞代码的效果!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//下面这个例子说明:await+普通表达式(非promise对象),起不到阻塞代码的作用
function resolveAfter2Seconds() {
setTimeout(() => {
console.log('resolved');
}, 2000)
}
async function asyncCall() {
await resolveAfter2Seconds() //并没有阻塞后面代码的执行
console.log('calling');
}
asyncCall()
//calling
//resolved

// resolveAfter2Seconds()被当做同步函数处理,setTimeout也直接被加入了宏任务队列
// 所以是按照事件循环机制那一套顺序输出

2.1.3 await+异步函数

1
2
3
4
5
6
7
8
9
10
11
12
async function resolveAfter2Seconds() {
setTimeout(() => {
console.log('resolved');
}, 2000)
}
async function asyncCall() {
await resolveAfter2Seconds()
console.log('calling');
}
asyncCall()
//calling
//resolved

虽然resolveAfter2Seconds被async关键字声明为了异步函数,但是await resolveAfter2Seconds()并没有起到阻塞同步代码的作用!

2.2、「try-catch」捕获错误

使用了async+await,就不需要再用Promise.then().catch()的处理方式,但同时也意味着,无法再使用catch(()=>{})的方式捕获错误。不过这里可以使用「try-catch」的方式捕获错误。

1
2
3
4
5
6
7
8
9
async function testResult(){
try{
let result1 = await doubleAfter2seconds(10)
let result2= await doubleAfter2seconds(20)
console.log(result1+result2)
}catch(err){
console.error(err);
}
}

需要注意,这里的catch()Promise.catch()的形式是有所区别的。两者比较如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
async function testResult(){
try{
let result1 = await doubleAfter2seconds(10)
let result2= await doubleAfter2seconds(20)
console.log(result1+result2)
}catch(err){
console.error(err);
}
}

var p = new Promise(()=>{...})
p.then((res)=>{
...
}).catch((err)=>{
...
})

2.3、await 和 并行

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
function resolveAfter2Seconds() {
console.log("starting slow promise");
return new Promise((resolve) => {
setTimeout(() => {
resolve("slow");
console.log("slow promise is done");
}, 2000);
});
}

function resolveAfter1Second() {
console.log("starting fast promise");
return new Promise((resolve) => {
setTimeout(() => {
resolve("fast");
console.log("fast promise is done");
}, 1000);
});
}

//串行执行
async function sequentialStart() {
console.log("==SEQUENTIAL START=="); //翻译:相继启动

// 1. Execution gets here almost instantly
const slow = await resolveAfter2Seconds();
console.log(slow); // 2. this runs 2 seconds after 1.

const fast = await resolveAfter1Second();
console.log(fast); // 3. this runs 3 seconds after 1.
}

async function concurrentStart() {
console.log("==CONCURRENT START with await=="); //翻译:同时启动await
const slow = resolveAfter2Seconds(); // starts timer immediately
const fast = resolveAfter1Second(); // starts timer immediately

// 1. Execution gets here almost instantly
console.log(await slow); // 2. this runs 2 seconds after 1.
console.log(await fast); // 3. this runs 2 seconds after 1., immediately after 2., since fast is already resolved
}

function concurrentPromise() {
console.log("==CONCURRENT START with Promise.all==");
return Promise.all([resolveAfter2Seconds(), resolveAfter1Second()]).then(
(messages) => {
console.log(messages[0]); // slow
console.log(messages[1]); // fast
}
);
}

//真正意义上的并行执行,先执行完的立即输出,不用等全部结束后再输出
async function parallel() {
console.log("==PARALLEL with await Promise.all==");

// Start 2 "jobs" in parallel and wait for both of them to complete
await Promise.all([
(async () => console.log(await resolveAfter2Seconds()))(),
(async () => console.log(await resolveAfter1Second()))(),
]);
}

sequentialStart(); // after 2 seconds, logs "slow", then after 1 more second, "fast"

concurrentStart() // after 2 seconds, logs "slow" and then "fast"

concurrentPromise() // same as concurrentStart

parallel() // truly parallel: after 1 second, logs "fast", then after 1 more second, "slow"

JavaScript异步(三)async与await
http://timegogo.top/2023/02/08/JavaScript/JavaScript异步(三)async与await/
作者
丘智聪
发布于
2023年2月8日
更新于
2023年7月16日
许可协议