文章目录

JavaScript 异步编程:async/await 与 Promise 链

发布于 2026-04-03 19:36:40 · 浏览 2 次 · 评论 0 条

JavaScript 异步编程:async/await 与 Promise 链

JavaScript 中的异步操作用于处理耗时任务(如网络请求、文件读取),避免阻塞主线程。Promise 是处理异步的基础机制,而 async/await 是基于 Promise 的语法糖,让异步代码写起来像同步代码一样直观。


理解 Promise 链

创建一个 Promise 对象,它代表一个未来可能完成或失败的操作:

const fetchData = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const success = true;
      if (success) {
        resolve("数据加载成功");
      } else {
        reject("数据加载失败");
      }
    }, 1000);
  });
};

调用这个函数并使用 .then().catch() 处理结果:

  1. 调用 fetchData()
  2. 使用 .then(result => console.log(result)) 处理成功结果。
  3. 使用 .catch(error => console.error(error)) 捕获错误。

当多个异步操作需要按顺序执行时,可以链式调用 .then()

fetchData()
  .then(message => {
    console.log(message); // "数据加载成功"
    return "下一步处理";
  })
  .then(next => {
    console.log(next); // "下一步处理"
    return 42;
  })
  .catch(err => {
    console.error(err);
  });

每个 .then() 返回的值会自动包装成一个新的 Promise,供下一个 .then() 使用。


使用 async/await 简化异步代码

async/await 让你用同步风格写异步逻辑,代码更易读。

定义一个 async 函数:

async function processData() {
  try {
    const result = await fetchData();
    console.log(result); // "数据加载成功"
    const next = "下一步处理";
    console.log(next);
    return 42;
  } catch (error) {
    console.error(error);
  }
}

调用该函数:

processData().then(value => console.log(value)); // 42

关键规则:

  • async 函数内部使用 await 关键字等待 Promise 完成。
  • 必须await 放在 try...catch 块中,否则错误无法被捕获。
  • await 后面可以是任何 Promise,也可以是普通值(会被自动包装)。

对比两种写法的实际场景

假设你需要依次完成三个步骤:登录 → 获取用户信息 → 获取订单列表。

使用 Promise 链

login()
  .then(token => fetchUser(token))
  .then(user => fetchOrders(user.id))
  .then(orders => console.log("订单:", orders))
  .catch(err => console.error("流程出错:", err));

使用 async/await

async function getUserOrders() {
  try {
    const token = await login();
    const user = await fetchUser(token);
    const orders = await fetchOrders(user.id);
    console.log("订单:", orders);
  } catch (err) {
    console.error("流程出错:", err);
  }
}

两者功能完全等价,但 async/await 更接近人类阅读顺序,逻辑更清晰。


错误处理的关键差异

在 Promise 链中,任何一个 .then() 抛出错误或返回被拒绝的 Promise,都会跳转到最近的 .catch()

async/await 中,只要 awaitPromise 被拒绝,就会抛出异常,必须用 try...catch 捕获。

若忘记 try...catch,错误会冒泡到调用者:

async function risky() {
  await Promise.reject("出错了"); // 未捕获
}

risky().catch(err => console.log(err)); // 正确方式:在调用处捕获

并行执行多个异步操作

如果多个异步任务彼此独立,应并行执行以提升性能。

不要这样顺序等待:

// ❌ 低效:串行
const user = await fetchUser();
const posts = await fetchPosts();
const comments = await fetchComments();

应该使用 Promise.all()

// ✅ 高效:并行
const [user, posts, comments] = await Promise.all([
  fetchUser(),
  fetchPosts(),
  fetchComments()
]);

Promise.all() 接收一个 Promise 数组,当所有都成功时返回结果数组;任一失败则整体失败。

若希望部分失败也不影响其他任务,可使用 Promise.allSettled()

const results = await Promise.allSettled([
  fetchUser(),
  fetchPosts(),
  fetchComments()
]);

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

