源本科技 | 码上会

Promise async & await

2026/04/11
1
0

引言

async / await 是 ES 2017 新增的异步编程语法,本质是 Promise 的语法糖。 它用同步代码的写法处理异步操作,彻底消除了 Promise 链式调用的 .then() 嵌套,让异步代码的可读性和维护性达到最优。

async

async 用于声明一个函数为异步函数,是使用 await 的前提条件。

核心特性

  1. 异步函数的返回值会自动包装成 Promise 对象

  2. 函数内部 return 普通值 → 自动包装为 fulfilled 状态的 Promise

  3. 函数内部抛出错误 → 自动包装为 rejected 状态的 Promise

  4. 可以像调用普通 Promise 一样,使用 .then() / .catch() 处理结果

基础使用

// 声明异步函数
async function myAsyncFunction() {
  // 直接返回普通值,自动包装为 Promise
  return "Hello, World!";
}

// 调用方式与 Promise 完全一致
myAsyncFunction().then((result) => {
  console.log(result); // 输出: Hello, World!
});

await

await 用于等待一个 Promise 对象执行完成,是 async/await 的核心。

核心规则

  1. 必须在 async 函数内部使用,在普通函数中使用会直接报错

  2. 暂停当前函数的执行,等待 Promise 状态变更后再继续

  3. Promise 成功 → 返回 resolve 的结果;Promise 失败 → 抛出错误

  4. 不仅可以等待 Promise,也可等待普通值(直接返回原值)

基础使用

async function fetchData() {
  // 定义异步操作
  const promise = new Promise((resolve) => {
    setTimeout(() => resolve("Data fetched"), 2000);
  });

  // 等待 Promise 执行完成,直接获取结果
  const result = await promise;
  console.log(result); // 输出: Data fetched
}

// 调用异步函数
fetchData();

组合使用

实际开发中,asyncawait 固定搭配使用,完美替代 Promise 链式调用,处理顺序执行的异步任务。

示例:按顺序读取文件

结合前文的 then-fs,用同步写法实现文件顺序读取:

import thenFs from "then-fs";

async function getAllFiles() {
  // 依次读取 3 个文件,上一个完成后再执行下一个
  const f1 = await thenFs.readFile('./files/1.txt', 'utf8');
  console.log(f1);

  const f2 = await thenFs.readFile('./files/2.txt', 'utf8');
  console.log(f2);

  const f3 = await thenFs.readFile('./files/3.txt', 'utf8');
  console.log(f3);
}

getAllFiles();

错误处理

await 等待的 Promise 失败时会抛出异常,必须捕获处理,否则程序会崩溃。 推荐使用 try...catch 语句捕获异常,替代 Promise 的 .catch() 方法。

基础错误捕获

import thenFs from "then-fs";

async function getAllFiles() {
  try {
    // 尝试执行异步操作
    const f1 = await thenFs.readFile('./files/1.txt', 'utf8');
    console.log(f1);
  } catch (error) {
    // 捕获异步操作的所有错误
    console.error("文件读取失败:", error);
  }
}

getAllFiles();

网络请求错误处理

async function fetchData() {
  try {
    const response = await fetch('https://api.example.com/data');
    // 手动抛出 HTTP 错误
    if (!response.ok) throw new Error(`HTTP error! status: ${ response.status }`);
    
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error("请求失败:", error);
  }
}

fetchData();

并发执行优化

await 默认会串行执行异步任务(等待上一个完成再执行下一个),存在性能损耗。 结合 Promise.all() 实现并行执行,大幅提升效率。

示例:并发读取文件

import thenFs from "then-fs";

async function getAllFilesConcurrently() {
  try {
    // 同时发起 3 个读文件操作,并行执行
    const promiseArr = [
      thenFs.readFile('./files/1.txt', 'utf8'),
      thenFs.readFile('./files/2.txt', 'utf8'),
      thenFs.readFile('./files/3.txt', 'utf8')
    ];

    // 等待所有异步操作完成,统一接收结果
    const [f1, f2, f3] = await Promise.all(promiseArr);

    console.log(f1, f2, f3);
  } catch (error) {
    console.error("读取失败:", error);
  }
}

getAllFilesConcurrently();

结合其他静态方法

await 可以配合所有 Promise 静态方法使用:

// 赛跑机制:谁先完成返回谁
const result = await Promise.race(promiseArr);
// 兜底机制:等待所有任务完成,无论成功失败
const result = await Promise.allSettled(promiseArr);
// 成功优先:只要一个成功就返回
const result = await Promise.any(promiseArr);

核心特点

  1. 同步写法:代码扁平化,无嵌套,阅读难度极低

  2. Promise 兼容:底层基于 Promise,不改变异步本质

  3. 调试友好:断点调试时,和同步代码一样逐行执行

  4. 语法简洁:替代 .then() 链式调用,代码量大幅减少

最佳实践

避免过度串行

不要对无依赖关系的异步任务使用串行 await,会严重降低性能,优先使用 Promise.all() 并发执行。

强制捕获错误

所有 await 操作必须放在 try...catch 中,防止未捕获的异常导致程序崩溃。

顶层 await

在 ES 模块(ESM)中,无需 async 函数,可以直接在顶层使用 await

// 顶层 await(仅支持 ES 模块)
const data = await thenFs.readFile('./files/1.txt', 'utf8');
console.log(data);

禁止在普通函数中使用

await 只能在 async 函数 / 模块顶层使用,在普通函数、回调函数中直接使用会报错。

与 Promise 链式调用对比

方式

优点

缺点

Promise 链式调用

兼容性好、无语法糖依赖

嵌套多、可读性差

async / await

代码简洁、易读易维护

需要 ES 2017+ 环境

总结:日常开发优先使用 async / await,它是目前 JavaScript 异步编程的最优方案


总结

  1. async / await 是 Promise 的语法糖,用同步写法实现异步逻辑

  2. async 修饰函数,自动返回 Promise;await 等待 Promise 完成

  3. try...catch 捕获错误,用 Promise.all 实现并发优化

  4. 是现代 JavaScript 开发中处理异步操作的标准方案