引言
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()); console.log(iterator.next()); console.log(iterator.next()); console.log(iterator.next());
|
这种方式较为繁琐,我们可以编写一个自动执行 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);
|
这样就能直接获取生成器的最终返回值。
进一步思考,如果生成器中 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(); gen.next(); gen.next(); gen.next();
|
使用 run 方法:
1 2
| const result = run(generator); console.log(result);
|
这种方式并未等待 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(); console.log("step1:", step1); step1.value.then((result) => { console.log("回调结果:", result); const step2 = gen.next(result); console.log("step2:", step2); step2.value.then((result2) => { console.log("回调结果2:", result2); const step3 = gen.next(result2); console.log("step3:", step3); step3.value.then((result3) => { console.log("回调结果3:", result3); const step4 = gen.next(result3); 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); });
|
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 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 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 的出现,让我们能够以近乎同步的方式编写异步代码,大大提升了代码的可读性和可维护性。它背后的思想正是我们之前探索的”生成器 + 执行器”模式,只是现在由语言层面原生支持,不再需要手动实现执行器逻辑。