☰
JavaScript 异步编程|回调、Promise、async/await 权威指南
2026/10/26 10:21:36 网站建设 项目流程

一、前置认知:JS 单线程模型

JavaScript 是单线程语言,同一时间只能做一件事。 单线程避免了多线程资源抢占问题,但如果遇到耗时操作(网络请求、定时器、文件读取),会阻塞后面代码执行。 异步方案就是用来解决这个矛盾:耗时任务交给浏览器内核其他线程处理,主线程继续往下执行,任务完成后再回调执行结果。

核心概念:事件循环 Event Loop

  1. 同步代码优先执行,直接进入调用栈
  2. 异步任务分为:宏任务、微任务
  3. 执行顺序:同步代码 → 清空所有微任务队列 → 取一个宏任务执行,循环往复
  • 微任务:Promise.then/catch/finally、queueMicrotask
  • 宏任务:setTimeout、setInterval、AJAX、DOM 渲染

二、回调函数 Callback

基础定义

把一个函数作为参数传给另一个函数,等异步操作完成之后再执行这个传入函数,就是回调。

// 简单定时器回调 console.log('开始') setTimeout(function () { console.log('定时器回调执行') }, 1000) console.log('结束')

输出顺序:

开始 结束 定时器回调执行

回调地狱(经典痛点)

多层异步依赖,回调层层嵌套,代码向右无限缩进,可读性、可维护性极差:

// 回调地狱示例 ajax('/api/user', function(res1) { ajax(`/api/order?uid=${res1.id}`, function(res2) { ajax(`/api/goods?oid=${res2.id}`, function(res3) { console.log('最终数据', res3) }, err3 => {}) }, err2 => {}) }, err1 => {})

✅ 优点:简单、原生支持,早期唯一异步方案 ❌ 缺点:嵌套臃肿、错误处理分散、无法用 return /try catch、难以串行控制

三、Promise

Promise 是 ES6 推出的异步容器,用来包装异步操作,解决回调地狱。

三种状态(状态不可逆)

  • pending:进行中(初始状态)
  • fulfilled:已成功
  • rejected:已失败

基础语法

const p = new Promise(function(resolve, reject) { // 异步操作 setTimeout(() => { const ok = true if (ok) { resolve('成功结果') // 触发 .then } else { reject('失败原因') // 触发 .catch } }, 1000) }) p.then(res => { console.log('拿到结果:', res) }).catch(err => { console.error('错误:', err) }).finally(() => { console.log('无论成功失败都会执行') })

链式调用(核心优势)

.then内部 return 新 Promise,实现串行异步,不再嵌套:

new Promise(resolve => resolve(1)) .then(res => { console.log(res) return 2 }) .then(res => { console.log(res) return new Promise(resolve => resolve(3)) }) .then(res => console.log(res))

Promise 静态方法

表格

API作用
Promise.all([p1,p2,p3])全部成功才成功,任意一个失败直接 reject
Promise.race([p1,p2,p3])谁先有结果(成功 / 失败)就返回谁
Promise.allSettled([p1,p2,p3])等待全部完成,不管成功失败,返回每个结果状态
Promise.any([p1,p2,p3])任意一个成功就成功;全部失败才 reject

示例:

// all 并发请求 const p1 = fetch('/api/a') const p2 = fetch('/api/b') Promise.all([p1, p2]).then(([res1, res2]) => { // 两个接口都拿到数据 })

✅ 优点:链式写法扁平、统一错误捕获、支持并发控制 ❌ 缺点:依旧是回调风格,不能直观像同步代码一样书写;无法中途取消

四、async /await

ES2017 语法糖,基于 Promise 封装,让异步代码看起来像同步代码,是目前项目首选方案。

基础规则

  1. async修饰函数,函数返回值自动包装成 Promise
  2. await只能写在async函数内部
  3. await后面一般跟 Promise,会暂停当前代码,直到 Promise 完成,拿到结果
// 基础示例 async function getData() { try { const res1 = await new Promise(resolve => setTimeout(() => resolve('数据1'), 1000)) console.log(res1) const res2 = await new Promise(resolve => setTimeout(() => resolve('数据2'), 1000)) console.log(res2) } catch (err) { console.error('捕获异步错误', err) } } getData()

并发场景搭配 Promise.all

await 直接串行等待,如果不需要依赖,不要依次 await,避免浪费时间:

async function batch() { // 同时发起 const p1 = fetch('/api/a') const p2 = fetch('/api/b') const [r1, r2] = await Promise.all([p1, p2]) }

✅ 优点:代码直观、可读性最强,错误统一 try/catch ❌ 缺点:await 会阻塞当前 async 函数内部代码;必须依托 Promise

五、方案选型对照表

表格

方案适用场景推荐度
回调函数简单一次性异步、老旧兼容代码⭐
Promise 链式简单异步串行、需要并发控制⭐⭐⭐
async + await业务接口请求、复杂异步流程(日常开发首选)⭐⭐⭐⭐⭐

六、高频踩坑清单

  1. await只能在 async 函数里,普通函数直接使用会语法报错
  2. 忘记 try-catch:await 的 Promise reject 会抛出未捕获异常
  3. 无依赖接口串行 await:白白拉长总耗时,优先 Promise.all
  4. 混淆微任务宏任务:Promise.then 比 setTimeout 先执行
  5. Promise 状态一旦改变,无法二次修改

七、拓展:异步迭代器 & Generator(了解)

Generator(function*+ yield)早期用来实现异步流程控制,现在项目里很少用,被 async/await 替代,框架底层源码偶尔可见。


小结

异步是 JS 核心难点,演进路线:回调函数 → Promise → async/await。现代前端业务开发,优先选择 async/await + Promise 组合。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询