正在加载,请稍候…

JavaScript Promise 与 Async/Await:完整可视化指南

掌握 JavaScript 异步编程中的 Promise 和 async/await,学习常见模式、错误处理、并行执行以及如何避免最常见的错误。

JavaScript Promise 与 Async/Await:完整可视化指南

JavaScript Promise 与 Async/Await:完整可视化指南

异步编程是 JavaScript 中最难掌握的概念之一。本指南涵盖从回调到 Promise 再到 async/await 的所有内容——配有可视化解释和真实世界模式。

问题:回调地狱

// 旧方式——"回调金字塔"
getUser(userId, function(err, user) {
  if (err) return handleError(err);
  getOrders(user.id, function(err, orders) {
    if (err) return handleError(err);
    getProducts(orders[0].id, function(err, products) {
      if (err) return handleError(err);
      // 现在你已经嵌套了 3 层...
      displayResults(user, orders, products);
    });
  });
});

Promise 和 async/await 解决了这个问题。

JavaScript Promise 与 Async/Await:完整可视化指南插图

理解 Promise

Promise 是一个表示异步操作最终完成(或失败)的对象。

// 创建一个 promise
const myPromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    const success = Math.random() > 0.5;
    if (success) {
      resolve('数据加载完成!');  // 成功
    } else {
      reject(new Error('加载失败'));  // 失败
    }
  }, 1000);
});

三种状态:

  1. Pending(待定) — 初始状态,操作进行中
  2. Fulfilled(已兑现) — 操作成功完成
  3. Rejected(已拒绝) — 操作失败

Promise 链式调用

// 扁平、可读的链式调用
fetchUser(userId)
  .then(user => fetchOrders(user.id))     // 每个 .then 接收前一个结果
  .then(orders => fetchProducts(orders))
  .then(products => displayProducts(products))
  .catch(error => console.error('出错了:', error))
  .finally(() => setLoading(false));       // 始终执行

关键规则.then() 总是返回一个新的 Promise,因此你可以无限链式调用。

Async/Await——更简洁的语法

// 相同逻辑,但读起来像同步代码
async function loadUserData(userId) {
  try {
    const user = await fetchUser(userId);
    const orders = await fetchOrders(user.id);
    const products = await fetchProducts(orders);
    displayProducts(products);
  } catch (error) {
    console.error('出错了:', error);
  } finally {
    setLoading(false);
  }
}

async/await 的规则:

  • async 函数总是返回一个 Promise
  • await 只能在 async 函数内部使用
  • await 会暂停执行直到 Promise 解决

并行执行——最重要的模式

顺序执行(慢)——逐个等待:

// 如果每个请求需要 1 秒,总共需要 3 秒
async function loadDashboard() {
  const users = await fetchUsers();     // 1s
  const orders = await fetchOrders();   // 1s  
  const stats = await fetchStats();     // 1s
  return { users, orders, stats };      // 总计:3s
}

并行执行(快)——同时运行:

// 只需要 1 秒(所有请求同时运行)
async function loadDashboard() {
  const [users, orders, stats] = await Promise.all([
    fetchUsers(),
    fetchOrders(),
    fetchStats(),
  ]);
  return { users, orders, stats };  // 总计:1s
}

何时使用哪种:

  • 当请求相互独立时,使用 Promise.all
  • 当请求 B 依赖于请求 A 的结果时,使用顺序 await

Promise 方法详解

Promise.all——全部或全不

// 当所有 Promise 都解决时解决;任何一个拒绝则拒绝
const [user, profile, settings] = await Promise.all([
  fetchUser(id),
  fetchProfile(id),
  fetchSettings(id),
]);

JavaScript Promise 与 Async/Await:完整可视化指南插图

Promise.allSettled——获取所有结果

// 当所有 Promise 完成时解决(无论成功或失败)
const results = await Promise.allSettled([
  fetchUser(id),
  fetchProfile(id),
  fetchSettings(id),
]);

results.forEach(result => {
  if (result.status === 'fulfilled') {
    console.log('成功:', result.value);
  } else {
    console.log('失败:', result.reason);
  }
});

Promise.race——先到先得

// 以第一个解决的 Promise 的结果解决或拒绝
// 常用于超时处理
const result = await Promise.race([
  fetchData(),
  new Promise((_, reject) => 
    setTimeout(() => reject(new Error('超时')), 5000)
  )
]);

