一、异步演进之路
1. 回调函数(Callback)—— “回调地狱”
js
编辑
fs.readFile('1.txt', 'utf-8', (err, data1) => {
if (err) throw err;
fs.readFile(data1.trim(), 'utf-8', (err, data2) => {
if (err) throw err;
fs.readFile(data2.trim(), 'utf-8', (err, data3) => {
// ... 嵌套越来越深
});
});
});
✅ 优点:简单直接
❌ 缺点:可读性差、错误处理困难、难以维护
2. ES6 Promise —— 链式调用的曙光
Promise 是一个表示异步操作最终完成或失败的对象。
js
编辑
const readFile = (path) => {
return new Promise((resolve, reject) => {
fs.readFile(path, 'utf-8', (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
};
readFile('1.txt')
.then(data => readFile(data.trim()))
.then(data => readFile(data.trim()))
.catch(err => console.error(err));
✅ 优点:
- 链式调用
.then().then() - 统一错误处理
.catch() - 支持并发
Promise.all()
❌ 缺点:
- 语法仍显冗长
- 无法像同步代码一样书写逻辑
3. ES8 async / await —— 异步代码同步化
async/await 是基于 Promise 的语法糖,让异步代码看起来像同步!
js
编辑
const main = async () => {
try {
const data1 = await readFile('1.txt');
const data2 = await readFile(data1.trim());
const data3 = await readFile(data2.trim());
console.log(data3);
} catch (err) {
console.error(err);
}
};
main();
✅ 优点:
- 代码简洁、逻辑清晰
- 错误处理用
try/catch - 可配合
for...of、if等同步控制流
💡 本质:
await后面必须是 Promise,async函数返回一个 Promise。
二、核心对比
| 特性 | 回调函数 | Promise | async/await |
|---|---|---|---|
| 可读性 | 差(嵌套深) | 中(链式) | 优(同步风格) |
| 错误处理 | 分散 | .catch() 统一 | try/catch |
| 并发支持 | 难 | ✅ Promise.all() | ✅ await Promise.all() |
| 调试难度 | 高 | 中 | 低(堆栈清晰) |
✅ 现代开发推荐:优先使用
async/await,底层封装用 Promise。
三、实际应用场景
场景 1:浏览器 Fetch API
js
编辑
const fetchData = async () => {
const res = await fetch('https://api.github.com/users/shunwu/repos');
const data = await res.json();
console.log(data); // 数组:用户的所有仓库
};
fetchData();
场景 2:Node.js 文件读取(Promise 化)
js
编辑
import { promises as fs } from 'fs'; // 直接使用 Promise 版本
const readConfig = async () => {
const content = await fs.readFile('./config.json', 'utf-8');
return JSON.parse(content);
};
四、大厂高频面试题(附答案)
❓ 面试题 1:async/await 和 Promise 有什么关系?
答:
async/await是 基于 Promise 实现的语法糖。async函数总是返回一个 Promise。await只能用于async函数内部,它会暂停函数执行,等待右侧的 Promise settle(fulfilled/rejected),然后继续。- 本质上,
await p等价于p.then(...),但写法更直观。
❓ 面试题 2:下面代码输出什么?为什么?
js
编辑
console.log(1);
setTimeout(() => console.log(2), 0);
Promise.resolve().then(() => console.log(3));
console.log(4);
答:输出顺序是 1 → 4 → 3 → 2
原因:
console.log(1)和4是同步代码,先执行。Promise.then()属于 微任务(microtask) ,在当前宏任务结束后立即执行。setTimeout是 宏任务(macrotask) ,要等下一轮事件循环。- 事件循环顺序:同步 → 微任务 → 宏任务
❓ 面试题 3:如何用 Promise 实现 sleep(1000)?
答:
js
编辑
const sleep = (ms) => {
return new Promise(resolve => setTimeout(resolve, ms));
};
// 使用
await sleep(1000); // 暂停 1 秒
这是模拟延时的经典方法,常用于测试或节流。
❓ 面试题 4:Promise.all 和 Promise.allSettled 有什么区别?
答:
| 方法 | 行为 | 适用场景 |
|---|---|---|
Promise.all([...]) | 只要有一个 reject,立即 reject | 需要全部成功才继续(如并行加载资源) |
Promise.allSettled([...]) | 等待所有 Promise 结束(无论成功/失败) ,返回 { status, value/reason } | 需要处理部分失败的情况(如批量请求) |
示例:
js
编辑
const results = await Promise.allSettled([
fetch('/api/user'),
fetch('/api/posts')
]);
// 即使一个失败,也能拿到另一个的结果
❓ 面试题 5:async 函数中的异常如何捕获?
答:两种方式:
方式 1:try/catch
js
编辑
const fn = async () => {
try {
await someAsyncTask();
} catch (err) {
console.error('捕获异常:', err);
}
};
方式 2:调用时用 .catch()
js
编辑
fn().catch(err => console.error(err));
⚠️ 注意:如果既不用
try/catch也不处理返回的 Promise,会导致 未处理的 Promise rejection,可能使程序崩溃(Node.js)或静默失败(浏览器)。
五、总结
- 回调函数 → 基础但易陷“地狱”
- Promise(ES6) → 解决链式调用和错误处理
- async/await(ES8) → 让异步代码像同步一样写,现代开发首选
🚀 最佳实践:
- 封装异步操作为返回 Promise 的函数
- 业务逻辑用
async/await编写- 始终处理错误(
try/catch或.catch())- 并发请求用
Promise.all/allSettled