DevToolBox免费
博客

JavaScript Promises 和 Async/Await 完全指南

16 分钟作者 DevToolBox

TL;DR — 快速摘要

Promise 是 JavaScript 异步操作的基础。Promise 有三种状态:pending(等待)fulfilled(已完成)rejected(已拒绝)。 使用 async/await 编写可读性更强的异步代码; 使用 Promise.all 并行执行多个操作; 使用 try/catch.catch() 捕获错误; 使用 Promise.race 实现超时; 使用 Promise.allSettled 在部分失败时仍获取全部结果。 本指南包含真实代码示例和常见错误的解决方案。

核心要点

  • Promise 是单次使用的异步结果容器,状态不可逆
  • async/await 是 Promise 链式调用的语法糖,本质相同
  • Promise.all 快速失败;Promise.allSettled 等待全部完成
  • 错误必须用 .catch() 或 try/catch 显式捕获,否则成为未处理的拒绝
  • 并行执行比串行 await 快得多
  • Promise.race 可实现超时和竞速场景
  • 避免回调地狱、嵌套 Promise 和忘记 await 等常见错误

1. Promise 基础:状态与生命周期

在 Promise 诞生之前,JavaScript 使用回调函数处理异步操作。回调函数层层嵌套,形成臭名昭著的"回调地狱"。 ES6(2015)引入的 Promise 提供了一种结构化的方式来表示异步操作的最终完成(或失败)。

Promise 对象代表一个异步操作,它有三种互斥状态:

  • Pending(等待):初始状态,操作尚未完成
  • Fulfilled(已完成):操作成功完成,有一个结果值
  • Rejected(已拒绝):操作失败,有一个错误原因

一旦 Promise 从 pending 状态过渡到 fulfilled 或 rejected,状态就"定格"了,永远不会再改变。 已完成或已拒绝的 Promise 统称为"已敲定(settled)"。

// Promise 的三种状态
const pending   = new Promise(() => {});          // 永远等待
const fulfilled = Promise.resolve('success');     // 立即完成
const rejected  = Promise.reject(new Error('fail')); // 立即拒绝

// 使用 .then() 和 .catch() 处理结果
fulfilled
  .then(value => console.log('值:', value))       // 值: success
  .catch(err  => console.error('错误:', err));

rejected
  .then(value => console.log('不会执行'))
  .catch(err  => console.error('捕获到:', err.message)); // 捕获到: fail

.then()、.catch() 和 .finally()

Promise 的三个核心方法用于处理不同的结果:

fetch('https://api.example.com/users/1')
  .then(response => {
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    return response.json();       // 返回新 Promise
  })
  .then(user => {
    console.log('用户名:', user.name);
    return user;
  })
  .catch(error => {
    // 捕获链上任何地方抛出的错误
    console.error('请求失败:', error.message);
    return null;                  // 可以恢复,返回默认值
  })
  .finally(() => {
    // 无论成功还是失败都执行
    // 适合清理操作:隐藏加载状态、关闭连接等
    console.log('请求已完成');
  });

2. 创建 Promise:new Promise()、resolve 和 reject

使用 new Promise(executor) 创建 Promise。executor 函数接收两个参数:resolve(成功时调用)和 reject(失败时调用)。

// 基本用法
function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

// 带结果的延迟
function delayedValue(value, ms) {
  return new Promise(resolve => setTimeout(() => resolve(value), ms));
}

// 模拟随机成功/失败
function unreliableOperation() {
  return new Promise((resolve, reject) => {
    const success = Math.random() > 0.5;
    setTimeout(() => {
      if (success) resolve({ data: '操作成功', timestamp: Date.now() });
      else reject(new Error('操作随机失败'));
    }, 1000);
  });
}

// 包装回调风格的 API
function readFileAsync(path) {
  return new Promise((resolve, reject) => {
    const fs = require('fs');
    fs.readFile(path, 'utf8', (err, data) => {
      if (err) reject(err);
      else resolve(data);
    });
  });
}

// 立即完成/拒绝的快捷方式
const ok  = Promise.resolve(42);
const err = Promise.reject(new TypeError('类型错误'));

3. Promise 链式调用

.then() 总是返回一个新的 Promise,这使得链式调用成为可能。 链中某一步返回的值会传递到下一个 .then();抛出的错误会传递到最近的 .catch()

