javascript如何实现异步编程?_回调函数、Promise和Async/Await有什么区别?【教程】

JavaScript异步编程应按场景选型:回调易致嵌套地狱且错误传递不统一;Promise支持链式调用但错误处理隐晦;async/await语法简洁但需注意try/catch、并发控制及返回值始终为Promise。

JavaScript 异步编程不是选“哪个更高级”,而是看场景和可维护性——回调函数能跑通,但容易陷入地狱;Promise 解决了嵌套,却让错误处理变隐晦;async/await 最接近同步写法,但不等于没有陷阱。

回调函数为什么容易出问题?

它本身不是错,错在多层嵌套 + 错误传递不统一。比如读文件后发请求再存数据库:

fs.readFile('a.json', (err, data) => {
  if (err) throw err;
  fetch('/api', { body: data })
    .then(res => res.json())
    .then(json => db.save(json))
    .catch(err => console.error(err));
});

这段代码看似用了 Promise,但外层仍是回调——一旦 fetch 抛异常,catch 捕不到;如果 db.save 是回调风格,又得嵌一层,形成混合式混乱。

  • 错误必须手动层层传,if (err) return callback(err) 容易漏写
  • 无法用 try/catch 统一捕获异步错误
  • return 在回调里只退出当前函数,不影响外层流程

Promise 解决了什么,又带来了什么新问题?

Promise 的核心价值是「状态可组合」:pending → fulfilled/rejected,且 .then().catch() 总返回新 Promise,支持链式调用。

但它对错误的处理有隐蔽性:

  • .then(success, fail) 中的 fail 只捕获前一个 Promise 的 rejection,不捕获 success 回调里的异常
  • .catch() 会吞掉未被处理的 rejection(尤其在未加 catch 的链尾)
  • 多个并行请求用 Promise.all([p1, p2]) 时,一个失败整个失败;想“全都要”得用 Promise.allSettled

常见误写:

getUser().then(user => {
  getPosts(user.id); // 忘了 return!结果后续 .then 接收到 undefined
}).then(posts => console.log(posts)); // posts 是 undefined

async/await 真的更简单吗?

语法糖没错,但掩盖了两个关键事实:

  • await 只解包 Promisefulfilled 值,rejected 仍会抛出异常——所以必须配 try/catch,不能靠 .catch()
  • await 是顺序执行,想并发得显式用 Promise.all 包一层,否则性能反而更差
  • 顶层 await 只在模块作用域或 async 函数里合法,直接写 await fetch(...) 会报 SyntaxError: await is only valid in async function

正确写法示例:

async function loadProfile() {
  try {
    const [user, posts] = await Promise.all([
     

fetch('/user').then(r => r.json()), fetch('/posts').then(r => r.json()) ]); return { user, posts }; } catch (err) { console.error('加载失败:', err.message); throw err; // 不 throw,调用方拿不到错误 } }

什么时候该用哪个?

没有银弹,但有现实约束:

  • 老项目兼容 IE 或低版本 Node.js?只能用回调或 Promise(需 polyfill)
  • 需要精细控制错误传播路径(比如部分失败可降级)?Promise.catch() 链比 async/awaittry/catch 更灵活
  • 团队新手多、逻辑分支深?async/await 可读性优势明显,但得盯紧 try/catch 是否覆盖所有 await
  • 写库函数暴露 API?优先返回 Promise,而非 async 函数——后者返回的是带额外原型的 Promise,且无法被 Promise.resolve() 正常包裹

最常被忽略的一点:async 函数内部哪怕没写 await,也一定会返回 Promise。这意味着 return 42 实际等价于 return Promise.resolve(42)——如果你依赖返回值是否为 Promise 做类型判断,这点必须意识到。