🚀告别回调地狱!手把手教你用 async/await 玩转异步编程(附实战案例)

47 阅读4分钟

从 Ajax 到 Promise,再到 async/await——异步编程的进化之路,90% 的开发者都曾被坑过!

在前端开发中,"异步"是绕不开的坎。无论是请求接口、读取文件,还是处理用户交互,都离不开异步操作。但传统的回调函数写法让代码像俄罗斯套娃一样嵌套,一旦出错就陷入"回调地狱",连自己都看不懂。

今天,我们就用 最接地气的方式,带大家彻底搞懂 ES8 推出的 async/await 语法——它让异步代码像同步一样简单!文末附两个超实用实战案例,让你秒变异步编程高手。


🔥 一、为什么我们需要 async/await?——异步编程的进化史

1️⃣ 回调函数(ES5 前):代码像"套娃"一样嵌套

// 读取文件 -> 处理数据 -> 写入新文件
fs.readFile('./1.html', 'utf-8', (err, data) => {
  if (err) return console.error(err);
  console.log(data); // 处理数据
  fs.writeFile('./2.html', data, (err) => {
    if (err) return console.error(err);
    console.log('文件写入成功'); // 写入文件
  });
});

痛点:嵌套层数多,逻辑分散,错误处理混乱,代码可读性极差。

2️⃣ Promise(ES6):链式调用的曙光

fs.promises.readFile('./1.html', 'utf-8')
  .then(data => {
    console.log(data);
    return fs.promises.writeFile('./2.html', data);
  })
  .then(() => console.log('文件写入成功'))
  .catch(err => console.error(err));

进步:链式调用,逻辑更清晰,错误统一处理。
问题.then().then().catch() 依然啰嗦,变量作用域分散。

3️⃣ async/await(ES8):异步变"同步"的魔法 ✨

const main = async () => {
  try {
    const data = await fs.promises.readFile('./1.html', 'utf-8');
    console.log(data);
    await fs.promises.writeFile('./2.html', data);
    console.log('文件写入成功');
  } catch (err) {
    console.error('操作失败:', err);
  }
};
main();

核心优势:用同步的写法,写异步的逻辑!代码结构线性,逻辑清晰,错误统一处理。


🧠 二、async/await 核心原理:一图看懂

async 函数 → 返回 Promiseawait 等待 Promise → 获取 resolve 值
↓
代码继续执行(同步写法,异步实现)

💡 await 就像"暂停符":等待右边的 Promise 完成,然后把结果赋值给左边的变量。


🛠️ 三、实战案例 1:获取 GitHub 用户仓库(浏览器端)

❌ 传统 Promise 写法

fetch('https://api.github.com/users/shunwuyu/repos')
  .then(res => res.json())
  .then(data => console.log(data))
  .catch(err => console.error(err));

✅ async/await 写法(超清晰!)

const main = async () => {
  try {
    // 1. 等待 fetch 请求完成
    const res = await fetch('https://api.github.com/users/shunwuyu/repos');
    
    // 2. 等待 res.json() 解析完成
    const data = await res.json();
    
    // 3. 逻辑清晰,像同步代码一样执行
    console.log('仓库列表:', data);
    console.log('数据长度:', data.length);
  } catch (err) {
    console.error('请求失败:', err);
  }
};
main();

关键点

  1. await 会暂停函数执行,直到 Promise 解析
  2. .json() 本身返回 Promise,所以也要用 await
  3. try/catch 统一处理所有异步错误

🎯 四、实战案例 2:Node.js 读取文件(服务器端)

❌ 传统回调写法(坑点满满)

fs.readFile('./1.html', 'utf-8', (err, data) => {
  if (err) return console.error(err);
  console.log(data);
  console.log('文件读取成功');
});

✅ async/await 写法(简洁优雅)

import { promises as fs } from 'fs'; // 推荐使用 Promise 版本

const readFileAsync = async () => {
  try {
    // 1. 等待文件读取完成
    const data = await fs.readFile('./1.html', 'utf-8');
    
    // 2. 直接使用数据,无需回调嵌套
    console.log('文件内容:', data);
    console.log('内容长度:', data.length);
    
    // 3. 可继续添加其他异步操作
    await fs.writeFile('./2.html', data);
    console.log('文件已保存');
  } catch (err) {
    console.error('文件操作失败:', err);
  }
};

readFileAsync(); // 启动异步流程

为什么推荐 fs.promises

  • Node.js 8+ 原生支持 Promise API
  • 无需手动包装 Promise,代码更简洁
  • 与 async/await 完美契合

⚠️ 五、避坑指南:async/await 的 3 个致命误区

❌ 误区 1:在非 async 函数中使用 await

// ❌ 报错!
const data = await fs.promises.readFile('./1.html');

// ✅ 正确:必须在 async 函数内使用
const main = async () => {
  const data = await fs.promises.readFile('./1.html');
};

❌ 误区 2:忽略错误处理

// ❌ 会丢失错误,导致程序崩溃
const data = await fs.promises.readFile('./1.html');

// ✅ 正确:必须用 try/catch
try {
  const data = await fs.promises.readFile('./1.html');
} catch (err) {
  console.error(err);
}

❌ 误区 3:串行执行导致性能下降

// ❌ 串行执行(慢!)
const user = await fetchUser();
const posts = await fetchPosts();

// ✅ 并行执行(快!)
const [user, posts] = await Promise.all([
  fetchUser(),
  fetchPosts()
]);

📌 六、为什么说 async/await 是异步编程的终极方案?

特性回调函数Promiseasync/await
代码可读性❌ 严重嵌套✅ 链式调用✅ 同步写法
错误处理❌ 分散处理✅ 统一 catch✅ try/catch
逻辑流程❌ 扭曲✅ 线性✅ 线性
与同步代码一致性❌ 无❌ 有差异✅ 一致
性能优化❌ 无法并行❌ 需手动处理✅ 简单实现并行

💡 结论:async/await 不是"新方案",而是让异步编程更符合人类思维的语法糖。


💡 七、终极总结:如何用好 async/await?

  1. 所有异步操作都用 async/awaitfetchfsaxios
  2. 必须包裹在 try/catch 中:避免未处理的 Promise 拒绝
  3. 需要并行时用 Promise.all:避免不必要的串行等待
  4. 在 async 函数外调用main().catch(console.error)
// 优雅的调用方式
(async () => {
  try {
    // 异步操作
  } catch (err) {
    // 错误处理
  }
})();

本文已授权掘金社区发布,转载需注明来源。