async/await 是简化异步控制流的关键机制,async 函数必返回 Promise,await 仅限 async 内使用,需配合 try/catch,支持 thenable 对象但需防 undefined。

async/await 不是语法糖的“装饰”,而是让异步流程回归同步阅读习惯的关键机制——它真正简化的是控制流逻辑,而不是减少代码行数。
async 函数必须显式返回 Promise
很多人误以为只要加了 async,函数就“自动变异步”了,其实不然:async 只保证函数返回一个 Promise(哪怕你 return 42,也会被包装成 Promise.resolve(42))。如果你在函数里直接 return fetch(...),那返回的就是原生 Promise;但若中间有 await,就必须确保每个分支都走异步路径,否则容易漏掉错误处理。
- 不要写
if (condition) return someSyncValue;—— 这会让返回类型不一致(有时是 number,有时是 Promise) - 统一用
return Promise.resolve(value)或直接return value(async会帮你包) - 想提前退出?用
return await Promise.reject(new Error(...)),避免未被捕获的 rejected promise
await 只能在 async 函数内部使用
这是最常踩的语法错误:await 不是全局操作符,它依赖 async 函数提供的执行上下文。在普通函数、事件回调、模块顶层(ESM)中直接写 await 会报 SyntaxError: await is only valid in async function。
- 常见误用场景:在
setTimeout(() => { await doSomething(); }, 100)里直接 await - 正确做法:把回调包进
async函数,或改用setTimeout(async () => { ... }, 100) - 模块顶层想 await?需启用
"type": "module"并配合top-level await(Node.js 14.8+ / 现代浏览器支持),但注意这会阻塞模块初始化
try/catch 是 await 的默认搭档,不是可选项
和 .then().catch() 不同,await 遇到 rejected Promise 会直接抛出异常,不 catch 就崩。很多初学者只写 await apiCall() 却没包 try,结果网络失败时整个调用栈静默中断。
立即学习“Java免费学习笔记(深入)”;
- 单个 await:用
try { const res = await fetch(...); } catch (e) { console.error(e); } - 多个串行 await:一个 try 块能覆盖全部,比链式
.catch()更紧凑 - 并行请求别滥用 await:想同时发两个请求,应写
const [a, b] = await Promise.all([fetch('/a'), fetch('/b')]),而非连续 await(那是串行) - 注意
Promise.allSettled和Promise.race的语义差异——它们返回结构固定的结果,和直接 await 行为不同
await 后面不一定是 Promise,但必须“thenable”
await 实际上会调用右侧值的 then 方法(如果存在),所以它支持任何带 .then() 的对象(比如 jQuery Deferred、Axios response、甚至手写的类 Promise 对象)。但这也带来隐性风险:如果对象有 then 属性却不是 Promise(比如某个 API 返回 { then: 'not a function' }),就会触发 “unhandled promise rejection”。
- 安全起见,对不确定来源的值,先用
Promise.resolve(val)包一层再 await - 调试时遇到
Cannot read property 'then' of undefined,大概率是 await 了undefined或null - TypeScript 用户注意:类型守卫如
val instanceof Promise在运行时不可靠,因为 Promise 可能被 polyfill 替换
真正的复杂点不在语法,而在于异步边界如何划分:什么时候该用 await 阻塞当前流程,什么时候该用 Promise.all 并行推进,又什么时候该把 await 推到调用方去处理——这些决策直接影响错误传播路径和资源释放时机。