// 正确的链式调用(扁平化,非嵌套)
fetchUser(userId)
  .then(user => fetchUserPosts(user.id))   // 返回新 Promise
  .then(posts => fetchComments(posts[0].id))
  .then(comments => renderComments(comments))
  .catch(err => showError(err));

// 错误传播:错误会跳过 .then() 直到遇到 .catch()
Promise.resolve(1)
  .then(v => { throw new Error('第2步出错'); })
  .then(v => console.log('这里不会执行'))
  .then(v => console.log('这里也不会执行'))
  .catch(err => console.log('捕获到:', err.message)); // 捕获到: 第2步出错

// 在 .catch() 后恢复链式调用
fetchData()
  .then(processData)
  .catch(err => {
    console.warn('使用缓存数据,原因:', err.message);
    return getCachedData();  // 返回备用值,链继续执行
  })
  .then(data => renderUI(data));  // 无论原始请求是否成功都会执行

4. async/await:现代异步语法

async/await 是 ES2017 引入的语法糖,让异步代码看起来像同步代码。async 函数始终返回一个 Promise;await 暂停函数执行直到 Promise 敲定。

// async 函数基本用法
async function fetchUserProfile(userId) {
  const response = await fetch(`/api/users/${userId}`);
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  const user = await response.json();
  return user;
}

// try/catch 错误处理(等同于 .catch())
async function getUserSafely(userId) {
  try {
    const user = await fetchUserProfile(userId);
    console.log('用户:', user.name);
    return user;
  } catch (error) {
    if (error.message.includes('404')) {
      console.warn('用户不存在');
      return null;
    }
    throw error;  // 重新抛出无法处理的错误
  } finally {
    console.log('fetchUserSafely 执行完毕');
  }
}

// 顶层 await(ES2022,在 ES 模块中可用)
const config = await loadConfig('./config.json');
console.log('配置已加载:', config.version);

串行执行 vs 并行执行

这是 async/await 最常见的性能陷阱:连续的 await 是串行执行的, 如果操作之间互不依赖,应该并行执行。

// 慢:串行执行,总耗时 = 300ms + 200ms + 400ms = 900ms
async function slowApproach() {
  const users    = await fetchUsers();    // 等待 300ms
  const products = await fetchProducts(); // 再等待 200ms
  const orders   = await fetchOrders();   // 再等待 400ms
  return { users, products, orders };
}

// 快:并行执行,总耗时 ≈ max(300, 200, 400) = 400ms
async function fastApproach() {
  const [users, products, orders] = await Promise.all([
    fetchUsers(),
    fetchProducts(),
    fetchOrders(),
  ]);
  return { users, products, orders };
}

// 数组并行处理
async function processAllItems(items) {
  // 正确:并行处理所有项
  const results = await Promise.all(items.map(item => processItem(item)));

  // 错误:for...of 中的 await 是串行的
  // for (const item of items) {
  //   await processItem(item);  // 逐个处理,很慢!
  // }
  return results;
}

5. Promise 组合器:all、allSettled、race、any

JavaScript 提供了四个静态方法来处理多个 Promise,每个方法有不同的"等待策略":

方法完成条件失败条件典型使用场景
Promise.all全部完成任意一个拒绝需要所有结果才能继续
Promise.allSettled全部敲定(无论结果)永不拒绝批量操作,需要全部状态
Promise.race第一个敲定第一个拒绝超时、取第一个响应
Promise.any第一个完成全部拒绝多个备用源,取最快成功的
// Promise.all — 全部成功才完成,任一失败立即失败
const [user, posts, friends] = await Promise.all([
  fetchUser(id),
  fetchPosts(id),
  fetchFriends(id),
]);

// Promise.allSettled — 等待全部,获取每个的状态
const results = await Promise.allSettled([
  fetchPrimaryAPI(),
  fetchSecondaryAPI(),
  fetchTertiaryAPI(),
]);
results.forEach(result => {
  if (result.status === 'fulfilled') {
    console.log('成功:', result.value);
  } else {
    console.warn('失败:', result.reason.message);
  }
});

// Promise.race — 第一个敲定的(成功或失败)
const firstResponse = await Promise.race([
  fetchFromServer1(),
  fetchFromServer2(),
  fetchFromServer3(),
]);

