JavaScript 异步编程入门

JavaScript 是单线程的——同一时间只能做一件事。但网页需要同时处理很多事情:等待服务器响应、读取文件、定时提醒。异步编程让 JavaScript 在不阻塞页面的情况下处理这些耗时操作。

为什么需要异步

来看一个最简单的异步操作:setTimeout()

javascript
console.log('开始');

setTimeout(() => {
  console.log('2 秒后执行');
}, 2000);

console.log('结束');

// 输出顺序:开始 → 结束 → 2 秒后执行

setTimeout 不会阻塞后面的代码——这就是异步。“开始”和”结束”立即打印,回调函数在 2 秒后才执行。

setTimeout()setInterval()

javascript
// 延迟执行一次
const timeoutId = setTimeout(() => {
  console.log('3 秒后我只执行一次');
}, 3000);

// 每隔一段时间重复执行
const intervalId = setInterval(() => {
  console.log('每秒执行一次');
}, 1000);

// 停止定时器
clearTimeout(timeoutId);   // 取消 setTimeout
clearInterval(intervalId); // 取消 setInterval

回调函数

最初处理异步的方式是回调函数:

javascript
function fetchData(callback) {
  setTimeout(() => {
    const data = { name: '张三', age: 25 };
    callback(data);
  }, 1000);
}

fetchData((data) => {
  console.log('收到数据:', data);
});

但当多个异步操作需要按顺序执行时,回调嵌套会迅速恶化:

javascript
// ❌ 回调地狱(Callback Hell)
step1((result1) => {
  step2(result1, (result2) => {
    step3(result2, (result3) => {
      step4(result3, (result4) => {
        console.log('终于完成');
      });
    });
  });
});

这就是为什么 Promise 被发明了。

Promise — 承诺

Promise 代表一个未来会完成的异步操作。它有三种状态:

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

创建 Promise

javascript
const promise = new Promise((resolve, reject) => {
  // 异步操作
  setTimeout(() => {
    const success = true;
    if (success) {
      resolve('成功了'); // 将 Promise 标记为 fulfilled
    } else {
      reject('失败了'); // 将 Promise 标记为 rejected
    }
  }, 1000);
});

消费 Promise

javascript
promise
  .then((result) => {
    console.log('成功:', result);
  })
  .catch((error) => {
    console.log('失败:', error);
  })
  .finally(() => {
    console.log('无论成败都会执行');
  });

Promise 链

then 可以链式调用,解决回调地狱:

javascript
fetchUser(1)
  .then(user => fetchPosts(user.id))
  .then(posts => fetchComments(posts[0].id))
  .then(comments => console.log('评论:', comments))
  .catch(error => console.log('出错:', error));

扁平、可读、错误在链尾统一处理。

async / await — 异步同步写法

ES2017 引入的语法糖,让异步代码看起来像同步代码

javascript
async function loadData() {
  try {
    const user = await fetchUser(1);
    const posts = await fetchPosts(user.id);
    const comments = await fetchComments(posts[0].id);
    console.log('评论:', comments);
  } catch (error) {
    console.log('出错:', error);
  }
}

loadData();
  • async 标记函数为异步函数——它始终返回一个 Promise
  • await 等待一个 Promise 完成,获取其结果
  • try...catch 捕获错误,替代 .catch()

async/await 是当前 JavaScript 异步编程的推荐方式——代码清晰易读,错误处理自然。await 只能在 async 函数内部使用;在顶层(模块顶层)也可以使用,现代浏览器都支持。

并行的异步操作

当多个异步操作互不依赖时,可以并行执行:

javascript
// 串行(总时间 = 三个耗时之和)
const user = await fetchUser(1);
const posts = await fetchPosts(2);
const settings = await fetchSettings(3);

// 并行(总时间 = 最长的那个)
const [user, posts, settings] = await Promise.all([
  fetchUser(1),
  fetchPosts(2),
  fetchSettings(3),
]);

Promise.all 是”全部成功或一个失败”——只要其中一个 Promise 被 reject,整个 Promise.all 立即失败。如果需要”部分成功也可以”的语义,使用 Promise.allSettled

fetch() — 发送网络请求

fetch() 是浏览器内置的 HTTP 请求方法,返回 Promise:

javascript
async function getUser(id) {
  const response = await fetch(`https://api.example.com/users/${id}`);
  if (!response.ok) {
    throw new Error(`请求失败:${response.status}`);
  }
  const data = await response.json(); // 解析 JSON
  return data;
}

fetch 的详细用法在 JSON 章节中进一步展开。