async和await如何简化javascript异步代码_怎样编写更清晰的异步函数【教程】

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 valueasync 会帮你包)
  • 想提前退出?用 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,结果网络失败时整个调用栈静默中断。

  • 单个 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.allSettledPromise.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 了 undefinednull
  • TypeScript 用户注意:类型守卫如 val instanceof Promise 在运行时不可靠,因为 Promise 可能被 polyfill 替换

真正的复杂点不在语法,而在于异步边界如何划分:什么时候该用 await 阻塞当前流程,什么时候该用 Promise.all 并行推进,又什么时候该把 await 推到调用方去处理——这些决策直接影响错误传播路径和资源释放时机。