引言

await 是 ES2017 引入的语法糖,用于简化 Promise 的使用。

在学习 await 之前,我们先来看一段生成器(Generator)的代码:

1
2
3
4
5
6
function* generator() {
yield 1;
yield 2;
yield 3;
return 4;
}

如果想获取这个迭代器对象的最终返回值,我们需要手动调用 next() 方法:

1
2
3
4
5
const iterator = generator();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: 4, done: true }

这种方式较为繁琐,我们可以编写一个自动执行 next() 的函数:

1
2
3
4
5
6
7
8
function run(generator) {
const gen = generator();
let value = gen.next();
while (!value.done) {
value = gen.next();
}
return value.value;
}

使用 run 方法执行生成器:

1
2
const result = run(generator);
console.log(result); // 4

这样就能直接获取生成器的最终返回值。


进一步思考,如果生成器中 yield 后面是一个 Promise,而我们仍希望直接获取最终返回值(不等待 Promise 完成),可以这样写:

1
2
3
4
5
6
function* generator() {
yield Promise.resolve(1);
yield Promise.resolve(2);
yield Promise.resolve(3);
return 4;
}

调用方式与同步 yield 相同:

1
2
3
4
5
const gen = generator();
gen.next(); // { value: Promise {1}, done: false }
gen.next(); // { value: Promise {2}, done: false }
gen.next(); // { value: Promise {3}, done: false }
gen.next(); // { value: 4, done: true }

使用 run 方法:

1
2
const result = run(generator);
console.log(result); // 4

这种方式并未等待 Promise 完成。如果我们需要在每一步等待 Promise 完成,则需调整调用方式:

1
2
3
4
5
6
function* generator() {
const value = yield Promise.resolve(1);
const value2 = yield Promise.resolve(value);
const value3 = yield Promise.resolve(value2);
return value3;
}

最简单的调用方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const gen = generator();
const step1 = gen.next(); // { value: Promise {1}, done: false }
console.log("step1:", step1);
step1.value.then((result) => {
console.log("回调结果:", result); // 需要外部传回 value
const step2 = gen.next(result); // { value: Promise {1}, done: false }
console.log("step2:", step2);
step2.value.then((result2) => {
console.log("回调结果2:", result2);
const step3 = gen.next(result2); // { value: Promise {1}, done: false }
console.log("step3:", step3);
step3.value.then((result3) => {
console.log("回调结果3:", result3);
const step4 = gen.next(result3); // { value: undefined, done: true }
console.log("step4:", step4);
});
});
});

这种嵌套回调的方式显然非常繁琐,我们急需一个自动执行 next() 的执行器函数:

1
2
3
4
5
6
7
8
9
10
11
12
function run(generator) {
const iterator = generator();
function next(result) {
if (result.done) {
return Promise.resolve(result.value);
}
return result.value.then((value) => {
return next(iterator.next(value));
});
}
return next(iterator.next());
}

关键点说明
在异步操作中实现递归调用时,需要通过 Promise 链和微任务队列来维持执行流程和返回值传递。由于递归调用被放入微任务队列中,而同步代码执行完毕后微任务尚未执行,因此无法直接获取返回值。此时,必须将递归调用的返回值包装为 Promise,并通过 .then() 方法获取最终结果。

await 语法糖

基于我们之前实现的 run 执行器,让我们继续探讨 async/await 的诞生。

从 Generator 执行器到 async/await

我们刚才实现的 run 函数,本质上就是一个异步生成器的执行器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function run(generator) {
const iterator = generator();
function next(result) {
if (result.done) {
return Promise.resolve(result.value);
}
return result.value.then((value) => {
return next(iterator.next(value));
});
}
return next(iterator.next());
}

// 使用示例
function* asyncGenerator() {
const value = yield Promise.resolve(1);
const value2 = yield Promise.resolve(value + 1);
const value3 = yield Promise.resolve(value2 + 1);
return value3;
}

run(asyncGenerator).then((result) => {
console.log(result); // 3
});

async/await 的诞生

ES2017 引入了 async/await,本质上就是为我们刚才的”生成器 + 执行器”模式提供了语法糖:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 之前的生成器方式
function* asyncGenerator() {
const value = yield Promise.resolve(1);
const value2 = yield Promise.resolve(value + 1);
return value2;
}

// 使用 async/await 重写
async function asyncFunction() {
const value = await Promise.resolve(1);
const value2 = await Promise.resolve(value + 1);
return value2;
}

async/await 的优势

1. 更简洁的语法

1
2
3
4
5
6
7
8
9
10
11
12
13
// 之前需要执行器
run(function* () {
const user = yield fetchUser();
const posts = yield fetchPosts(user.id);
return posts;
});

// 现在直接使用
async function getUserPosts() {
const user = await fetchUser();
const posts = await fetchPosts(user.id);
return posts;
}

2. 更好的错误处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 生成器方式需要手动处理
function* asyncGenerator() {
try {
const value = yield Promise.reject(new Error("失败"));
} catch (error) {
console.error(error);
}
}

// async/await 可以用熟悉的 try-catch
async function asyncFunction() {
try {
const value = await Promise.reject(new Error("失败"));
} catch (error) {
console.error(error);
}
}

3. 自动返回 Promise

1
2
3
4
5
6
7
8
async function example() {
return 42;
}

// 等价于
function example() {
return Promise.resolve(42);
}

实际应用对比

场景:顺序执行多个异步操作

使用 Generator + 执行器:

1
2
3
4
5
6
7
8
9
10
function* fetchUserData() {
const user = yield fetch("/api/user");
const orders = yield fetch(`/api/orders/${user.id}`);
const profile = yield fetch(`/api/profile/${user.id}`);
return { user, orders, profile };
}

run(fetchUserData).then((data) => {
console.log(data);
});

使用 async/await:

1
2
3
4
5
6
7
8
9
10
11
async function fetchUserData() {
const user = await fetch("/api/user");
const orders = await fetch(`/api/orders/${user.id}`);
const profile = await fetch(`/api/profile/${user.id}`);
return { user, orders, profile };
}

// 直接调用
fetchUserData().then((data) => {
console.log(data);
});

理解 async/await 的本质

可以这样理解:

  • async 关键字:标记函数为”需要特殊处理的生成器”
  • await 关键字:相当于 yield,但会自动处理 Promise 的解析和值传递
  • JavaScript 引擎:内置了我们之前手写的 run 执行器逻辑

现代用法示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 并行请求优化
async function fetchUserData() {
const [user, settings] = await Promise.all([
fetch("/api/user"),
fetch("/api/settings"),
]);

return { user, settings };
}

// 错误处理
async function fetchWithRetry(url, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
const response = await fetch(url);
return await response.json();
} catch (error) {
if (i === retries - 1) throw error;
await new Promise((resolve) => setTimeout(resolve, 1000 * (i + 1)));
}
}
}

总结

async/await 的出现,让我们能够以近乎同步的方式编写异步代码,大大提升了代码的可读性和可维护性。它背后的思想正是我们之前探索的”生成器 + 执行器”模式,只是现在由语言层面原生支持,不再需要手动实现执行器逻辑。