技术 · 阅读 8 分钟

JavaScript 异步编程完全指南

从回调函数到 Promise,再到 async/await,JavaScript 的异步编程经历了巨大演变。本文系统梳理各种异步模式的优缺点,帮助你写出更优雅的异步代码。

为什么需要异步?

JavaScript 是单线程语言,这意味着同一时间只能执行一个任务。如果遇到网络请求、文件读写等耗时操作,同步执行会阻塞后续代码,导致页面卡顿。异步编程让我们可以在等待耗时操作的同时继续执行其他代码。

回调函数(Callback)

最原始的异步处理方式。将一个函数作为参数传递给异步操作,当操作完成时调用这个函数。

function fetchData(url, callback) {
  const xhr = new XMLHttpRequest();
  xhr.open('GET', url);
  xhr.onload = () => callback(null, xhr.responseText);
  xhr.onerror = () => callback(new Error('请求失败'));
  xhr.send();
}

fetchData('/api/user', (err, data) => {
  if (err) {
    console.error(err);
    return;
  }
  console.log(data);
});

回调函数简单直接,但当多个异步操作需要依次执行时,就会出现著名的"回调地狱":

fetchData('/api/user', (err, user) => {
  fetchData(`/api/posts/${user.id}`, (err, posts) => {
    fetchData(`/api/comments/${posts[0].id}`, (err, comments) => {
      // 嵌套越来越深...
    });
  });
});

Promise

ES6 引入的 Promise 大大改善了异步代码的可读性。Promise 代表一个最终会完成或失败的异步操作。

function fetchData(url) {
  return new Promise((resolve, reject) => {
    fetch(url)
      .then(res => res.json())
      .then(data => resolve(data))
      .catch(err => reject(err));
  });
}

fetchData('/api/user')
  .then(user => fetchData(`/api/posts/${user.id}`))
  .then(posts => fetchData(`/api/comments/${posts[0].id}`))
  .then(comments => console.log(comments))
  .catch(err => console.error(err));

Promise 的链式调用将嵌套结构变成了扁平结构,代码更加清晰。

Async/Await

ES2017 带来的 async/await 语法糖,让异步代码看起来像同步代码一样直观:

async function loadData() {
  try {
    const user = await fetchData('/api/user');
    const posts = await fetchData(`/api/posts/${user.id}`);
    const comments = await fetchData(`/api/comments/${posts[0].id}`);
    console.log(comments);
  } catch (err) {
    console.error('加载失败:', err);
  }
}

并行执行

当多个异步操作互不依赖时,应该并行执行以提高性能:

// 串行 — 慢 ❌
const users = await fetchUsers();
const posts = await fetchPosts();

// 并行 — 快 ✅
const [users, posts] = await Promise.all([
  fetchUsers(),
  fetchPosts()
]);

总结

异步编程是 JavaScript 的核心能力之一。建议在新项目中优先使用 async/await,它兼顾了可读性和功能性。同时,理解底层的 Promise 和回调机制,能帮助你更好地调试和优化代码。