从入门到精通:Async_Await 异步编程完全指南

266 阅读5分钟

引言:为什么 Async/Await 成为异步编程的首选?

**

在 JavaScript 世界中,异步编程是绕不开的核心话题。从早期的回调函数,到 ES6 的 Promise,再到 ES8 推出的 Async/Await,每一次演进都在解决前一代方案的痛点。

  • 回调函数:陷入 “回调地狱”,代码可读性差、维护成本高
  • Promise:解决了回调地狱,但链式调用的 then 依然不够直观,错误处理需要嵌套
  • Async/Await:基于 Promise 封装,用同步的语法写异步代码,可读性拉满,错误处理更优雅

本文将从基础到进阶,全面解析 Async/Await 的用法、原理、调优技巧,无论是新手还是老手,都能从中获得收获。


一、新手必学:Async/Await 基础用法

1. 核心语法规则

Async/Await 由两个关键字组成,必须搭配使用:

// 1. 用 async 声明异步函数
async function fetchData() {
  // 2. 用 await 等待 Promise 完成(只能在 async 函数内使用)
  const result = await new Promise((resolve) => {
    setTimeout(() => resolve("请求成功"), 1000);
  });
  console.log(result); // 1秒后输出:请求成功
}
fetchData();

关键特性:

  • async 函数的返回值必然是 Promise(无论是否显式返回)
async function test() {
  return "hello"; // 等价于 return Promise.resolve("hello")
}
test().then(res => console.log(res)); // hello
  • await 会 “暂停” 函数执行,直到 Promise 状态变为 fulfilled 或 rejected
  • await 后面可以跟任意值(非 Promise 会直接转为 resolved 状态)

2. 错误处理:try/catch 优雅捕获

Async/Await 用同步的错误处理方式(try/catch)替代 Promise 的 catch 方法,更符合直觉:

async function fetchDataWithError() {
  try {
    const result = await new Promise((resolve, reject) => {
      setTimeout(() => reject(new Error("请求失败")), 1000);
    });
    console.log(result); // 不会执行
  } catch (error) {
    console.error(error.message); // 输出:请求失败
  }
}
fetchDataWithError();

批量错误处理技巧:

如果多个异步操作互不依赖,可用 Promise.all 配合 try/catch:

async function fetchMultipleData() {
  try {
    // 并行执行多个异步操作(比串行更高效)
    const [data1, data2] = await Promise.all([
      fetch("/api/user"),
      fetch("/api/posts")
    ]);
    console.log("所有请求成功", data1, data2);
  } catch (error) {
    console.error("至少一个请求失败", error);
  }
}

3. 与 Promise 的兼容性

Async/Await 是 Promise 的 “语法糖”,完全兼容 Promise 生态:

// 现有 Promise 代码可直接迁移
function oldPromiseFunc() {
  return new Promise((resolve) => {
    setTimeout(() => resolve("旧接口"), 500);
  });
}
// 用 await 直接调用
async function newAsyncFunc() {
  const data = await oldPromiseFunc();
  console.log(data); // 旧接口
}

二、老手必备:Async/Await 进阶调优

1. 性能优化:避免串行陷阱

新手常犯的错误:不必要的串行执行,导致性能浪费:

// 反面示例:串行执行(总耗时 = 1s + 1s = 2s)
async function badPerformance() {
  const data1 = await fetch("/api/data1"); // 1s
  const data2 = await fetch("/api/data2"); // 1s(需等待 data1 完成)
}

优化方案:

  • 互不依赖的异步操作:用 Promise.all 并行执行(总耗时 = 最长单个任务时间)
async function goodPerformance() {
  // 先启动所有异步操作
  const promise1 = fetch("/api/data1");
  const promise2 = fetch("/api/data2");
  // 再等待所有结果
  const [data1, data2] = await Promise.all([promise1, promise2]); // 总耗时 1s
}
  • 部分依赖的异步操作:用 Promise.race 或 Promise.allSettled