常见陷阱与最佳实践

  1. 避免在循环中直接使用 await(会导致串行执行):

    // ❌ 串行,慢
    for (const url of urls) {
      const data = await fetch(url);
      process(data);
    }
    
    // ✅ 并行
    const promises = urls.map(url => fetch(url));
    const results = await Promise.all(promises);
    results.forEach(process);
  2. 不要混淆 return awaitreturn

    • async 函数中,return value 等价于 return Promise.resolve(value)
    • return await promise 通常多余,除非在 try...catch 中需要确保错误被捕获。
  3. 顶层 await(Top-level await)仅在 ES 模块中可用:

    // 只能在 .mjs 文件或 <script type="module"> 中使用
    const config = await loadConfig();

何时选择哪种写法

场景 推荐写法
简单的单个异步调用 async/await
多个依赖前一步结果的异步操作 async/await
需要并行执行多个独立任务 Promise.all() + await
需要在链中动态决定下一步(如条件分支) Promise 链更灵活
兼容旧环境(不支持 async/await) Promise 链
// 动态分支示例:Promise 链更自然
fetchConfig()
  .then(config => {
    if (config.featureEnabled) {
      return enableFeature();
    } else {
      return disableFeature();
    }
  })
  .then(result => console.log(result));

而在 async/await 中也能实现,但需额外变量:

async function handleConfig() {
  const config = await fetchConfig();
  let result;
  if (config.featureEnabled) {
    result = await enableFeature();
  } else {
    result = await disableFeature();
  }
  console.log(result);
}

两者均可,按团队习惯选择。


调试技巧

启用浏览器或 Node.js 的异步栈追踪(Async Stack Traces),能清晰看到 await 调用链。

在 Node.js 中,确保使用较新版本(v12+ 默认开启)。

在 Chrome DevTools 中,勾选 “Async” 选项以查看完整的异步调用栈。

遇到未处理的 Promise 拒绝时,监听 unhandledrejection 事件:

window.addEventListener('unhandledrejection', event => {
  console.error('未捕获的异步错误:', event.reason);
  event.preventDefault(); // 阻止默认报错
});

Node.js 中使用:

process.on('unhandledRejection', (reason, promise) => {
  console.error('未处理的 rejection:', reason);
});

性能与内存注意事项

async/awaitPromise 链在性能上几乎没有差异,因为 async/await 编译后就是 Promise

但要注意:

  • 避免创建不必要的 Promise 包装。
  • 不要在高频循环中滥用 await,可能导致大量微任务堆积。
  • 及时处理错误,防止内存泄漏(未处理的 rejected Promise 会保留引用)。

例如,以下写法会泄漏:

function leaky() {
  someAsyncOperation().catch(() => {}); // 错误被静默忽略,但 Promise 对象可能长期驻留
}

正确做法是显式处理或记录错误。


迁移旧代码的策略

将回调风格(Callback Hell)迁移到 Promise,再升级到 async/await

  1. 封装回调函数为返回 Promise 的函数:

    const readFilePromise = (path) => {
      return new Promise((resolve, reject) => {
        fs.readFile(path, 'utf8', (err, data) => {
          if (err) reject(err);
          else resolve(data);
        });
      });
    };
  2. 替换所有回调调用为 await

    async function processFile() {
      const content = await readFilePromise('data.txt');
      return content.toUpperCase();
    }

现代 Node.js 已内置 util.promisify 辅助转换:

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

测试异步代码

使用 Jest 或 Mocha 等测试框架时,标记测试函数为 async

test('should fetch user', async () => {
  const user = await fetchUser('123');
  expect(user.name).toBe('Alice');
});

返回 Promise

test('should fetch user', () => {
  return fetchUser('123').then(user => {
    expect(user.name).toBe('Alice');
  });
});

切勿在测试中遗漏 awaitreturn,否则测试会提前结束,无法捕获异步错误。


总结核心原则

  • 优先使用 async/await 编写新代码,结构清晰。
  • 善用 Promise.all 实现并行,避免不必要的串行等待。
  • 始终处理异步错误,用 try...catch.catch()
  • 理解 await 只能在 async 函数内使用。
  • 调试时开启异步栈追踪,快速定位问题源头。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文