Skip to content

17|JavaScript 异步编程

JavaScript 在浏览器中执行时,不能阻塞主线程——用户点击按钮、页面滚动、动画播放都依赖主线程响应。网络请求、文件读取、定时器等耗时操作必须以异步方式进行:发起操作后不等结果,继续执行后续代码,结果回来后再通过回调处理。

一、回调函数

最早的异步模式是把回调函数传给异步 API:

js
function fetchData(callback) {
  setTimeout(() => {
    callback("数据加载完成");
  }, 1000);
}

fetchData((result) => {
  console.log(result);
});

多个异步操作串行时,回调嵌套越来越深:

js
getUser((user) => {
  getOrders(user.id, (orders) => {
    getDetails(orders[0].id, (details) => {
      console.log(details);
    });
  });
});

这种"回调地狱"(callback hell)代码难以阅读和维护。现代 JavaScript 用 Promise 和 async/await 替代嵌套回调。

二、Promise

Promise 是对异步操作的封装,表示一个尚未完成但预期将来会完成的操作:

js
const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    const success = true;
    if (success) {
      resolve("操作成功");     // 操作成功,传递结果
    } else {
      reject("操作失败");      // 操作失败,传递错误原因
    }
  }, 1000);
});

Promise 有三种状态:

状态含义
pending进行中,尚未完成
fulfilled已成功,调用 resolve
rejected已失败,调用 reject

状态一旦改变(从 pendingfulfilledrejected),就不可再次改变。

then / catch

js
promise
  .then((result) => {
    console.log("成功:", result);   // "操作成功"
  })
  .catch((err) => {
    console.error("失败:", err);
  });

then 接收成功的回调,catch 接收失败的回调。两者可以链式调用。

链式调用

js
fetchUser()
  .then((user) => fetchOrders(user.id))
  .then((orders) => fetchDetails(orders[0].id))
  .then((details) => {
    console.log(details);
  })
  .catch((err) => {
    console.error("任意一步出错:", err);
  });

链式调用中,then 的回调返回值会包装成新的 Promise 传给下一个 then。如果任意一步抛出异常或返回 rejected Promise,会跳过后续 then,直接进入 catch

Promise.all

多个异步操作并行,等全部完成后统一处理:

js
const p1 = fetch("/api/servers");
const p2 = fetch("/api/tasks");
const p3 = fetch("/api/events");

Promise.all([p1, p2, p3])
  .then(([res1, res2, res3]) => {
    console.log("所有请求完成");
  })
  .catch((err) => {
    console.error("任一请求失败:", err);
  });

Promise.all 在任意一个 Promise 失败时立即失败。如果希望"无论成功失败,等全部结束",用 Promise.allSettled

js
Promise.allSettled([p1, p2, p3]).then((results) => {
  results.forEach((r) => {
    if (r.status === "fulfilled") {
      console.log("成功:", r.value);
    } else {
      console.error("失败:", r.reason);
    }
  });
});

Promise.race

多个 Promise 竞争,返回最先完成的那个(无论成功失败):

js
const timeout = new Promise((_, reject) => {
  setTimeout(() => reject(new Error("超时")), 5000);
});

Promise.race([fetch("/api/data"), timeout])
  .then((data) => console.log(data))
  .catch((err) => console.error(err.message));

常用于给异步操作加超时限制。

三、async / await

async/await 是 Promise 的语法糖,让异步代码看起来像同步代码:

js
async function loadDashboard() {
  try {
    const user = await fetchUser();
    const orders = await fetchOrders(user.id);
    const details = await fetchDetails(orders[0].id);
    return details;
  } catch (err) {
    console.error("加载失败:", err.message);
    return null;
  }
}
  • async 函数总是返回一个 Promise
  • await 只能用在 async 函数内部
  • await 会暂停函数执行,等待 Promise 完成,然后把结果返回

并行 await

默认情况下 await 是串行的:

js
// 串行:total 耗时 = a + b + c
const a = await fetchA();
const b = await fetchB();
const c = await fetchC();

没有依赖关系的请求应该并行:

js
// 并行:total 耗时 = max(a, b, c)
const [a, b, c] = await Promise.all([
  fetchA(),
  fetchB(),
  fetchC(),
]);

循环中的 await

js
// 串行执行(每次循环等上一个完成)
for (const url of urls) {
  const data = await fetch(url);
  console.log(data);
}

// 并行执行
const promises = urls.map((url) => fetch(url));
const results = await Promise.all(promises);

需要按顺序处理结果时用 for...of + await;追求速度时用 Promise.all

四、事件循环

JavaScript 是单线程的,异步操作通过事件循环(event loop)调度:

  1. 同步代码在主线程直接执行
  2. 异步操作(如 setTimeout、Promise)交给浏览器/Node.js 底层处理
  3. 底层完成操作后,把回调放入任务队列
  4. 主线程空闲时,从队列中取出回调执行

宏任务与微任务

任务队列分为两种:

类型来源优先级
微任务(microtask)Promise.then/catch/finally、MutationObserver
宏任务(macrotask)setTimeout、setInterval、DOM 事件、I/O

执行顺序:

  1. 执行当前宏任务(如同步脚本)
  2. 执行所有微任务队列中的任务
  3. 渲染页面(如果需要)
  4. 取下一个宏任务执行
js
console.log("1");

setTimeout(() => {
  console.log("2");
}, 0);

Promise.resolve().then(() => {
  console.log("3");
});

console.log("4");

// 输出顺序:1, 4, 3, 2

虽然 setTimeout 延迟是 0,但它的回调是宏任务;Promise 的 then 是微任务,微任务优先于下一个宏任务执行。

五、常见错误

在普通函数中用 await

js
// 错误:await 只能在 async 函数中使用
function load() {
  const data = await fetchData();
}

// 正确
async function load() {
  const data = await fetchData();
}

忘记 await

js
// 错误:data 是 Promise,不是实际结果
async function load() {
  const data = fetchData();   // 缺少 await
  console.log(data.name);     // undefined
}

// 正确
async function load() {
  const data = await fetchData();
  console.log(data.name);
}

Promise 链中忘记 return

js
// 错误:then 中没 return,下一个 then 收到 undefined
fetchUser()
  .then((user) => {
    fetchOrders(user.id);   // 缺少 return
  })
  .then((orders) => {
    console.log(orders);    // undefined
  });

// 正确
fetchUser()
  .then((user) => {
    return fetchOrders(user.id);
  })
  .then((orders) => {
    console.log(orders);
  });

用 forEach 加 await

js
// 错误:forEach 不会等待异步操作
urls.forEach(async (url) => {
  await fetch(url);
});
console.log("全部完成");   // 这行会先执行,fetch 可能还没完成

// 正确:用 for...of
for (const url of urls) {
  await fetch(url);
}
console.log("全部完成");