从 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 函数 → 返回 Promise
↓
await 等待 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();
关键点:
await会暂停函数执行,直到 Promise 解析.json()本身返回 Promise,所以也要用awaittry/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 是异步编程的终极方案?
| 特性 | 回调函数 | Promise | async/await |
|---|---|---|---|
| 代码可读性 | ❌ 严重嵌套 | ✅ 链式调用 | ✅ 同步写法 |
| 错误处理 | ❌ 分散处理 | ✅ 统一 catch | ✅ try/catch |
| 逻辑流程 | ❌ 扭曲 | ✅ 线性 | ✅ 线性 |
| 与同步代码一致性 | ❌ 无 | ❌ 有差异 | ✅ 一致 |
| 性能优化 | ❌ 无法并行 | ❌ 需手动处理 | ✅ 简单实现并行 |
💡 结论:async/await 不是"新方案",而是让异步编程更符合人类思维的语法糖。
💡 七、终极总结:如何用好 async/await?
- 所有异步操作都用 async/await:
fetch、fs、axios等 - 必须包裹在 try/catch 中:避免未处理的 Promise 拒绝
- 需要并行时用 Promise.all:避免不必要的串行等待
- 在 async 函数外调用:
main().catch(console.error)
// 优雅的调用方式
(async () => {
try {
// 异步操作
} catch (err) {
// 错误处理
}
})();
本文已授权掘金社区发布,转载需注明来源。