async and await

从字面上,async 是“异步”的简写,而 await 可以认为是 async wait 的简写。所以应该很好理解 async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成。

另外还有一个很有意思的语法规定,await 只能出现在 async 函数中。然后细心的朋友会产生一个疑问,如果 await 只能出现在 async 函数中,那这个 async 函数应该怎么调用?

如果需要通过 await 来调用一个 async 函数,那这个调用的外面必须得再包一个 async 函数,然后就进入了死循环。究竟底层是怎么实现的呢?

PS:本文内容参考了理解 JavaScript 的 async/await

async 的作用

为了了解 async 的作用,我们先来了解 async 函数的返回值。

执行下面这段代码:

1
2
3
4
5
6
async function testAsync() {
return "hello async";
}

const result = testAsync();
console.log(result); // Promise { 'hello async' }

看到输出,我们可以明确知道输出的是一个 Promise 对象。

其实在 MDN 关于 async 的说明文档中也体现了这一点:

The async function declaration defines an asynchronous function, which returns an AsyncFunction object. An asynchronous function is a function which operates asynchronously via the event loop, using an implicit Promise to return its result. But the syntax and structure of your code using async functions is much more like using standard synchronous functions.

async 函数(包含函数语句、函数表达式、Lambda表达式)会返回一个 Promise 对象。如果在函数中 return 一个变量,async 会直接通过 Promise.resolve() 封装成 Promise 对象。如果没有返回值,则会返回 Promise.resolve(undefined)。

async 函数返回的是一个 Promise 对象,所以在最外层不能用 await 获取其返回值的情况下,因此也可以通过 then 来执行这个 Promise:

1
2
3
testAsync().then(v => {
console.log(v); // 输出 hello async
});

联想一下 Promise 的特点——无等待,所以在没有 await 的情况下执行 async 函数,它会立即执行,返回一个 Promise 对象,并且,绝不会阻塞后面的语句。这和普通返回 Promise 对象的函数并无二致。

await 的作用

一般来说,都认为 await 是在等待一个 async 函数完成。
同样我们可以通过 MSD 关于 await 的说明文档进行了解:

The await operator is used to wait for a Promise. It can only be used inside an async function.

可知 await 等待的是一个表达式,这个表达式的计算结果是 Promise 对象或者其它值(换句话说,就是没有特殊限定)。
因为 async 函数返回一个 Promise 对象,所以 await 可以用于等待一个 async 函数的返回值——这也可以说是 await 在等 async 函数,但要清楚,它等的实际是一个返回值。

但值得注意的是,await 虽然一定要在 async 函数中执行,但是不仅可以用于等 Promise 对象,还可以等任意表达式的结果,比如接普通函数调用或者直接量的。下面这个示例完全可以正确运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function getSomething() {
return "something";
}

async function testAsync() {
return Promise.resolve("hello async");
}

async function test() {
const v1 = await getSomething();
const v2 = await testAsync();
console.log(v1, v2);
}

test();

await 是个运算符,其具体的运算结果取决于它等的东西。

  • 如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是它等到的东西。
  • 如果它等到的是一个 Promise 对象,await 会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。

由于 await 可能会阻塞后面代码的运行,因此 await 必须用在 async 函数中的原因(async 内部所有的阻塞都被封装在一个 Promise 对象中异步执行)。

async/await 的作用

async 会将其后的函数(函数表达式或 Lambda)的返回值封装成一个 Promise 对象,而 await 会等待这个 Promise 完成,并将其 resolve 的结果返回出来。

我们先通过 Promise 的方式封装一个异步操作:

1
2
3
4
5
6
7
8
9
function takeLongTime() {
return new Promise(resolve => {
setTimeout(() => resolve("long_time_value"), 1000);
});
}

takeLongTime().then(v => {
console.log("got", v);
});

如果改用 async/await ,则是:

1
2
3
4
5
6
7
8
9
10
11
12
function takeLongTime() {
return new Promise(resolve => {
setTimeout(() => resolve("long_time_value"), 1000);
});
}

async function test() {
const v = await takeLongTime();
console.log(v);
}

test();

可以看到这两段代码,两种方式对异步调用的处理(实际就是对 Promise 对象的处理)差别并不明显,甚至使用 async/await 还需要多写一些代码。
但async/await 的优势在于处理 then 链,单一的 Promise 链并不能发现 async/await 的优势,但是,如果需要处理由多个 Promise 组成的 then 链的时候,优势就能体现出来了:

假设一个业务,分多个步骤完成,每个步骤都是异步的,而且需要使用上一个步骤的结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function step1(n) {
console.log(`step1 with ${n}`);
return takeLongTime(n);
}


function step2(m, n) {
console.log(`step2 with ${m} and ${n}`);
return takeLongTime(m + n);
}


function step3(k, m, n) {
console.log(`step3 with ${k}, ${m} and ${n}`);
return takeLongTime(k + m + n);
}

如果通过 Promise 来实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function doIt() {
console.time("doIt");
const time1 = 300;
step1(time1)
.then(time2 => {
return step2(time1, time2)
.then(time3 => [time1, time2, time3]);
})
.then(times => {
const [time1, time2, time3] = times;
return step3(time1, time2, time3);
})
.then(result => {
console.log(`result is ${result}`);
console.timeEnd("doIt");
});
}


doIt();

而使用 async/await 则是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
async function doIt() {
console.time("doIt");
const time1 = 300;
const time2 = await step1(time1);
const time3 = await step2(time1, time2);
const result = await step3(time1, time2, time3);
console.log(`result is ${result}`);
console.timeEnd("doIt");
}


doIt();

// step1 with 300
// step2 with 800 = 300 + 500
// step3 with 1800 = 300 + 500 + 1000
// result is 2000
// doIt: 2907.387ms

可以看到通过 async / await 方式实现的代码更加简洁,阅读性更好。