Promise.any——第一个成功

// 以第一个兑现的 Promise 的结果解决
// 仅当所有 Promise 都拒绝时才拒绝(抛出 AggregateError)
const result = await Promise.any([
  fetchFromServer1(),
  fetchFromServer2(),
  fetchFromServer3(),
]);
// 返回最先成功响应的服务器结果

错误处理模式

Try/Catch 模式

async function fetchUserSafe(id) {
  try {
    const response = await fetch(`/api/users/${id}`);
    
    if (!response.ok) {
      throw new Error(`HTTP 错误:${response.status}`);
    }
    
    return await response.json();
  } catch (error) {
    if (error.name === 'AbortError') {
      console.log('请求已取消');
    } else if (error.name === 'TypeError') {
      console.log('网络错误——你在线吗?');
    } else {
      console.error('意外错误:', error);
    }
    return null; // 或者重新抛出,取决于你的需求
  }
}

Result 模式(Go 风格错误处理)

// 返回 [data, error] 元组——调用处无需 try/catch
async function safeAsync(promise) {
  try {
    const data = await promise;
    return [data, null];
  } catch (error) {
    return [null, error];
  }
}

// 使用
const [user, error] = await safeAsync(fetchUser(id));
if (error) {
  console.error('失败:', error);
  return;
}
// 此处 user 保证已定义
console.log(user.name);

JavaScript Promise 与 Async/Await:完整可视化指南插图

常见错误

错误 1:循环中未使用 await

// ❌ 错误——所有请求同时触发,无法正确捕获错误
async function processItems(items) {
  items.forEach(async (item) => {
    await processItem(item); // forEach 不会等待异步回调!
  });
}

// ✅ 正确——顺序处理
async function processItemsSequential(items) {
  for (const item of items) {
    await processItem(item);
  }
}

// ✅ 正确——并行处理
async function processItemsParallel(items) {
  await Promise.all(items.map(item => processItem(item)));
}

错误 2:遗漏 await

// ❌ 错误——console.log 得到的是 Promise 对象,而不是数据
async function getUser() {
  const user = fetchUser(); // 遗漏了 await!
  console.log(user); // Promise { <pending> }
}

// ✅ 正确
async function getUser() {
  const user = await fetchUser();
  console.log(user); // { id: 1, name: 'John' }
}

错误 3:未处理的 Promise 拒绝

// ❌ 危险——未处理的拒绝可能导致 Node.js 崩溃
async function riskyOperation() {
  await doSomething();
}
riskyOperation(); // 没有任何 .catch()!

// ✅ 始终处理拒绝
riskyOperation().catch(console.error);
// 或者在模块中使用顶层 await
try {
  await riskyOperation();
} catch (e) {
  console.error(e);
}

错误 4:创建不必要的 Promise

// ❌ 冗余——将 async 函数包装在新 Promise 中
function fetchData() {
  return new Promise(async (resolve, reject) => {
    try {
      const data = await fetch('/api/data');
      resolve(data);
    } catch (error) {
      reject(error);
    }
  });
}

// ✅ async 函数已经返回 Promise
async function fetchData() {
  const data = await fetch('/api/data');
  return data; // 自动包装在 Promise 中
}

真实世界模式:带重试的数据获取

async function fetchWithRetry(url, options = {}, retries = 3) {
  for (let attempt = 1; attempt <= retries; attempt++) {
    try {
      const response = await fetch(url, options);
      if (!response.ok) throw new Error(`HTTP ${response.status}`);
      return await response.json();
    } catch (error) {
      if (attempt === retries) throw error;
      
      // 指数退避:等待 1s, 2s, 4s...
      const delay = Math.pow(2, attempt - 1) * 1000;
      console.log(`第 ${attempt} 次尝试失败。${delay}ms 后重试...`);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

// 使用
const data = await fetchWithRetry('https://api.example.com/data');

总结

模式 使用场景
顺序 await 每一步依赖前一步的结果
Promise.all 独立操作,需要所有结果
Promise.allSettled 需要所有结果,部分可能失败
Promise.race 超时模式,第一个结果胜出
Promise.any 多个来源,需要第一个成功结果

→ 使用 JSON Viewer 测试 JSON API 响应,并在 JWT Parser 解码 JWT 令牌。