异步函数

异步函数

异步函数,也称为“async/await”(语法关键字),是 ES6 期约模式在 ECMAScript 函数中的应用。这个特性从行为和语法上都增强了 JavaScript,让以同步方式写的代码能够异步执行。

async

使用 async 关键字可以让函数具有异步特征,但总体上其代码仍然是同步求值的。而在参数或闭包方面,异步函数仍然具有普通 JavaScript 函数的正常行为。

1
2
3
4
5
6
7
async function foo() {
console.log(1);
}
foo();
console.log(2);
// 1
// 2

不过,异步函数如果使用 return 关键字返回了值(如果没有 return 则会返回 undefined),这个值会被 Promise.resolve()包装成一个期约对象。异步函数始终返回期约对象。

1
2
3
4
5
6
7
8
9
10
async function foo() {
console.log(1);
return 3;
}
// 给返回的期约添加一个解决处理程序
foo().then(console.log);
console.log(2);
// 1
// 2
// 3

异步函数的返回值期待(但实际上并不要求)一个实现 thenable 接口的对象,但常规的值也可以。如果返回的是实现 thenable 接口的对象,则这个对象可以由提供给 then()的处理程序“解包”。如果不是,则返回值就被当作已经解决的期约。

await

因为异步函数主要针对不会马上完成的任务,所以自然需要一种暂停和恢复执行的能力。使用 await 关键字可以暂停异步函数代码的执行,等待期约解决。

await 关键字期待(但实际上并不要求)一个实现 thenable 接口的对象,但常规的值也可以。如果是实现 thenable 接口的对象,则这个对象可以由 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
// 等待一个原始值
async function foo() {
console.log(await 'foo');
}
foo();
// foo
// 等待一个没有实现 thenable 接口的对象
async function bar() {
console.log(await ['bar']);
}
bar();
// ['bar']
// 等待一个实现了 thenable 接口的非期约对象
async function baz() {
const thenable = {
then(callback) { callback('baz'); }
};
console.log(await thenable);
}
baz();
// baz
// 等待一个期约
async function qux() {
console.log(await Promise.resolve('qux'));
}
qux();
// qux

停止和恢复执行

下面的例子中按顺序调用了 3 个函数,但它们的输出结果顺序是相反的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
async function foo() {
console.log(await Promise.resolve('foo'));
}
async function bar() {
console.log(await 'bar');
}
async function baz() {
console.log('baz');
}
foo();
bar();
baz();
// baz
// bar
// foo

async/await 中真正起作用的是 await。async 关键字,无论从哪方面来看,都不过是一个标识符。异步函数如果不包含 await 关键字,其执行基本上跟普通函数没有什么区别。

JavaScript 运行时在碰到 await 关键字时,会记录在哪里暂停执行。等到 await 右边的值可用了,JavaScript 运行时会向消息队列中推送一个任务,这个任务会恢复异步函数的执行。因此,即使 await 后面跟着一个立即可用的值,函数的其余部分也会被异步求值。

1
2
3
4
5
6
7
8
9
10
11
12
async function foo() {
console.log(2);
await null;
console.log(4);
}
console.log(1);
foo();
console.log(3);
// 1
// 2
// 3
// 4

注意点

  1. 必须处理错误

在 async 函数中,如果 await 的 Promise 失败了且没有 try…catch 保护,整个函数的后续逻辑都会死掉。

1
2
3
4
5
6
7
8
9
10
11
const err = Promise.reject("2");

async function foo() {
console.log("1");
await err;
console.log("3");
}

foo().catch(console.log);
// 1
// 2
  1. 循环中的 await

forEach 循环里使用 await,它不会按顺序等待,而是会“并发”。如果需要按顺序执行,请使用传统的 for…of 循环。

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
const now = Date.now();
const sleep = (ms) =>
new Promise((resolve) =>
setTimeout(() => {
const end = Date.now();
resolve(Math.floor((end - now) / 1000));
}, ms),
);

Array.from({ length: 10 }, (_, i) => i).forEach(async (i) => {
const res = await sleep(i * 1000);
console.log(res);
});
// 0
// 1
// 2
// 3
// 4
// 5
// 6
// 7
// 8
// 9

async function main() {
for (let i = 0; i < 10; i++) {
const res = await sleep(i * 1000);
console.log(res);
}
}
main();

// 0
// 1 = 0 + 1
// 3 = 0 + 1 + 2
// 6 = 0 + 1 + 2 + 3
// ...
// 45 = 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9

参考资料:

  • JavaScript高级程序设计(第4版) 11.3 异步函数

异步函数
https://www.my-web.cn/js/async/
发布于
2026年8月6日
许可协议