// 场景:优先使用缓存数据,超时后请求接口
async function fetchWithCache() {
  const cachePromise = getCache(); // 快速获取缓存(0.1s)
  const apiPromise = fetch("/api/data").then(res => res.json()); // 1s
  
  // 谁先完成用谁的结果
  const result = await Promise.race([cachePromise, apiPromise]);
  return result;
}

2. 错误处理进阶:精准捕获多个异步错误

Promise.all 会 “快速失败”(一个失败则整体失败),如果需要捕获每个异步操作的错误,用 Promise.allSettled:

async function fetchAllWithDetails() {
  const results = await Promise.allSettled([
    fetch("/api/user"),
    fetch("/api/invalid-url"), // 无效接口
    fetch("/api/posts")
  ]);
  // 遍历结果,分别处理成功和失败
  results.forEach((result, index) => {
    if (result.status === "fulfilled") {
      console.log(`接口${index+1}成功`, result.value);
    } else {
      console.log(`接口${index+1}失败`, result.reason);
    }
  });
}

3. 异步函数的返回值处理技巧

  • 返回 Promise 链:实现链式调用
async function getUser() {
  const res = await fetch("/api/user");
  return res.json(); // 返回 Promise,可继续 then
}
getUser()
  .then(user => fetch(`/api/posts?userId=${user.id}`))
  .then(posts => console.log(posts));
  • 中断异步流程:抛出错误或返回 Promise.reject
async function validateUser(user) {
  if (!user.id) {
    // 抛出错误会被 catch 捕获
    throw new Error("用户ID不存在");
    // 等价于 return Promise.reject(new Error("用户ID不存在"))
  }
  return user;
}

4. 与 Generator 函数的对比(老手视角)

Async/Await 本质上是 Generator 函数的语法糖,但更简洁、功能更强:

特性Generator 函数Async/Await
执行触发需要手动调用 next ()直接调用,自动执行
错误处理需要手动传递错误原生支持 try/catch
可读性需配合 co 库,较复杂同步语法,直观易懂
返回值Iterator 对象Promise 对象

示例对比:

// Generator 函数(需 co 库)
const co = require("co");
function* generatorFunc() {
  const data = yield fetch("/api/data");
  return data.json();
}
co(generatorFunc()).then(res => console.log(res));
// Async/Await 函数(原生支持)
async function asyncFunc() {
  const data = await fetch("/api/data");
  return data.json();
}
asyncFunc().then(res => console.log(res));

三、实战场景:Async/Await 的最佳实践

1. 接口请求封装(前后端通用)

// 封装请求工具函数
async function request(url, options = {}) {
  try {
    const res = await fetch(url, {
      method: options.method || "GET",
      headers: {
        "Content-Type": "application/json",
        ...options.headers
      },
      body: options.body ? JSON.stringify(options.body) : undefined
    });
    // 统一处理 HTTP 错误(4xx/5xx)
    if (!res.ok) {
      throw new Error(`HTTP错误:${res.status}`);
    }
    return await res.json();
  } catch (error) {
    // 统一错误日志上报
    console.error("请求失败:", error);
    // 重新抛出错误,让调用方处理
    throw error;
  }
}
// 调用示例
async function fetchUserInfo() {
  try {
    const user = await request("/api/user/1", { method: "GET" });
    console.log("用户信息:", user);
  } catch (error) {
    // 业务层错误处理(如提示用户)
    alert("获取用户信息失败,请重试");
  }
}

2. 异步迭代(处理批量异步任务)

场景:分页加载数据,直到没有更多数据为止:

async function fetchAllPages() {
  let page = 1;
  let allData = [];
  let hasMore = true;
  while (hasMore) {
    try {
      const data = await request(`/api/data?page=${page}&size=10`);
      allData = [...allData, ...data.list];
      hasMore = data.hasMore;
      page++;
    } catch (error) {
      console.error("加载分页失败", error);
      // 失败后重试机制
      await new Promise(resolve => setTimeout(resolve, 1000));
    }
  }
  return allData;
}

3. 结合定时器的异步控制

场景:接口请求超时处理:

