A

async/await

2025-10-28 11:40

“async/await 让异步代码,回到了最自然的同步写法。”

什么是 async/await

async/await 是 JavaScript 中处理异步操作的一种语法糖。它基于Promise,但让异步代码写起来更像同步,读起来更清晰。

  • async 用来声明一个异步函数,返回一个 Promise
  • await 用来“暂停”异步操作,等待 Promise 结果。
JS
async 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 链依然可能变得冗长:

JS
fetchUser() .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 的 特性

  1. 总是返回一个 Promise
JS
async function fn() { return 42; } fn().then(console.log); // 输出 42
  1. 如果抛出异常,相当于 reject
JS
async function fn() { throw new Error("出错了"); } fn().catch((e) => console.error(e)); // 捕获到错误
  1. await 只能在 async 函数里用
JS
function bad() { await fetchData(); // SyntaxError await必须在async函数内部。否则,直接报错。 }

async/await 到底做了什么

async/await的设计思想类很似 Generator + co,但并不是基于 Generator 实现的。它是 V8 引擎原生支持的特性,性能更好,机制更直接。

await promise 等价于:把 await 后面的代码,用 .then 包起来,交给 Promise 处理。

举个例子:

JS
console.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 的控制流机制forEachmap 函数的回调函数是同步执行的,它们会等待回调函数执行完毕,再继续执行下一个回调函数。而 async/await 是异步执行的,它不会等待回调函数执行完毕,而是直接进入下一个回调函数。

虽然 async/await 能暂停异步执行,但只有在原生控制流中才能真正生效。

解决方式是 使用 for...of 循环或者 for...in 循环。因为它们是一种原生的控制流语句,,它们会等待回调函数执行完毕,再继续执行下一个回调函数。

对比项for...offorEach()
本质语言级语法(keyword)数组方法(function)
谁控制循环JavaScript 引擎回调函数
是否支持暂停✅ 可以暂停(await 有效)❌ 无法暂停
是否等待异步结果✅ 会等待❌ 不会等待

当然 map 的特性是对数组中的每一项执行回调函数,并返回一个新数组,这个新数组的每一项就是回调函数的返回值。于是当使用 async 函数时返回的是 Promise 数组。

JS
const 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 都完成。

JS
const 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 的行为来实现。

JS
const 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 暂停,也学会了让代码变得更从容。