// Promise.any — 第一个成功的(忽略拒绝直到全部拒绝)
try {
  const fastest = await Promise.any([
    fetchFromCDN1(),
    fetchFromCDN2(),
    fetchFromCDN3(),
  ]);
  console.log('最快成功:', fastest);
} catch (aggregateError) {
  // 所有 Promise 都拒绝时抛出 AggregateError
  console.error('所有来源都失败:', aggregateError.errors);
}

6. 错误处理模式

正确处理 Promise 错误至关重要。未处理的 Promise 拒绝会导致 Node.js 进程崩溃 (从 Node.js 15 开始),在浏览器中会触发 unhandledrejection 事件。

// 自定义错误类型
class NetworkError extends Error {
  constructor(message, statusCode) {
    super(message);
    this.name = 'NetworkError';
    this.statusCode = statusCode;
  }
}

class ValidationError extends Error {
  constructor(message, field) {
    super(message);
    this.name = 'ValidationError';
    this.field = field;
  }
}

// 区分错误类型进行处理
async function createUser(data) {
  try {
    validateUserData(data);  // 可能抛出 ValidationError
    const user = await apiCreateUser(data);  // 可能抛出 NetworkError
    return user;
  } catch (error) {
    if (error instanceof ValidationError) {
      console.error(`字段 ${error.field} 验证失败: ${error.message}`);
      return { error: 'validation', field: error.field };
    }
    if (error instanceof NetworkError && error.statusCode === 409) {
      console.warn('用户已存在');
      return { error: 'conflict' };
    }
    // 无法处理的错误:重新抛出
    throw error;
  }
}

// 全局未处理拒绝捕获(Node.js)
process.on('unhandledRejection', (reason, promise) => {
  console.error('未处理的 Promise 拒绝:', reason);
});

// 浏览器中的全局捕获
window.addEventListener('unhandledrejection', event => {
  console.error('未处理的 Promise 拒绝:', event.reason);
  event.preventDefault();
});

7. 高级模式

Promise.withResolvers()(ES2024)

// 旧写法:手动保存 resolve/reject
let resolve, reject;
const promise = new Promise((res, rej) => { resolve = res; reject = rej; });

// 新写法:Promise.withResolvers()(Node.js 22+)
const { promise, resolve, reject } = Promise.withResolvers();

// 使用场景:在不同作用域中控制 Promise
function createEventPromise(emitter, event) {
  const { promise, resolve, reject } = Promise.withResolvers();
  emitter.once(event, resolve);
  emitter.once('error', reject);
  return promise;
}

const connected = createEventPromise(socket, 'connect');
await connected;
console.log('已连接!');

Promisification:将回调转为 Promise

const { promisify } = require('util');
const fs = require('fs');

// Node.js 内置 util.promisify
const readFile  = promisify(fs.readFile);
const writeFile = promisify(fs.writeFile);

// 使用 promisified 函数
const content = await readFile('./config.json', 'utf8');
const config  = JSON.parse(content);

// 手动 promisify(通用模式)
function promisifyFn(fn) {
  return function(...args) {
    return new Promise((resolve, reject) => {
      fn(...args, (err, result) => {
        if (err) reject(err);
        else resolve(result);
      });
    });
  };
}

// 整个模块 promisify(Node.js fs.promises 已内置)
const fsPromises = fs.promises;
const data = await fsPromises.readFile('./data.txt', 'utf8');

8. 真实场景示例

带重试逻辑的 fetch

async function fetchWithRetry(url, options = {}, retries = 3, delay = 1000) {
  for (let attempt = 1; attempt <= retries; attempt++) {
    try {
      const response = await fetch(url, options);
      if (!response.ok) {
        if (response.status >= 500 && attempt < retries) {
          console.warn(`尝试 ${attempt}/${retries} 失败,重试中...`);
          await new Promise(r => setTimeout(r, delay * attempt));
          continue;
        }
        throw new Error(`HTTP ${response.status}`);
      }
      return await response.json();
    } catch (error) {
      if (attempt === retries) throw error;
      await new Promise(r => setTimeout(r, delay * attempt));
    }
  }
}

超时包装器