async function fetchWithTimeout(url, timeout = 5000) {
  // 创建超时 Promise
  const timeoutPromise = new Promise((_, reject) => {
    setTimeout(() => reject(new Error("请求超时")), timeout);
  });
  // 用 Promise.race 实现超时控制
  const result = await Promise.race([
    request(url),
    timeoutPromise
  ]);
  return result;
}

四、避坑指南:新手常犯的 5 个错误

1. 忘记 await 导致异步变同步

// 错误示例:没有 await,直接返回 Promise 对象
async function wrongUsage() {
  const data = fetch("/api/data"); // 错误:缺少 await
  console.log(data); // Promise { <pending> }
}
// 正确示例
async function correctUsage() {
  const data = await fetch("/api/data");
  console.log(data); // Response 对象
}

2. 在非 async 函数中使用 await

// 错误:SyntaxError: await is only valid in async functions
function notAsyncFunc() {
  const data = await fetch("/api/data");
}

3. 滥用 Promise.all 处理依赖关系

// 错误:data2 依赖 data1 的结果,却用 Promise.all 并行执行
async function wrongDependency() {
  const [data1, data2] = await Promise.all([
    fetch("/api/user"),
    fetch(`/api/posts?userId=${data1.id}`) // data1 未定义,报错
  ]);
}
// 正确:串行执行依赖任务
async function correctDependency() {
  const data1 = await fetch("/api/user");
  const data2 = await fetch(`/api/posts?userId=${data1.id}`);
}

4. 忽略错误处理导致程序崩溃

// 错误:未捕获 await 抛出的错误,导致函数执行中断
async function noErrorHandling() {
  const data = await fetch("/api/invalid-url"); // 失败会抛出错误
  console.log(data); // 不会执行
}

5. 同步循环中执行异步操作

// 错误:for 循环中直接 await,导致串行执行(耗时过长)
async function wrongLoop() {
  const ids = [1, 2, 3];
  const result = [];
  for (const id of ids) {
    const data = await fetch(`/api/data/${id}`); // 串行执行,总耗时 3s
    result.push(data);
  }
}
// 正确:先收集所有 Promise,再并行执行
async function correctLoop() {
  const ids = [1, 2, 3];
  const promises = ids.map(id => fetch(`/api/data/${id}`));
  const result = await Promise.all(promises); // 并行执行,总耗时 1s
}

五、原理深挖:Async/Await 是如何工作的?

核心原理:

Async/Await 基于 Promise 和 Generator 实现:

  1. async 函数本质是一个返回 Promise 的 Generator 函数的语法糖
  1. 浏览器 / Node.js 内置了类似 co 库的自动执行器,无需手动调用 next()
  1. await 关键字相当于 Generator 函数中的 yield,用于暂停和恢复执行

简化实现模拟:

// 模拟 async/await 的自动执行器
function myAsync(generatorFunc) {
  return new Promise((resolve, reject) => {
    const generator = generatorFunc();
    function runNext(result) {
      const { value, done } = generator.next(result);
      if (done) {
        // 生成器执行完毕,resolve 结果
        return resolve(value);
      }
      // 等待 Promise 完成后继续执行
      Promise.resolve(value).then(
        res => runNext(res),
        err => reject(err)
      );
    }
    runNext();
  });
}
// 使用模拟的 async 函数
const fetchData = myAsync(function* () {
  const data = yield fetch("/api/data");
  return data.json();
});

六、总结:Async/Await 的核心优势

  1. 可读性极强:同步语法写异步代码,降低认知成本
  1. 错误处理优雅:try/catch 统一捕获所有错误,无需嵌套
  1. 兼容性好:基于 Promise,完全兼容现有异步生态
  1. 调试友好:断点调试时,能像同步代码一样逐行执行
  1. 代码简洁:省去大量 then 链式调用,减少模板代码

无论是新手入门异步编程,还是老手重构现有代码,Async/Await 都是最优选择。掌握本文的基础用法、调优技巧和避坑指南,就能轻松应对各类异步场景。