第 16 章 async/await 与 Generator
第 16 章 async/await 与 Generator
async/await 是 Promise 的"语法糖",让异步代码看起来像同步代码。Generator 则是一种可以暂停和恢复的函数。它们都是 ES6+ 引入的强大特性。
16.1 async 函数
async 关键字:函数返回 Promise
async 关键字用于声明一个异步函数。异步函数总是返回一个 Promise,即使你显式返回的是一个非 Promise 值:
1
2
3
4
5
6
7
| async function greet(name) {
return "你好," + name + "!";
}
console.log(greet("小明")); // Promise { "你好,小明!" }
greet("小明").then(console.log); // "你好,小明!"
|
如果 async 函数显式返回一个 Promise,那就直接返回那个 Promise:
1
2
3
4
5
| async function fetchData() {
return fetch("/api/data").then(res => res.json());
}
console.log(fetchData()); // Promise(来自 fetch)
|
await 关键字:等待 Promise 结果
await 关键字只能在 async 函数中使用,它会暂停函数执行,等待 Promise 结果,然后返回该结果:
1
2
3
4
5
| async function fetchUser() {
const response = await fetch("/api/user/1");
const user = await response.json();
return user;
}
|
对比 Promise 写法:
1
2
3
4
5
6
7
8
9
10
11
12
| // Promise 写法
function fetchUser() {
return fetch("/api/user/1")
.then(response => response.json());
}
// async/await 写法(更直观)
async function fetchUser() {
const response = await fetch("/api/user/1");
const user = await response.json();
return user;
}
|
💡 await 暂停的是 async 函数内部的执行,不会暂停外部代码!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| async function demo() {
console.log("1");
await Promise.resolve();
console.log("3");
}
console.log("2");
demo();
console.log("4");
// 输出:
// 2
// 1
// 4
// 3
|
async/await 处理错误:try…catch
错误处理是 async/await 的一大优势——可以用 try...catch 来捕获异步错误:
1
2
3
4
5
6
7
8
9
10
11
12
| async function fetchData() {
try {
const response = await fetch("/api/data");
if (!response.ok) {
throw new Error("HTTP 错误:" + response.status);
}
const data = await response.json();
console.log(data);
} catch (error) {
console.log("获取数据失败:" + error.message);
}
}
|
对比 Promise 的 .catch:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| // Promise 写法
fetch("/api/data")
.then(response => {
if (!response.ok) throw new Error("HTTP 错误");
return response.json();
})
.then(data => console.log(data))
.catch(error => console.log(error));
// async/await 写法
async function fetchData() {
try {
const response = await fetch("/api/data");
if (!response.ok) throw new Error("HTTP 错误");
const data = await response.json();
console.log(data);
} catch (error) {
console.log(error);
}
}
|
💡 try...catch 可以捕获同步和异步错误,比 .catch() 更统一。
16.2 async/await 进阶
并行执行:Promise.all + await
如果你有多个独立的异步操作想要并行执行,不要用 await 一个一个等待,要用 Promise.all:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| // ❌ 串行执行(慢)
async function fetchAllSlow() {
const user = await fetchUser(); // 等 1 秒
const posts = await fetchPosts(); // 再等 1 秒
const comments = await fetchComments(); // 又等 1 秒
// 总共 3 秒
}
// ✅ 并行执行(快)
async function fetchAllFast() {
const [user, posts, comments] = await Promise.all([
fetchUser(), // 同时开始
fetchPosts(), // 同时开始
fetchComments() // 同时开始
]);
// 总共 1 秒
}
|
💡 记住:await 会暂停函数执行,如果多个操作之间没有依赖,就用 Promise.all 并行执行!
循环中的 await:串行问题
在 for 循环中使用 await 是串行执行的:
1
2
3
4
5
6
7
8
9
10
| async function processItems(items) {
const results = [];
for (const item of items) {
const result = await processItem(item); // 每次都要等待
results.push(result);
}
return results;
}
|
如果你想并行处理,可以用 map + Promise.all:
1
2
3
4
5
| async function processItems(items) {
const promises = items.map(item => processItem(item));
const results = await Promise.all(promises);
return results;
}
|
forEach 中的 await:失效问题与解决方案
forEach 中的 await 不起作用! 这是因为 forEach 不会等待 Promise 完成:
1
2
3
4
5
6
7
8
9
10
11
| async function demo() {
const items = [1, 2, 3];
// ❌ 错误:不会等待
items.forEach(async (item) => {
await processItem(item);
console.log("完成:" + item);
});
console.log("全部完成?"); // 这行会先执行!
}
|
forEach 不会处理 async 函数的返回值,所以 await 在这里毫无意义。
正确做法:用 for...of 循环:
1
2
3
4
5
6
7
8
9
10
11
| async function demo() {
const items = [1, 2, 3];
// ✅ 正确:用 for...of
for (const item of items) {
await processItem(item);
console.log("完成:" + item);
}
console.log("全部完成!"); // 这行会在所有完成后执行
}
|
async/await vs Promise.then 对比
两种写法都可以实现异步流程控制:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| // Promise 链式调用
function fetchUserPromise() {
return fetch("/api/user")
.then(response => response.json())
.then(user => fetch(`/api/posts?userId=${user.id}`))
.then(response => response.json())
.then(posts => console.log(posts));
}
// async/await
async function fetchUserAsync() {
const userResponse = await fetch("/api/user");
const user = await userResponse.json();
const postsResponse = await fetch(`/api/posts?userId=${user.id}`);
const posts = await postsResponse.json();
console.log(posts);
}
|
选择建议:
- 简单链式:Promise.then 更简洁
- 复杂逻辑:async/await 更易读
- 需要并行:Promise.all
- 错误处理:
try...catch vs .catch()
16.3 Generator 函数
function* 与 yield:函数可暂停可恢复
Generator(生成器)是一种可以暂停和恢复的函数。它不像普通函数那样一旦调用就必须执行到底。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| // 定义 Generator 函数
function* numberGenerator() {
console.log("开始");
yield 1;
console.log("暂停后继续");
yield 2;
console.log("再次暂停后继续");
yield 3;
console.log("结束");
return "完成";
}
// 创建 Generator 对象
const gen = numberGenerator();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: "完成", done: true }
|
执行过程:
- 第一次调用
gen.next(),函数开始执行,遇到第一个 yield 暂停 - 第二次调用
gen.next(),从暂停处继续执行 - 直到遇到
return 或函数结束,done 变为 true
next():控制函数执行,返回 { value, done }
next() 是 Generator 的核心方法:
- 返回
{ value, done } 对象 value 是 yield 后面表达式的值(或 return 的值)done 表示 Generator 是否已经结束
1
2
3
4
5
6
7
8
9
10
11
12
| function* simpleGenerator() {
yield "第一";
yield "第二";
return "最后";
}
const gen = simpleGenerator();
console.log(gen.next()); // { value: "第一", done: false }
console.log(gen.next()); // { value: "第二", done: false }
console.log(gen.next()); // { value: "最后", done: true }
console.log(gen.next()); // { value: undefined, done: true }(已经结束)
|
yield 传值:next(value)
next() 可以传递值给 Generator,这个值会作为上一个 yield 表达式的结果:
1
2
3
4
5
6
7
8
9
10
| function* calculator() {
const first = yield "请输入第一个数";
const second = yield "请输入第二个数";
return Number(first) + Number(second);
}
const gen = calculator();
console.log(gen.next().value); // "请输入第一个数"
console.log(gen.next("10").value); // "请输入第二个数"(10 作为第一个 yield 的结果)
console.log(gen.next("20").value); // 30("10" + "20")
|
应用:实现简单的状态机
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
| function* stateMachine() {
let state = "idle";
while (true) {
const event = yield state;
switch (state) {
case "idle":
if (event === "start") state = "running";
break;
case "running":
if (event === "stop") state = "idle";
if (event === "pause") state = "paused";
break;
case "paused":
if (event === "resume") state = "running";
if (event === "stop") state = "idle";
break;
}
}
}
const machine = stateMachine();
console.log(machine.next().value); // "idle"
console.log(machine.next("start").value); // "running"
console.log(machine.next("pause").value); // "paused"
console.log(machine.next("resume").value); // "running"
|
return() / throw()
return(value):提前结束 Generator,返回指定值throw(error):在 Generator 暂停位置抛出错误
1
2
3
4
5
6
7
8
9
10
11
| function* gen() {
yield 1;
yield 2;
yield 3;
}
const g = gen();
console.log(g.next()); // { value: 1, done: false }
console.log(g.return("结束")); // { value: "结束", done: true }
console.log(g.next()); // { value: undefined, done: true }
|
1
2
3
4
5
6
7
8
9
10
11
12
| function* gen() {
try {
yield 1;
yield 2;
} catch (e) {
console.log("捕获错误:" + e);
}
}
const g = gen();
console.log(g.next()); // { value: 1, done: false }
console.log(g.throw(new Error("出错了"))); // "捕获错误:出错了" { value: undefined, done: true }
|
for…of 自动遍历 Generator
for...of 可以自动遍历 Generator,不需要手动调用 next():
1
2
3
4
5
6
7
8
9
10
| function* numberGenerator() {
yield 1;
yield 2;
yield 3;
}
for (const num of numberGenerator()) {
console.log(num);
}
// 输出:1, 2, 3
|
⚠️ 注意:for...of 不会访问 return 的值!
1
2
3
4
5
6
7
8
9
| function* gen() {
yield 1;
yield 2;
return "被跳过的值";
}
for (const value of gen()) {
console.log(value); // 1, 2(不会打印 "被跳过的值")
}
|
Generator 与迭代器的关系
Generator 实现了迭代器协议(Iterator Protocol),所以它是可迭代的:
1
2
3
4
5
6
7
8
9
| function* gen() {
yield 1;
yield 2;
}
const iterator = gen()[Symbol.iterator]();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: undefined, done: true }
|
这意味着 Generator 可以使用展开运算符、数组方法等:
1
2
3
4
5
6
7
8
| function* range(start, end) {
for (let i = start; i <= end; i++) {
yield i;
}
}
console.log([...range(1, 5)]); // [1, 2, 3, 4, 5]
console.log(Array.from(range(1, 5))); // [1, 2, 3, 4, 5]
|
Generator 的惰性求值
Generator 是惰性求值的——只在需要时才计算下一个值,非常适合处理大量数据或无限序列:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| // 无限序列
function* fibonacci() {
let [a, b] = [0, 1];
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
// 取前10个,不需要定义10个数的数组
const fib = fibonacci();
for (let i = 0; i < 10; i++) {
console.log(fib.next().value); // 0, 1, 1, 2, 3, 5, 8, 13, 21, 34
}
|
💡 想象一下如果用普通数组来实现,需要先计算所有斐波那契数,会占用大量内存。而 Generator 按需计算,内存占用是 O(1)!
本章小结
本章我们学习了 async/await 和 Generator:
async 函数:
async 函数始终返回 Promiseawait 等待 Promise 结果(只能在 async 函数中使用)- 错误处理用
try...catch
async/await 进阶:
- 并行执行:用
Promise.all + await - 循环中的 await:串行执行
forEach 中的 await 不起作用(用 for...of)
Generator 函数:
function* 定义,yield 暂停next() 控制执行,返回 { value, done }- 可以传值给上一个
yield for...of 自动遍历- 实现迭代器协议
- 惰性求值,适合处理大数据
📊 图示:async/await vs Promise
graph LR
A[Promise.then链式] --> B[优点:链式调用]
A --> C[缺点:复杂时难读]
D[async/await] --> E[优点:像同步代码]
D --> F[缺点:需要捕获错误]
G[两者可以互相转换] --> H[本质都是Promise]
下章预告:下一章我们将进入 面向对象编程 的世界——原型链、继承、class 语法!准备好了吗? 🧩