function withTimeout(promise, ms, message = `操作超时 (${ms}ms)`) {
  const timeoutPromise = new Promise((_, reject) =>
    setTimeout(() => reject(new Error(message)), ms)
  );
  return Promise.race([promise, timeoutPromise]);
}

// 使用方式
try {
  const data = await withTimeout(
    fetch('https://slow-api.example.com/data'),
    5000,
    'API 请求超时(5秒)'
  );
  console.log(data);
} catch (error) {
  if (error.message.includes('超时')) {
    return getCachedData();
  }
  throw error;
}

并发控制(限制并发数)

// 限制并发数,避免同时发出过多请求
async function limitedConcurrency(tasks, concurrency = 5) {
  const results = [];
  const executing = new Set();

  for (const task of tasks) {
    const promise = task().then(result => {
      executing.delete(promise);
      return result;
    });
    executing.add(promise);
    results.push(promise);

    if (executing.size >= concurrency) {
      await Promise.race(executing);
    }
  }
  return Promise.all(results);
}

// 使用示例:处理1000个URL,最多同时5个请求
const urls = Array.from({ length: 1000 }, (_, i) => `/api/item/${i}`);
const tasks = urls.map(url => () => fetch(url).then(r => r.json()));
const allResults = await limitedConcurrency(tasks, 5);

9. 常见错误及解决方案

错误1:忘记 await

// 错误:没有 await,data 是 Promise 对象而不是值
async function wrong() {
  const data = fetchData();  // data = Promise { <pending> }
  console.log(data.name);   // undefined
  if (data) { ... }         // 总是 true!
}

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

错误2:吞掉错误(空的 catch)

// 错误:忽略错误,导致静默失败
async function wrong() {
  try {
    await doSomething();
  } catch (e) {
    // 空 catch:错误被吞掉!
  }
}

// 正确:至少记录错误
async function correct() {
  try {
    await doSomething();
  } catch (error) {
    console.error('操作失败:', error);
    throw error;
  }
}

错误3:嵌套 Promise

// 错误:Promise 嵌套
fetchUser(id).then(user => {
  fetchPosts(user.id).then(posts => {
    renderPage(user, posts);
  });
});

// 正确:async/await
async function loadPage(id) {
  const user  = await fetchUser(id);
  const posts = await fetchPosts(user.id);
  return renderPage(user, posts);
}

10. 回调 vs Promise vs async/await 对比

特性回调Promiseasync/await
可读性差(嵌套)一般(链式)优秀(同步风格)
错误处理容易遗漏.catch() 链try/catch
调试困难一般优秀(堆栈清晰)
并行执行手动管理Promise.allawait Promise.all
TypeScript 支持一般最好
适用场景遗留代码组合操作一般首选

常见问题

Promise 的三种状态是什么?

pending(等待)、fulfilled(已完成)、rejected(已拒绝)。一旦从 pending 过渡,状态永不改变。

async/await 和 .then() 有什么区别?

async/await 是语法糖,底层仍是 Promise。async/await 更易读和调试,.then() 在动态组合链时更灵活。

Promise.all 和 Promise.allSettled 的区别?

Promise.all 在任一失败时立即失败;Promise.allSettled 等待全部完成并返回每个的状态,永不失败。

如何给 Promise 添加超时?

使用 Promise.race([yourPromise, timeoutPromise]),timeoutPromise 在指定时间后拒绝。

如何并行执行多个 async 操作?

使用 await Promise.all([op1(), op2(), op3()]),不要连续使用多个独立的 await。

𝕏 Twitterin LinkedIn
这篇文章有帮助吗?

保持更新

获取每周开发技巧和新工具通知。

无垃圾邮件,随时退订。

试试这些相关工具

{ }JSON FormatterJSJavaScript Minifier

相关文章

JavaScript 数组方法速查表:每个方法含示例

完整的 JavaScript 数组方法参考。涵盖 map、filter、reduce、find、sort、flat、flatMap、splice 以及 ES2023 新方法。

TypeScript 泛型完全指南 2026:从基础到高级模式

全面掌握 TypeScript 泛型:类型参数、约束、条件类型、映射类型、工具类型,以及事件发射器和 API 客户端等实战模式。

REST API 最佳实践:2026 完整指南

学习 REST API 设计最佳实践,包括命名规范、错误处理、认证、分页、版本控制和安全头。