async/await
2025-10-28 11:40
“async/await 让异步代码,回到了最自然的同步写法。”
什么是 async/await
async/await 是 JavaScript 中处理异步操作的一种语法糖。它基于Promise,但让异步代码写起来更像同步,读起来更清晰。
async用来声明一个异步函数,返回一个Promise。await用来“暂停”异步操作,等待Promise结果。
JSasync function getData() { const user = await fetchUser(); const orders = await fetchOrders(user.id); const detail = await fetchDetail(orders[0].id); console.log(detail); }
在语义上:await 表示“在这里等一下,等结果回来再继续”。
为什么需要 async/await
在上一篇文章中, 我们通过 Promise 解决了“回调地狱”的问题。但当异步逻辑较多时,Promise 链依然可能变得冗长:
JSfetchUser() .then((user) => fetchOrders(user.id)) .then((orders) => fetchDetail(orders[0].id)) .then((detail) => console.log(detail)) .catch(console.error);
这种 .then() 链虽然比嵌套回调好看,但还是会层层传递参数、不容易调试, 并且实际开发中还会出现:
- 条件判断麻烦(比如:只有满足某个条件才继续下一步)
- 中间变量传递绕(得靠 then 一层层传)
- try/catch 捕获不了 Promise 内部错误(得用 .catch())
- 循环处理异步?写起来非常绕 于是 async/await 就应运而生。它使得异步代码看起来像同步代码,并使代码更清晰、更易读。
async/await 的 特性
- 总是返回一个 Promise
JSasync function fn() { return 42; } fn().then(console.log); // 输出 42
- 如果抛出异常,相当于 reject
JSasync function fn() { throw new Error("出错了"); } fn().catch((e) => console.error(e)); // 捕获到错误
- await 只能在 async 函数里用
JSfunction bad() { await fetchData(); // SyntaxError await必须在async函数内部。否则,直接报错。 }
async/await 到底做了什么
async/await的设计思想类很似 Generator + co,但并不是基于 Generator 实现的。它是 V8 引擎原生支持的特性,性能更好,机制更直接。
await promise 等价于:把 await 后面的代码,用 .then 包起来,交给 Promise 处理。
举个例子:
JSconsole.log("1"); async function foo() { console.log("2"); await Promise.resolve(); console.log("3"); } foo(); console.log("4"); // 1 2 4 3
await Promise.resolve()会把 console.log('3')放进微任务队列。当前同步代码 console.log('4')执行完后,事件循环才处理微任务。
这就是 await 的真相:它不是真的暂停,而是把后续逻辑放进微任务,等当前同步代码执行完再执行。
async/await 应用场景
- 顺序执行多个异步请求
- 依赖式请求(上一个结果作为下一个参数)
- 更优雅的错误捕获
- 结合 Promise.all 处理并发
常见陷阱
最常见的陷阱就是 async/await 在 forEach 和 map 中使用是无效的。
最主要原因就是 JavaScript 的控制流机制。forEach 和 map 函数的回调函数是同步执行的,它们会等待回调函数执行完毕,再继续执行下一个回调函数。而 async/await 是异步执行的,它不会等待回调函数执行完毕,而是直接进入下一个回调函数。
虽然 async/await 能暂停异步执行,但只有在原生控制流中才能真正生效。
解决方式是 使用 for...of 循环或者 for...in 循环。因为它们是一种原生的控制流语句,,它们会等待回调函数执行完毕,再继续执行下一个回调函数。
| 对比项 | for...of | forEach() |
|---|---|---|
| 本质 | 语言级语法(keyword) | 数组方法(function) |
| 谁控制循环 | JavaScript 引擎 | 回调函数 |
| 是否支持暂停 | ✅ 可以暂停(await 有效) | ❌ 无法暂停 |
| 是否等待异步结果 | ✅ 会等待 | ❌ 不会等待 |
当然 map 的特性是对数组中的每一项执行回调函数,并返回一个新数组,这个新数组的每一项就是回调函数的返回值。于是当使用 async 函数时返回的是 Promise 数组。
JSconst arr = [1, 2, 3]; const result = arr.map(async (num) => { await new Promise((res) => setTimeout(res, 1000)); return num * 2; }); console.log(result); // [Promise, Promise, Promise]
因为返回的数组元素刚好是 Promise,所以可以使用 Promise.all 来等待所有 Promise 都完成。
JSconst arr = [1, 2, 3]; const promises = arr.map(async (n) => { await new Promise((res) => setTimeout(res, 1000)); return n * 2; }); console.log(promises); // [Promise, Promise, Promise] const result = await Promise.all(promises); console.log(result); // [2, 4, 6]
虽然 forEach 没有返回值,不能使用 Promise.all(),但我们变通使用,通过类似模拟 map 的行为来实现。
JSconst arr = [1, 2, 3]; const tasks = []; arr.forEach((n) => { tasks.push( (async () => { await new Promise((res) => setTimeout(res, 1000)); return n * 2; })() ); }); const result = await Promise.all(tasks); console.log(result); // [2, 4, 6]
最后
async/await 不是新的异步模型,而是对 Promise 的一种更优雅的表达方式。
它让异步逻辑重新回到了“看起来像同步”的书写风格,也让错误捕获、流程控制、循环操作变得自然易懂。
在现代 JavaScript 中,我们可以这样理解异步演进:
Callback → Promise → async/await
每一步都让异步编程更接近“直觉思考”的方式。
如果说 Promise 让我们摆脱了回调地狱,那么 async/await 则让我们重新获得了对异步流程的掌控权。
当我们学会用 await 暂停,也学会了让代码变得更从容。