JavaScript 异步编程完全指南:从入门到精通
目录
javascript
第一部分:基础概念
├── 1. 为什么需要异步
├── 2. 事件循环机制
└── 3. 任务队列
第二部分:回调函数
├── 4. 回调基础
└── 5. 回调地狱
第三部分:Promise
├── 6. Promise 基础
├── 7. Promise 链式调用
├── 8. Promise 错误处理
├── 9. Promise 静态方法
└── 10. 手写 Promise
第四部分:Async/Await
├── 11. 基本语法
├── 12. 错误处理
└── 13. 常见模式
第五部分:高级异步模式
├── 14. Generator 与异步迭代
├── 15. 并发控制
├── 16. 发布/订阅与事件驱动
└── 17. RxJS 响应式编程简介
第六部分:实战与最佳实践
├── 18. 真实项目场景
├── 19. 性能优化
└── 20. 常见陷阱与调试
第一部分:基础概念
1. 为什么需要异步
1.1 JavaScript 是单线程语言
javascript
// JavaScript 只有一个主线程执行代码
// 如果所有操作都是同步的,耗时操作会阻塞后续代码
console.log("开始");
// 假设这是一个同步的网络请求(伪代码),需要3秒
// const data = syncFetch("https://api.example.com/data"); // 阻塞3秒!
console.log("结束"); // 必须等上面完成才能执行
1.2 同步 vs 异步的直观对比
javascript
// ============ 同步模型(阻塞)============
// 想象你在餐厅:点菜 → 等厨师做完 → 再点下一道
function syncExample() {
const start = Date.now();
// 模拟同步阻塞(千万别在实际项目中这样做!)
function sleep(ms) {
const end = Date.now() + ms;
while (Date.now() < end) {} // 忙等待,阻塞线程
}
console.log("任务1: 开始");
sleep(2000); // 阻塞2秒
console.log("任务1: 完成");
console.log("任务2: 开始");
sleep(1000); // 阻塞1秒
console.log("任务2: 完成");
console.log(`总耗时: ${Date.now() - start}ms`); // ≈ 3000ms
}
// ============ 异步模型(非阻塞)============
// 想象你在餐厅:点完所有菜 → 哪道先做好就先上
function asyncExample() {
const start = Date.now();
console.log("任务1: 开始");
setTimeout(() => {
console.log(`任务1: 完成 (${Date.now() - start}ms)`);
}, 2000);
console.log("任务2: 开始");
setTimeout(() => {
console.log(`任务2: 完成 (${Date.now() - start}ms)`);
}, 1000);
console.log("两个任务都已发起");
// 输出顺序:
// 任务1: 开始
// 任务2: 开始
// 两个任务都已发起
// 任务2: 完成 (≈1000ms) ← 先完成的先执行
// 任务1: 完成 (≈2000ms)
// 总耗时 ≈ 2000ms(而非3000ms)
}
1.3 常见的异步操作
javascript
// 1. 定时器
setTimeout(() => console.log("延迟执行"), 1000);
setInterval(() => console.log("重复执行"), 1000);
// 2. 网络请求
fetch("https://api.github.com/users/octocat")
.then(res => res.json())
.then(data => console.log(data));
// 3. DOM 事件
document.addEventListener("click", (e) => {
console.log("用户点击了", e.target);
});
// 4. 文件读写(Node.js)
const fs = require("fs");
fs.readFile("./data.txt", "utf8", (err, data) => {
console.log(data);
});
// 5. 数据库操作
// db.query("SELECT * FROM users", (err, rows) => { ... });
// 6. Web Workers(浏览器多线程)
// const worker = new Worker("worker.js");
// worker.onmessage = (e) => console.log(e.data);
2. 事件循环机制(Event Loop)
这是理解 JS 异步的核心,必须彻底掌握!
2.1 执行模型全景图
scss
┌─────────────────────────────────────────────────────┐
│ 调用栈 (Call Stack) │
│ ┌─────────────────────────────────────────────┐ │
│ │ 当前正在执行的函数 │ │
│ └─────────────────────────────────────────────┘ │
└───────────────────────┬─────────────────────────────┘
│
▼ 当调用栈为空时
┌─────────────────────────────────────────────────────┐
│ 事件循环 (Event Loop) │
│ 不断检查:调用栈空了吗?队列里有任务吗? │
└───────┬──────────────────────────────┬──────────────┘
│ │
▼ 优先 ▼ 其次
┌───────────────────┐ ┌─────────────────────────┐
│ 微任务队列 │ │ 宏任务队列 │
│ (Microtask Queue) │ │ (Macrotask Queue) │
│ │ │ │
│ • Promise.then │ │ • setTimeout/setInterval │
│ • MutationObserver │ │ • I/O 回调 │
│ • queueMicrotask │ │ • UI 渲染 │
│ • process.nextTick │ │ • setImmediate (Node) │
│ (Node.js) │ │ • requestAnimationFrame │
└───────────────────┘ └─────────────────────────┘
2.2 事件循环执行顺序
javascript
console.log("1. 同步代码 - script start");
setTimeout(() => {
console.log("6. 宏任务 - setTimeout");
}, 0);
Promise.resolve()
.then(() => {
console.log("3. 微任务 - Promise 1");
})
.then(() => {
console.log("5. 微任务 - Promise 2");
});
queueMicrotask(() => {
console.log("4. 微任务 - queueMicrotask");
});
console.log("2. 同步代码 - script end");
// 输出顺序(带编号):
// 1. 同步代码 - script start
// 2. 同步代码 - script end
// 3. 微任务 - Promise 1
// 4. 微任务 - queueMicrotask
// 5. 微任务 - Promise 2
// 6. 宏任务 - setTimeout
2.3 事件循环的详细步骤
javascript
/*
* 事件循环算法:
*
* 1. 执行全局同步代码(这本身就是一个宏任务)
* 2. 调用栈清空后,检查微任务队列
* 3. 依次执行所有微任务(包括执行过程中新产生的微任务)
* 4. 微任务队列清空后,进行一次 UI 渲染(如果需要)
* 5. 取出一个宏任务执行
* 6. 回到步骤 2
*
* 关键:每执行完一个宏任务,就要清空所有微任务
*/
// 经典面试题:详细分析执行顺序
console.log("script start"); // 同步 → 立即执行
async function async1() {
console.log("async1 start"); // 同步 → 立即执行
await async2();
// await 之后的代码相当于 promise.then 的回调
console.log("async1 end"); // 微任务
}
async function async2() {
console.log("async2"); // 同步 → 立即执行
}
setTimeout(function() {
console.log("setTimeout"); // 宏任务
}, 0);
async1();
new Promise(function(resolve) {
console.log("promise1"); // 同步 → 立即执行
resolve();
}).then(function() {
console.log("promise2"); // 微任务
});
console.log("script end"); // 同步 → 立即执行
/*
* 执行分析:
*
* === 第一轮:执行同步代码(全局宏任务)===
* 调用栈:[global]
* 输出:script start
* 输出:async1 start
* 输出:async2 (async2 函数体是同步的)
* → async1 中 await 后面的代码放入微任务队列
* 输出:promise1 (Promise 构造函数是同步的)
* → then 回调放入微任务队列
* 输出:script end
*
* 此时微任务队列:[async1 end, promise2]
* 此时宏任务队列:[setTimeout]
*
* === 第二轮:清空微任务队列 ===
* 输出:async1 end
* 输出:promise2
*
* === 第三轮:取一个宏任务 ===
* 输出:setTimeout
*
* 最终顺序:
* script start → async1 start → async2 → promise1 →
* script end → async1 end → promise2 → setTimeout
*/
2.4 微任务中产生微任务
javascript
// 微任务中可以继续产生微任务,会在同一轮全部执行完
console.log("start");
setTimeout(() => console.log("timeout"), 0);
Promise.resolve()
.then(() => {
console.log("promise 1");
// 在微任务中产生新的微任务
Promise.resolve().then(() => {
console.log("promise 1-1");
Promise.resolve().then(() => {
console.log("promise 1-1-1");
});
});
})
.then(() => {
console.log("promise 2");
});
console.log("end");
// 输出:start → end → promise 1 → promise 1-1 → promise 2 → promise 1-1-1 → timeout
// 注意:微任务全部执行完才会执行宏任务 setTimeout
// ⚠️ 危险:无限产生微任务会阻塞渲染
// Promise.resolve().then(function loop() {
// Promise.resolve().then(loop); // 永远清不完微任务,页面卡死!
// });
2.5 Node.js 事件循环(与浏览器的区别)
javascript
/*
* Node.js 事件循环有 6 个阶段:
*
* ┌───────────────────────────┐
* │ timers │ ← setTimeout, setInterval
* ├───────────────────────────┤
* │ pending callbacks │ ← 系统级回调(如 TCP 错误)
* ├───────────────────────────┤
* │ idle, prepare │ ← 内部使用
* ├───────────────────────────┤
* │ poll │ ← I/O 回调,在此阶段可能阻塞
* ├───────────────────────────┤
* │ check │ ← setImmediate
* ├───────────────────────────┤
* │ close callbacks │ ← socket.on('close')
* └───────────────────────────┘
*
* 每个阶段之间都会执行 process.nextTick 和 Promise 微任务
* process.nextTick 优先级高于 Promise.then
*/
// Node.js 特有的优先级演示
process.nextTick(() => console.log("1. nextTick"));
Promise.resolve().then(() => console.log("2. promise"));
setTimeout(() => console.log("3. setTimeout"), 0);
setImmediate(() => console.log("4. setImmediate"));
// Node.js 输出:
// 1. nextTick (最高优先级微任务)
// 2. promise (普通微任务)
// 3. setTimeout (timers 阶段)
// 4. setImmediate (check 阶段)
3. 调用栈深入理解
javascript
// 调用栈是 LIFO(后进先出)结构
function multiply(a, b) {
return a * b; // 4. multiply 执行完毕,弹出栈
}
function square(n) {
return multiply(n, n); // 3. 调用 multiply,入栈
} // 5. square 执行完毕,弹出栈
function printSquare(n) {
const result = square(n); // 2. 调用 square,入栈
console.log(result); // 6. 调用 console.log
}
printSquare(4); // 1. printSquare 入栈
/*
* 调用栈变化过程:
*
* Step 1: [printSquare]
* Step 2: [printSquare, square]
* Step 3: [printSquare, square, multiply]
* Step 4: [printSquare, square] ← multiply 返回
* Step 5: [printSquare] ← square 返回
* Step 6: [printSquare, console.log]
* Step 7: [printSquare] ← console.log 返回
* Step 8: [] ← printSquare 返回
* 调用栈空 → 事件循环检查队列
*/
// 栈溢出演示
function infiniteRecursion() {
return infiniteRecursion(); // 无限递归
}
// infiniteRecursion();
// RangeError: Maximum call stack size exceeded
第二部分:回调函数
4. 回调函数基础
4.1 什么是回调
javascript
// 回调函数:作为参数传递给另一个函数,在适当时机被调用的函数
// === 同步回调 ===
const numbers = [1, 2, 3, 4, 5];
// forEach 的回调是同步执行的
numbers.forEach(function(num) {
console.log(num); // 立即执行
});
console.log("forEach 之后"); // 在所有回调之后
// map 也是同步回调
const doubled = numbers.map(num => num * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
// === 异步回调 ===
console.log("请求开始");
// setTimeout 的回调是异步执行的
setTimeout(function callback() {
console.log("1秒后执行"); // 至少1秒后
}, 1000);
console.log("请求已发起"); // 先于回调执行
4.2 Node.js 错误优先回调(Error-First Callback)
javascript
const fs = require('fs');
// Node.js 约定:回调的第一个参数是 error
fs.readFile('./config.json', 'utf8', function(err, data) {
if (err) {
// 错误处理
if (err.code === 'ENOENT') {
console.error('文件不存在');
} else {
console.error('读取失败:', err.message);
}
return; // 提前返回,不执行后续逻辑
}
// 成功处理
const config = JSON.parse(data);
console.log('配置:', config);
});
// 自己实现错误优先回调风格
function fetchUserData(userId, callback) {
setTimeout(() => {
if (!userId) {
callback(new Error('userId is required'));
return;
}
// 模拟数据库查询
const user = {
id: userId,
name: 'Alice',
email: 'alice@example.com'
};
callback(null, user); // 第一个参数为 null 表示没有错误
}, 1000);
}
// 使用
fetchUserData(1, function(err, user) {
if (err) {
console.error('获取用户失败:', err.message);
return;
}
console.log('用户信息:', user);
});
4.3 实际应用:事件监听器
javascript
// DOM 事件回调
const button = document.getElementById('myButton');
// 点击事件
button.addEventListener('click', function(event) {
console.log('按钮被点击', event.target);
});
// 可以添加多个回调
button.addEventListener('click', handleClick);
button.addEventListener('mouseenter', handleHover);
button.addEventListener('mouseleave', handleLeave);
function handleClick(e) {
console.log('处理点击');
}
function handleHover(e) {
e.target.style.backgroundColor = '#eee';
}
function handleLeave(e) {
e.target.style.backgroundColor = '';
}
// 移除事件监听(必须传入同一个函数引用)
button.removeEventListener('click', handleClick);
// ⚠️ 常见错误:匿名函数无法移除
// button.addEventListener('click', () => {}); // 无法移除这个监听器
5. 回调地狱(Callback Hell)
5.1 问题演示
javascript
// 需求:获取用户信息 → 获取用户订单 → 获取订单详情 → 获取物流信息
function getUserInfo(userId, callback) {
setTimeout(() => callback(null, { id: userId, name: 'Alice' }), 300);
}
function getOrders(userId, callback) {
setTimeout(() => callback(null, [{ orderId: 101 }, { orderId: 102 }]), 300);
}
function getOrderDetail(orderId, callback) {
setTimeout(() => callback(null, { orderId, product: 'iPhone', trackingId: 'TK001' }), 300);
}
function getShippingInfo(trackingId, callback) {
setTimeout(() => callback(null, { trackingId, status: '运输中', location: '上海' }), 300);
}
// 😱 回调地狱 - 金字塔形代码
getUserInfo(1, function(err, user) {
if (err) {
console.error('获取用户失败', err);
return;
}
console.log('用户:', user.name);
getOrders(user.id, function(err, orders) {
if (err) {
console.error('获取订单失败', err);
return;
}
console.log('订单数:', orders.length);
getOrderDetail(orders[0].orderId, function(err, detail) {
if (err) {
console.error('获取详情失败', err);
return;
}
console.log('商品:', detail.product);
getShippingInfo(detail.trackingId, function(err, shipping) {
if (err) {
console.error('获取物流失败', err);
return;
}
console.log('物流状态:', shipping.status);
console.log('当前位置:', shipping.location);
// 如果还有更多层嵌套...
// 代码会越来越难以维护
});
});
});
});
5.2 回调地狱的问题
javascript
/*
* 回调地狱的三大问题:
*
* 1. 可读性差(Readability)
* - 代码向右缩进,形成"金字塔"
* - 逻辑流程难以追踪
*
* 2. 错误处理困难(Error Handling)
* - 每一层都需要单独处理错误
* - 无法统一 catch
* - 容易遗漏错误处理
*
* 3. 控制反转(Inversion of Control)
* - 把回调交给第三方库,你无法控制:
* - 回调是否会被调用
* - 回调会被调用几次
* - 回调是同步还是异步调用
* - 回调的参数是否正确
*/
// 控制反转的危险示例
function riskyThirdPartyLib(callback) {
// 你无法控制第三方库如何调用你的回调
callback(); // 调用了一次
callback(); // 又调用了一次! ← 可能导致重复计费等严重问题
// 或者根本不调用
// 或者同步调用(不在下一个 tick)
}
5.3 改善回调地狱的方法(不用 Promise)
javascript
// 方法1:命名函数 + 扁平化
function handleUser(err, user) {
if (err) return console.error('获取用户失败', err);
console.log('用户:', user.name);
getOrders(user.id, handleOrders);
}
function handleOrders(err, orders) {
if (err) return console.error('获取订单失败', err);
console.log('订单数:', orders.length);
getOrderDetail(orders[0].orderId, handleDetail);
}
function handleDetail(err, detail) {
if (err) return console.error('获取详情失败', err);
console.log('商品:', detail.product);
getShippingInfo(detail.trackingId, handleShipping);
}
function handleShipping(err, shipping) {
if (err) return console.error('获取物流失败', err);
console.log('物流状态:', shipping.status);
}
// 启动链条
getUserInfo(1, handleUser);
// 代码变平了,但函数间的关系不够直观
// 方法2:使用工具库(如 async.js)
const async = require('async');
async.waterfall([
function(cb) {
getUserInfo(1, cb);
},
function(user, cb) {
console.log('用户:', user.name);
getOrders(user.id, cb);
},
function(orders, cb) {
console.log('订单数:', orders.length);
getOrderDetail(orders[0].orderId, cb);
},
function(detail, cb) {
console.log('商品:', detail.product);
getShippingInfo(detail.trackingId, cb);
}
], function(err, shipping) {
if (err) {
console.error('流程出错:', err);
return;
}
console.log('物流状态:', shipping.status);
});
第三部分:Promise
6. Promise 基础
6.1 什么是 Promise
javascript
/*
* Promise 是一个代表异步操作最终结果的对象
*
* 三种状态:
* ┌─────────┐ resolve(value) ┌───────────┐
* │ pending │ ──────────────────→ │ fulfilled │
* │ (等待中) │ │ (已成功) │
* └─────────┘ └───────────┘
* │
* │ reject(reason) ┌───────────┐
* └────────────────────────→ │ rejected │
* │ (已失败) │
* └───────────┘
*
* 重要特性:
* 1. 状态一旦改变就不可逆(pending → fulfilled 或 pending → rejected)
* 2. 状态改变后,任何时候都可以获取结果
*/
// 创建 Promise
const promise = new Promise(function(resolve, reject) {
// 这个函数叫做 executor(执行器),立即同步执行
console.log("executor 执行了"); // 同步执行!
// 异步操作
setTimeout(() => {
const success = true;
if (success) {
resolve("操作成功的数据"); // 将 promise 变为 fulfilled
} else {
reject(new Error("操作失败的原因")); // 将 promise 变为 rejected
}
}, 1000);
});
console.log("Promise 创建后"); // 在 executor 之后,在异步回调之前
// 消费 Promise
promise.then(
function onFulfilled(value) {
console.log("成功:", value);
},
function onRejected(reason) {
console.log("失败:", reason.message);
}
);
6.2 将回调转换为 Promise
javascript
// 改造之前回调风格的函数
function getUserInfo(userId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (!userId) {
reject(new Error('userId is required'));
return;
}
resolve({ id: userId, name: 'Alice' });
}, 300);
});
}
function getOrders(userId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve([{ orderId: 101 }, { orderId: 102 }]);
}, 300);
});
}
function getOrderDetail(orderId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ orderId, product: 'iPhone', trackingId: 'TK001' });
}, 300);
});
}
function getShippingInfo(trackingId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ trackingId, status: '运输中', location: '上海' });
}, 300);
});
}
// Node.js 提供的通用转换工具
const { promisify } = require('util');
const fs = require('fs');
// 将回调风格的 fs.readFile 转为 Promise 风格
const readFile = promisify(fs.readFile);
readFile('./config.json', 'utf8').then(data => console.log(data));
// 手写 promisify
function myPromisify(fn) {
return function(...args) {
return new Promise((resolve, reject) => {
fn(...args, (err, result) => {
if (err) reject(err);
else resolve(result);
});
});
};
}
6.3 Promise 基本使用
javascript
// .then() 处理成功
// .catch() 处理失败
// .finally() 无论成功失败都执行
function fetchData(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (url.includes("error")) {
reject(new Error(`请求失败: ${url}`));
} else {
resolve({ data: `来自 ${url} 的数据`, status: 200 });
}
}, 500);
});
}
fetchData("https://api.example.com/data")
.then(result => {
console.log("成功:", result.data);
})
.catch(error => {
console.error("失败:", error.message);
})
.finally(() => {
console.log("请求完成(无论成败)");
// 常用于:隐藏 loading、释放资源等
});
7. Promise 链式调用
7.1 链式调用原理
javascript
/*
* .then() 返回一个新的 Promise,这是链式调用的关键
*
* 返回值规则:
* 1. return 普通值 → 新 Promise 以该值 resolve
* 2. return Promise → 新 Promise 跟随该 Promise 的状态
* 3. throw 错误 → 新 Promise 以该错误 reject
* 4. 不 return → 新 Promise 以 undefined resolve
*/
// 基本链式调用
Promise.resolve(1)
.then(value => {
console.log(value); // 1
return value + 1; // return 普通值
})
.then(value => {
console.log(value); // 2
return Promise.resolve(value + 1); // return Promise
})
.then(value => {
console.log(value); // 3
// 不 return
})
.then(value => {
console.log(value); // undefined
throw new Error("出错了"); // throw 错误
})
.catch(err => {
console.error(err.message); // "出错了"
return "recovered"; // catch 也可以 return,链继续
})
.then(value => {
console.log(value); // "recovered"
});
7.2 用链式调用解决回调地狱
javascript
// 之前的回调地狱,用 Promise 链改写
getUserInfo(1)
.then(user => {
console.log('用户:', user.name);
return getOrders(user.id);
})
.then(orders => {
console.log('订单数:', orders.length);
return getOrderDetail(orders[0].orderId);
})
.then(detail => {
console.log('商品:', detail.product);
return getShippingInfo(detail.trackingId);
})
.then(shipping => {
console.log('物流状态:', shipping.status);
console.log('当前位置:', shipping.location);
})
.catch(err => {
// 统一错误处理!任何一步失败都会到这里
console.error('流程出错:', err.message);
});
// 代码扁平化、错误统一处理、流程清晰
7.3 链式调用中传递数据
javascript
// 问题:后面的 .then 需要用到前面多个步骤的数据
// 方案1:闭包(简单但变量多了会乱)
let savedUser;
getUserInfo(1)
.then(user => {
savedUser = user; // 保存到外部变量
return getOrders(user.id);
})
.then(orders => {
console.log(savedUser.name, '有', orders.length, '个订单');
});
// 方案2:逐层传递对象(推荐)
getUserInfo(1)
.then(user => {
return getOrders(user.id).then(orders => ({
user,
orders
}));
})
.then(({ user, orders }) => {
console.log(user.name, '有', orders.length, '个订单');
return getOrderDetail(orders[0].orderId).then(detail => ({
user,
orders,
detail
}));
})
.then(({ user, orders, detail }) => {
console.log(`${user.name} 购买了 ${detail.product}`);
});
// 方案3:async/await(最佳方案,后面会讲)
async function getFullInfo() {
const user = await getUserInfo(1);
const orders = await getOrders(user.id);
const detail = await getOrderDetail(orders[0].orderId);
const shipping = await getShippingInfo(detail.trackingId);
// 所有变量都在同一作用域!
console.log(`${user.name} 购买了 ${detail.product},${shipping.status}`);
}
8. Promise 错误处理
8.1 错误捕获机制
javascript
// .catch() 相当于 .then(undefined, onRejected)
// 方式1:.then 的第二个参数
promise.then(
value => console.log(value),
error => console.error(error) // 只能捕获 promise 本身的错误
);
// 方式2:.catch()(推荐)
promise
.then(value => {
// 如果这里抛出错误...
throw new Error("then 中的错误");
})
.catch(error => {
// .catch 可以捕获前面所有 .then 中的错误
console.error(error.message);
});
// 区别演示
const p = Promise.reject(new Error("初始错误"));
// ❌ .then 的第二个参数无法捕获同一个 .then 的第一个参数中的错误
p.then(
value => { throw new Error("then 中的错误"); },
error => console.log("捕获:", error.message) // 捕获的是"初始错误"
);
// ✅ .catch 可以捕获链上任何位置的错误
p.then(value => {
throw new Error("then 中的错误");
}).catch(error => {
console.log("捕获:", error.message); // 可以捕获两种错误
});
8.2 错误传播
javascript
// 错误会沿着链向下传播,直到被 catch
Promise.resolve("start")
.then(v => {
console.log("step 1:", v);
throw new Error("step 1 出错");
})
.then(v => {
console.log("step 2:", v); // ❌ 跳过!不执行
})
.then(v => {
console.log("step 3:", v); // ❌ 跳过!不执行
})
.catch(err => {
console.log("捕获错误:", err.message); // "step 1 出错"
return "error handled"; // 错误恢复
})
.then(v => {
console.log("step 4:", v); // ✅ "error handled" --- 继续执行
});
8.3 多层错误处理
javascript
// 可以在链的不同位置放置 catch
fetchData("/api/users")
.then(users => {
return processUsers(users);
})
.catch(err => {
// 处理获取/处理用户数据的错误
console.warn("用户数据处理失败,使用缓存:", err.message);
return getCachedUsers(); // 降级方案
})
.then(users => {
return fetchData(`/api/users/${users[0].id}/orders`);
})
.catch(err => {
// 处理获取订单的错误
console.warn("订单获取失败:", err.message);
return []; // 返回空数组作为默认值
})
.then(orders => {
renderOrders(orders);
})
.catch(err => {
// 最终的错误兜底
showErrorPage(err);
});
8.4 未处理的 Promise 拒绝
javascript
// ⚠️ 危险:没有 catch 的 rejected Promise
const unhandled = Promise.reject(new Error("无人处理的错误"));
// 浏览器控制台会警告:UnhandledPromiseRejectionWarning
// Node.js 15+ 会直接终止进程!
// 全局捕获未处理的 rejection
// 浏览器环境
window.addEventListener('unhandledrejection', event => {
console.error('未处理的 Promise 拒绝:', event.reason);
event.preventDefault(); // 阻止默认的控制台错误输出
// 上报错误到监控系统
reportError({
type: 'unhandledrejection',
message: event.reason?.message || String(event.reason),
stack: event.reason?.stack
});
});
// Node.js 环境
process.on('unhandledRejection', (reason, promise) => {
console.error('未处理的 Promise 拒绝:', reason);
// 推荐:记录日志后优雅退出
});
process.on('rejectionHandled', (promise) => {
// 之前未处理的 rejection 后来被处理了
console.log('延迟处理的 rejection');
});
9. Promise 静态方法
9.1 Promise.resolve() 和 Promise.reject()
javascript
// Promise.resolve() --- 创建一个 fulfilled 的 Promise
const p1 = Promise.resolve(42);
p1.then(v => console.log(v)); // 42
// 传入 Promise 会直接返回
const p2 = Promise.resolve(Promise.resolve("hello"));
p2.then(v => console.log(v)); // "hello"(不会嵌套)
// 传入 thenable 对象(有 then 方法的对象)
const thenable = {
then(resolve, reject) {
resolve("from thenable");
}
};
Promise.resolve(thenable).then(v => console.log(v)); // "from thenable"
// Promise.reject() --- 创建一个 rejected 的 Promise
const p3 = Promise.reject(new Error("失败"));
p3.catch(err => console.log(err.message)); // "失败"
// 注意:reject 不会解包 Promise
const p4 = Promise.reject(Promise.resolve("嵌套"));
p4.catch(v => console.log(v)); // Promise {<fulfilled>: "嵌套"} ← 注意是 Promise 对象
9.2 Promise.all() --- 全部成功才成功
javascript
/*
* Promise.all(iterable)
* - 所有 Promise 都 fulfilled → 结果数组(顺序与输入一致)
* - 任何一个 rejected → 立即 rejected(快速失败)
*/
// 并行请求多个 API
const userPromise = fetch('/api/user').then(r => r.json());
const ordersPromise = fetch('/api/orders').then(r => r.json());
const settingsPromise = fetch('/api/settings').then(r => r.json());
Promise.all([userPromise, ordersPromise, settingsPromise])
.then(([user, orders, settings]) => {
// 三个请求都完成后才执行
console.log('用户:', user);
console.log('订单:', orders);
console.log('设置:', settings);
renderDashboard(user, orders, settings);
})
.catch(err => {
// 任何一个失败就到这里
console.error('加载仪表盘失败:', err);
});
// 空数组
Promise.all([]).then(v => console.log(v)); // [](立即 fulfilled)
// 包含非 Promise 值
Promise.all([1, "hello", Promise.resolve(true)])
.then(values => console.log(values)); // [1, "hello", true]
// 实际应用:批量上传文件
async function uploadFiles(files) {
const uploadPromises = files.map(file => {
return fetch('/api/upload', {
method: 'POST',
body: file
});
});
try {
const results = await Promise.all(uploadPromises);
console.log('全部上传成功');
return results;
} catch (err) {
console.error('有文件上传失败:', err);
throw err;
}
}
9.3 Promise.allSettled() --- 等所有完成(不论成败)
javascript
/*
* Promise.allSettled(iterable) [ES2020]
* - 等待所有 Promise 完成(settled = fulfilled 或 rejected)
* - 永远不会 reject
* - 结果数组中每个元素:
* { status: "fulfilled", value: ... }
* { status: "rejected", reason: ... }
*/
const promises = [
fetch('/api/user'),
fetch('/api/nonexistent'), // 这个会失败
fetch('/api/settings')
];
Promise.allSettled(promises)
.then(results => {
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`请求 ${index} 成功:`, result.value);
} else {
console.log(`请求 ${index} 失败:`, result.reason);
}
});
// 过滤出成功的结果
const successful = results
.filter(r => r.status === 'fulfilled')
.map(r => r.value);
// 过滤出失败的结果
const failed = results
.filter(r => r.status === 'rejected')
.map(r => r.reason);
console.log(`${successful.length} 个成功, ${failed.length} 个失败`);
});
// 实际场景:批量通知(不因某个失败就停止)
async function notifyUsers(userIds) {
const notifications = userIds.map(id => sendNotification(id));
const results = await Promise.allSettled(notifications);
const report = {
total: results.length,
success: results.filter(r => r.status === 'fulfilled').length,
failed: results.filter(r => r.status === 'rejected').length,
errors: results
.filter(r => r.status === 'rejected')
.map(r => r.reason.message)
};
console.log('通知报告:', report);
return report;
}
9.4 Promise.race() --- 最快的那个
javascript
/*
* Promise.race(iterable)
* - 返回最先 settle 的 Promise 的结果(无论成功失败)
*/
// 超时控制
function fetchWithTimeout(url, timeoutMs = 5000) {
const fetchPromise = fetch(url);
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => {
reject(new Error(`请求超时 (${timeoutMs}ms): ${url}`));
}, timeoutMs);
});
return Promise.race([fetchPromise, timeoutPromise]);
}
// 使用
fetchWithTimeout('https://api.example.com/data', 3000)
.then(response => response.json())
.then(data => console.log('数据:', data))
.catch(err => console.error(err.message));
// 多个数据源竞速
function fetchFromFastestCDN(resource) {
return Promise.race([
fetch(`https://cdn1.example.com/${resource}`),
fetch(`https://cdn2.example.com/${resource}`),
fetch(`https://cdn3.example.com/${resource}`)
]);
}
// 注意:其他未完成的 Promise 不会被取消,只是结果被忽略
9.5 Promise.any() --- 第一个成功的
javascript
/*
* Promise.any(iterable) [ES2021]
* - 返回第一个 fulfilled 的 Promise
* - 全部 rejected → 返回 AggregateError
*
* vs Promise.race():
* - race: 第一个 settled(无论成败)
* - any: 第一个 fulfilled(忽略 rejected)
*/
// 从多个镜像获取资源
const mirrors = [
fetch('https://mirror1.example.com/data.json'),
fetch('https://mirror2.example.com/data.json'),
fetch('https://mirror3.example.com/data.json')
];
Promise.any(mirrors)
.then(response => {
console.log('从最快的可用镜像获取到数据');
return response.json();
})
.catch(err => {
// AggregateError: All promises were rejected
console.error('所有镜像都不可用');
console.error('错误列表:', err.errors); // 所有错误的数组
});
// 对比 race 和 any
const p1 = new Promise((_, reject) => setTimeout(() => reject('p1 fail'), 100));
const p2 = new Promise((resolve) => setTimeout(() => resolve('p2 success'), 200));
Promise.race([p1, p2]).catch(e => console.log('race:', e)); // "race: p1 fail"
Promise.any([p1, p2]).then(v => console.log('any:', v)); // "any: p2 success"
9.6 Promise.withResolvers() [ES2024]
javascript
/*
* Promise.withResolvers() --- 将 resolve/reject 提取到外部
* 返回 { promise, resolve, reject }
*/
// 之前的写法
let externalResolve, externalReject;
const promise = new Promise((resolve, reject) => {
externalResolve = resolve;
externalReject = reject;
});
// ES2024 新写法
const { promise: p, resolve, reject } = Promise.withResolvers();
// 实际用途:在其他地方控制 Promise 的状态
class EventEmitter {
#listeners = new Map();
waitFor(eventName) {
const { promise, resolve } = Promise.withResolvers();
this.#listeners.set(eventName, resolve);
return promise;
}
emit(eventName, data) {
const resolve = this.#listeners.get(eventName);
if (resolve) {
resolve(data);
this.#listeners.delete(eventName);
}
}
}
const emitter = new EventEmitter();
emitter.waitFor('data').then(data => console.log('收到:', data));
emitter.emit('data', { message: 'hello' }); // 收到: { message: 'hello' }
9.7 静态方法对比总结
javascript
/*
* ┌──────────────────┬───────────────┬─────────────────────────────┐
* │ 方法 │ 何时 resolve │ 何时 reject │
* ├──────────────────┼───────────────┼─────────────────────────────┤
* │ Promise.all │ 全部 fulfilled │ 任一 rejected(快速失败) │
* │ Promise.allSettled│ 全部 settled │ 永不 reject │
* │ Promise.race │ 首个 fulfilled │ 首个 rejected │
* │ Promise.any │ 首个 fulfilled │ 全部 rejected(AggregateError)│
* └──────────────────┴───────────────┴─────────────────────────────┘
*/
// 完整对比示例
const fast = new Promise(resolve => setTimeout(() => resolve('fast'), 100));
const slow = new Promise(resolve => setTimeout(() => resolve('slow'), 500));
const fail = new Promise((_, reject) => setTimeout(() => reject('fail'), 200));
// all: 等全部,一个失败就失败
Promise.all([fast, slow, fail]).catch(e => console.log('all:', e)); // "fail"
// allSettled: 等全部,告诉你每个的结果
Promise.allSettled([fast, slow, fail]).then(r => console.log('allSettled:', r));
// [{status:'fulfilled',value:'fast'}, {status:'fulfilled',value:'slow'}, {status:'rejected',reason:'fail'}]
// race: 第一个完成的(无论成败)
Promise.race([fast, slow, fail]).then(v => console.log('race:', v)); // "fast"
// any: 第一个成功的
Promise.any([fast, slow, fail]).then(v => console.log('any:', v)); // "fast"
10. 手写 Promise(面试高频)
javascript
class MyPromise {
static PENDING = 'pending';
static FULFILLED = 'fulfilled';
static REJECTED = 'rejected';
constructor(executor) {
this.status = MyPromise.PENDING;
this.value = undefined;
this.reason = undefined;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
// 处理 resolve 一个 Promise 的情况
if (value instanceof MyPromise) {
value.then(resolve, reject);
return;
}
if (this.status === MyPromise.PENDING) {
this.status = MyPromise.FULFILLED;
this.value = value;
this.onFulfilledCallbacks.forEach(fn => fn());
}
};
const reject = (reason) => {
if (this.status === MyPromise.PENDING) {
this.status = MyPromise.REJECTED;
this.reason = reason;
this.onRejectedCallbacks.forEach(fn => fn());
}
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onFulfilled, onRejected) {
// 参数默认值:实现值穿透
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };
const promise2 = new MyPromise((resolve, reject) => {
const fulfilledMicrotask = () => {
queueMicrotask(() => {
try {
const x = onFulfilled(this.value);
this.#resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
});
};
const rejectedMicrotask = () => {
queueMicrotask(() => {
try {
const x = onRejected(this.reason);
this.#resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
});
};
if (this.status === MyPromise.FULFILLED) {
fulfilledMicrotask();
} else if (this.status === MyPromise.REJECTED) {
rejectedMicrotask();
} else {
// pending 状态:收集回调
this.onFulfilledCallbacks.push(fulfilledMicrotask);
this.onRejectedCallbacks.push(rejectedMicrotask);
}
});
return promise2;
}
// Promise Resolution Procedure (Promises/A+ 规范核心)
#resolvePromise(promise2, x, resolve, reject) {
// 不能返回自己(防止死循环)
if (promise2 === x) {
reject(new TypeError('Chaining cycle detected'));
return;
}
if (x instanceof MyPromise) {
x.then(resolve, reject);
} else if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
// 处理 thenable
let called = false;
try {
const then = x.then;
if (typeof then === 'function') {
then.call(x,
y => {
if (called) return;
called = true;
this.#resolvePromise(promise2, y, resolve, reject);
},
r => {
if (called) return;
called = true;
reject(r);
}
);
} else {
resolve(x);
}
} catch (error) {
if (called) return;
called = true;
reject(error);
}
} else {
resolve(x);
}
}
catch(onRejected) {
return this.then(undefined, onRejected);
}
finally(callback) {
return this.then(
value => MyPromise.resolve(callback()).then(() => value),
reason => MyPromise.resolve(callback()).then(() => { throw reason; })
);
}
static resolve(value) {
if (value instanceof MyPromise) return value;
return new MyPromise(resolve => resolve(value));
}
static reject(reason) {
return new MyPromise((_, reject) => reject(reason));
}
static all(promises) {
return new MyPromise((resolve, reject) => {
const results = [];
let count = 0;
const promiseArr = Array.from(promises);
if (promiseArr.length === 0) {
resolve([]);
return;
}
promiseArr.forEach((p, index) => {
MyPromise.resolve(p).then(
value => {
results[index] = value;
count++;
if (count === promiseArr.length) {
resolve(results);
}
},
reject
);
});
});
}
static race(promises) {
return new MyPromise((resolve, reject) => {
for (const p of promises) {
MyPromise.resolve(p).then(resolve, reject);
}
});
}
static allSettled(promises) {
return new MyPromise((resolve) => {
const results = [];
let count = 0;
const promiseArr = Array.from(promises);
if (promiseArr.length === 0) {
resolve([]);
return;
}
promiseArr.forEach((p, index) => {
MyPromise.resolve(p).then(
value => {
results[index] = { status: 'fulfilled', value };
if (++count === promiseArr.length) resolve(results);
},
reason => {
results[index] = { status: 'rejected', reason };
if (++count === promiseArr.length) resolve(results);
}
);
});
});
}
static any(promises) {
return new MyPromise((resolve, reject) => {
const errors = [];
let count = 0;
const promiseArr = Array.from(promises);
if (promiseArr.length === 0) {
reject(new AggregateError([], 'All promises were rejected'));
return;
}
promiseArr.forEach((p, index) => {
MyPromise.resolve(p).then(resolve, reason => {
errors[index] = reason;
if (++count === promiseArr.length) {
reject(new AggregateError(errors, 'All promises were rejected'));
}
});
});
});
}
}
// 测试
const test = new MyPromise((resolve) => {
setTimeout(() => resolve('hello'), 100);
});
test.then(v => {
console.log(v); // 'hello'
return v + ' world';
}).then(v => {
console.log(v); // 'hello world'
});
第四部分:Async/Await
11. 基本语法
11.1 async 函数
javascript
// async 函数始终返回一个 Promise
// 声明方式
async function fetchUser() {
return { name: 'Alice' }; // 自动包装为 Promise.resolve({ name: 'Alice' })
}
// 等价于
function fetchUser() {
return Promise.resolve({ name: 'Alice' });
}
// 箭头函数
const fetchUser2 = async () => ({ name: 'Bob' });
// 类方法
class UserService {
async getUser(id) {
return { id, name: 'Charlie' };
}
}
// 验证返回 Promise
const result = fetchUser();
console.log(result); // Promise {<fulfilled>: { name: 'Alice' }}
console.log(result instanceof Promise); // true
result.then(user => console.log(user)); // { name: 'Alice' }
11.2 await 关键字
javascript
/*
* await 做了什么:
* 1. 暂停 async 函数的执行
* 2. 等待 Promise settle
* 3. 如果 fulfilled → 返回 value
* 4. 如果 rejected → 抛出 reason
* 5. 恢复 async 函数的执行
*
* await 只能在 async 函数内使用(或在 ES 模块的顶层)
*/
async function demo() {
console.log("开始");
// await 一个 Promise
const value = await new Promise(resolve => {
setTimeout(() => resolve("异步结果"), 1000);
});
console.log("得到:", value); // 1秒后: "异步结果"
// await 非 Promise 值会立即继续(自动包装为 Promise.resolve)
const num = await 42;
console.log("数字:", num); // 42
// await 一个 rejected Promise 会抛出错误
try {
const fail = await Promise.reject(new Error("出错了"));
} catch (err) {
console.error("捕获:", err.message); // "出错了"
}
console.log("结束");
}
demo();
// 顶层 await(ES Modules 中)
// 在 .mjs 文件或 type:"module" 中可以直接使用
// const data = await fetch('/api/data').then(r => r.json());
11.3 用 async/await 改写 Promise 链
javascript
// Promise 链版本
function getFullUserInfo_promise(userId) {
return getUserInfo(userId)
.then(user => {
return getOrders(user.id).then(orders => ({ user, orders }));
})
.then(({ user, orders }) => {
return getOrderDetail(orders[0].orderId)
.then(detail => ({ user, orders, detail }));
})
.then(({ user, orders, detail }) => {
return getShippingInfo(detail.trackingId)
.then(shipping => ({ user, orders, detail, shipping }));
});
}
// async/await 版本 ✨
async function getFullUserInfo(userId) {
const user = await getUserInfo(userId);
const orders = await getOrders(user.id);
const detail = await getOrderDetail(orders[0].orderId);
const shipping = await getShippingInfo(detail.trackingId);
return { user, orders, detail, shipping };
}
// 使用
getFullUserInfo(1).then(info => {
console.log(`${info.user.name} 购买了 ${info.detail.product}`);
console.log(`物流状态: ${info.shipping.status}`);
});
// 或者在另一个 async 函数中
async function main() {
const info = await getFullUserInfo(1);
console.log(info);
}
main();
12. 错误处理
12.1 try/catch
javascript
// 最直接的方式
async function fetchUserSafely(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const user = await response.json();
return user;
} catch (error) {
if (error.name === 'TypeError') {
console.error('网络错误:', error.message);
} else {
console.error('请求失败:', error.message);
}
return null; // 返回默认值
} finally {
hideLoadingSpinner();
}
}
// 嵌套 try/catch 处理不同阶段的错误
async function processOrder(orderId) {
let order;
try {
order = await fetchOrder(orderId);
} catch (err) {
console.error('获取订单失败');
throw new Error('ORDER_FETCH_FAILED');
}
try {
await validateOrder(order);
} catch (err) {
console.error('订单验证失败');
throw new Error('ORDER_VALIDATION_FAILED');
}
try {
const result = await submitPayment(order);
return result;
} catch (err) {
console.error('支付失败');
await rollbackOrder(order);
throw new Error('PAYMENT_FAILED');
}
}
12.2 优雅的错误处理模式
javascript
// 模式1:Go 风格的错误处理
async function to(promise) {
try {
const result = await promise;
return [null, result];
} catch (error) {
return [error, null];
}
}
// 使用
async function main() {
const [err, user] = await to(getUserInfo(1));
if (err) {
console.error('获取用户失败:', err.message);
return;
}
const [err2, orders] = await to(getOrders(user.id));
if (err2) {
console.error('获取订单失败:', err2.message);
return;
}
console.log(user, orders);
}
// 模式2:包装函数添加错误处理
function withErrorHandler(fn, errorHandler) {
return async function(...args) {
try {
return await fn.apply(this, args);
} catch (error) {
return errorHandler(error, ...args);
}
};
}
const safeGetUser = withErrorHandler(
async (id) => {
const response = await fetch(`/api/users/${id}`);
return response.json();
},
(error, id) => {
console.error(`获取用户 ${id} 失败:`, error);
return null;
}
);
const user = await safeGetUser(123);
// 模式3:装饰器模式(TypeScript/提案阶段)
function catchError(target, name, descriptor) {
const original = descriptor.value;
descriptor.value = async function(...args) {
try {
return await original.apply(this, args);
} catch (error) {
console.error(`${name} 执行出错:`, error);
throw error;
}
};
return descriptor;
}
12.3 重试模式
javascript
// 带指数退避的重试
async function retry(fn, options = {}) {
const {
maxRetries = 3,
baseDelay = 1000,
maxDelay = 10000,
backoffFactor = 2,
retryOn = () => true, // 判断是否应该重试
} = options;
let lastError;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await fn(attempt);
} catch (error) {
lastError = error;
if (attempt === maxRetries || !retryOn(error)) {
throw error;
}
const delay = Math.min(
baseDelay * Math.pow(backoffFactor, attempt),
maxDelay
);
// 添加随机抖动,避免雷群效应
const jitter = delay * 0.1 * Math.random();
const totalDelay = delay + jitter;
console.warn(
`第 ${attempt + 1} 次失败,${totalDelay.toFixed(0)}ms 后重试:`,
error.message
);
await new Promise(resolve => setTimeout(resolve, totalDelay));
}
}
throw lastError;
}
// 使用
async function fetchWithRetry(url) {
return retry(
async (attempt) => {
console.log(`第 ${attempt + 1} 次尝试请求 ${url}`);
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
},
{
maxRetries: 3,
baseDelay: 1000,
retryOn: (error) => {
// 只对特定错误重试
return error.message.includes('500') ||
error.message.includes('503') ||
error.name === 'TypeError'; // 网络错误
}
}
);
}
const data = await fetchWithRetry('https://api.example.com/data');
13. Async/Await 常见模式
13.1 串行 vs 并行
javascript
// ❌ 串行执行(慢!)--- 每个请求等上一个完成
async function serial() {
const start = Date.now();
const user = await fetchUser(); // 等 1 秒
const orders = await fetchOrders(); // 再等 1 秒
const products = await fetchProducts(); // 再等 1 秒
console.log(`串行总耗时: ${Date.now() - start}ms`); // ≈ 3000ms
}
// ✅ 并行执行(快!)--- 所有请求同时发出
async function parallel() {
const start = Date.now();
// 先发起所有请求(不 await)
const userPromise = fetchUser();
const ordersPromise = fetchOrders();
const productsPromise = fetchProducts();
// 再等待所有结果
const user = await userPromise;
const orders = await ordersPromise;
const products = await productsPromise;
console.log(`并行总耗时: ${Date.now() - start}ms`); // ≈ 1000ms
}
// ✅ 更推荐用 Promise.all
async function parallelWithAll() {
const start = Date.now();
const [user, orders, products] = await Promise.all([
fetchUser(),
fetchOrders(),
fetchProducts()
]);
console.log(`并行总耗时: ${Date.now() - start}ms`); // ≈ 1000ms
}
// 混合:部分串行,部分并行
async function mixed() {
// 先获取用户(必须先有用户信息)
const user = await fetchUser();
// 然后并行获取用户的订单和收藏(互不依赖)
const [orders, favorites] = await Promise.all([
fetchOrders(user.id),
fetchFavorites(user.id)
]);
return { user, orders, favorites };
}
13.2 循环中的 async/await
javascript
const urls = [
'/api/data/1',
'/api/data/2',
'/api/data/3'
];
// ❌ forEach 中的 await 不会等待!
async function badLoop() {
urls.forEach(async (url) => {
const data = await fetch(url); // forEach 不会等这个
console.log(data);
});
console.log("完成"); // 这行会在所有 fetch 之前执行!
}
// ✅ 串行:for...of
async function serialLoop() {
const results = [];
for (const url of urls) {
const response = await fetch(url);
const data = await response.json();
results.push(data);
console.log(`完成: ${url}`);
}
return results; // 按顺序串行执行
}
// ✅ 并行:Promise.all + map
async function parallelLoop() {
const results = await Promise.all(
urls.map(async (url) => {
const response = await fetch(url);
return response.json();
})
);
return results; // 并行执行,结果顺序与 urls 一致
}
// ✅ 控制并发数的并行(后面会详细讲)
async function limitedParallelLoop() {
const limit = 2; // 最多同时2个请求
const results = [];
for (let i = 0; i < urls.length; i += limit) {
const batch = urls.slice(i, i + limit);
const batchResults = await Promise.all(
batch.map(url => fetch(url).then(r => r.json()))
);
results.push(...batchResults);
}
return results;
}
// ✅ for await...of(异步迭代器)
async function* fetchAll(urls) {
for (const url of urls) {
const response = await fetch(url);
yield await response.json();
}
}
async function asyncIteratorLoop() {
for await (const data of fetchAll(urls)) {
console.log(data);
}
}
13.3 条件异步
javascript
// 根据条件决定是否执行异步操作
async function getUser(id, options = {}) {
const { useCache = true } = options;
// 有缓存就直接返回(同步路径)
if (useCache) {
const cached = cache.get(`user:${id}`);
if (cached) return cached;
}
// 无缓存则请求(异步路径)
const response = await fetch(`/api/users/${id}`);
const user = await response.json();
cache.set(`user:${id}`, user);
return user;
}
// 竞态条件处理
let currentRequestId = 0;
async function search(query) {
const requestId = ++currentRequestId;
const results = await fetchSearchResults(query);
// 如果在等待期间又发起了新请求,丢弃当前结果
if (requestId !== currentRequestId) {
console.log('过时的结果,已丢弃');
return;
}
displayResults(results);
}
// 更好的方式:使用 AbortController
let currentController = null;
async function searchWithAbort(query) {
// 取消之前的请求
if (currentController) {
currentController.abort();
}
currentController = new AbortController();
try {
const response = await fetch(`/api/search?q=${query}`, {
signal: currentController.signal
});
const results = await response.json();
displayResults(results);
} catch (err) {
if (err.name === 'AbortError') {
console.log('请求已取消');
} else {
throw err;
}
}
}
13.4 async/await 与类
javascript
class DataService {
#baseUrl;
#cache = new Map();
constructor(baseUrl) {
this.#baseUrl = baseUrl;
}
// 异步方法
async get(endpoint) {
const url = `${this.#baseUrl}${endpoint}`;
if (this.#cache.has(url)) {
return this.#cache.get(url);
}
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
this.#cache.set(url, data);
return data;
}
async post(endpoint, body) {
const response = await fetch(`${this.#baseUrl}${endpoint}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
return response.json();
}
// 静态异步方法
static async create(baseUrl) {
const service = new DataService(baseUrl);
// 可以在工厂方法中做异步初始化
await service.get('/health'); // 检查服务是否可用
return service;
}
}
// 使用(注意:constructor 不能是 async 的)
async function main() {
const api = await DataService.create('https://api.example.com');
const users = await api.get('/users');
console.log(users);
}
第五部分:高级异步模式
14. Generator 与异步迭代
14.1 Generator 基础
javascript
// Generator 函数:可以暂停和恢复的函数
function* numberGenerator() {
console.log("开始");
yield 1; // 暂停,返回 1
console.log("继续");
yield 2; // 暂停,返回 2
console.log("再继续");
return 3; // 结束
}
const gen = numberGenerator(); // 不会立即执行!
console.log(gen.next()); // "开始" → { value: 1, done: false }
console.log(gen.next()); // "继续" → { value: 2, done: false }
console.log(gen.next()); // "再继续" → { value: 3, done: true }
console.log(gen.next()); // → { value: undefined, done: true }
// yield 可以接收外部传入的值
function* conversation() {
const name = yield "你叫什么名字?";
const age = yield `${name},你多大了?`;
return `${name} 今年 ${age} 岁`;
}
const chat = conversation();
console.log(chat.next()); // { value: "你叫什么名字?", done: false }
console.log(chat.next("Alice")); // { value: "Alice,你多大了?", done: false }
console.log(chat.next(25)); // { value: "Alice 今年 25 岁", done: true }
14.2 Generator 实现异步流程控制
javascript
// Generator + Promise = async/await 的前身
function* fetchUserFlow() {
try {
const user = yield getUserInfo(1); // yield 一个 Promise
console.log('用户:', user.name);
const orders = yield getOrders(user.id); // yield 另一个 Promise
console.log('订单数:', orders.length);
return { user, orders };
} catch (err) {
console.error('出错:', err.message);
}
}
// 自动执行器(co 库的简化版)
function run(generatorFn) {
return new Promise((resolve, reject) => {
const gen = generatorFn();
function step(nextFn) {
let result;
try {
result = nextFn();
} catch (err) {
return reject(err);
}
if (result.done) {
return resolve(result.value);
}
// 假设 yield 的都是 Promise
Promise.resolve(result.value).then(
value => step(() => gen.next(value)), // 将结果送回 generator
error => step(() => gen.throw(error)) // 将错误送回 generator
);
}
step(() => gen.next());
});
}
// 使用
run(fetchUserFlow).then(result => {
console.log('最终结果:', result);
});
// 对比 async/await(完全等价!)
async function fetchUserAsync() {
try {
const user = await getUserInfo(1);
console.log('用户:', user.name);
const orders = await getOrders(user.id);
console.log('订单数:', orders.length);
return { user, orders };
} catch (err) {
console.error('出错:', err.message);
}
}
// async/await 本质上就是 Generator + 自动执行器的语法糖!
14.3 异步迭代器(Async Iterator)
javascript
// Symbol.asyncIterator 和 for await...of
// 创建异步可迭代对象
const asyncIterable = {
[Symbol.asyncIterator]() {
let i = 0;
return {
async next() {
if (i >= 3) {
return { value: undefined, done: true };
}
// 模拟异步操作
await new Promise(resolve => setTimeout(resolve, 1000));
return { value: i++, done: false };
}
};
}
};
// 使用 for await...of
async function consume() {
for await (const value of asyncIterable) {
console.log(value); // 每隔1秒: 0, 1, 2
}
}
// 异步生成器(更简洁的写法)
async function* asyncRange(start, end) {
for (let i = start; i <= end; i++) {
// 模拟每个值需要异步获取
await new Promise(resolve => setTimeout(resolve, 500));
yield i;
}
}
async function main() {
for await (const num of asyncRange(1, 5)) {
console.log(num); // 每隔500ms: 1, 2, 3, 4, 5
}
}
// 实际应用:分页获取数据
async function* fetchPages(baseUrl) {
let page = 1;
let hasMore = true;
while (hasMore) {
const response = await fetch(`${baseUrl}?page=${page}&limit=20`);
const data = await response.json();
yield data.items;
hasMore = data.hasMore;
page++;
}
}
// 使用
async function getAllItems() {
const allItems = [];
for await (const items of fetchPages('/api/products')) {
allItems.push(...items);
console.log(`已获取 ${allItems.length} 个商品`);
}
return allItems;
}
// 实际应用:读取大文件流(Node.js)
const fs = require('fs');
async function processLargeFile(filePath) {
const stream = fs.createReadStream(filePath, { encoding: 'utf8' });
let lineCount = 0;
for await (const chunk of stream) {
lineCount += chunk.split('\n').length;
}
console.log(`文件共 ${lineCount} 行`);
}
15. 并发控制
15.1 并发限制器
javascript
// 实现一个通用的并发限制器
class ConcurrencyLimiter {
#maxConcurrency;
#running = 0;
#queue = [];
constructor(maxConcurrency) {
this.#maxConcurrency = maxConcurrency;
}
async run(fn) {
// 如果达到并发上限,排队等待
if (this.#running >= this.#maxConcurrency) {
await new Promise(resolve => this.#queue.push(resolve));
}
this.#running++;
try {
return await fn();
} finally {
this.#running--;
// 释放一个排队的任务
if (this.#queue.length > 0) {
const next = this.#queue.shift();
next();
}
}
}
get running() { return this.#running; }
get pending() { return this.#queue.length; }
}
// 使用
async function downloadFiles(urls) {
const limiter = new ConcurrencyLimiter(3); // 最多3个并行
const results = await Promise.all(
urls.map(url =>
limiter.run(async () => {
console.log(`开始下载: ${url} (并行: ${limiter.running})`);
const response = await fetch(url);
const data = await response.json();
console.log(`完成下载: ${url}`);
return data;
})
)
);
return results;
}
15.2 Promise 池
javascript
// 更精细的并发控制:Promise 池
async function promisePool(tasks, poolSize) {
const results = [];
const executing = new Set();
for (const [index, task] of tasks.entries()) {
// 创建 Promise 并开始执行
const promise = Promise.resolve().then(() => task()).then(result => {
results[index] = { status: 'fulfilled', value: result };
}).catch(error => {
results[index] = { status: 'rejected', reason: error };
});
executing.add(promise);
// Promise 完成后从执行集合中移除
const clean = promise.then(() => executing.delete(promise));
// 达到池大小限制时,等待一个完成
if (executing.size >= poolSize) {
await Promise.race(executing);
}
}
// 等待剩余的任务完成
await Promise.all(executing);
return results;
}
// 使用
const tasks = Array.from({ length: 20 }, (_, i) => {
return () => new Promise(resolve => {
const delay = Math.random() * 2000;
setTimeout(() => {
console.log(`任务 ${i} 完成 (耗时 ${delay.toFixed(0)}ms)`);
resolve(`result-${i}`);
}, delay);
});
});
const results = await promisePool(tasks, 5);
console.log('所有结果:', results);
15.3 带进度的批量处理
javascript
async function batchProcess(items, processor, options = {}) {
const {
concurrency = 5,
onProgress = () => {},
onItemComplete = () => {},
onItemError = () => {},
} = options;
const limiter = new ConcurrencyLimiter(concurrency);
const total = items.length;
let completed = 0;
let failed = 0;
const results = [];
const promises = items.map((item, index) =>
limiter.run(async () => {
try {
const result = await processor(item, index);
results[index] = { success: true, data: result };
onItemComplete(item, result, index);
} catch (error) {
results[index] = { success: false, error };
failed++;
onItemError(item, error, index);
} finally {
completed++;
onProgress({
completed,
failed,
total,
percent: ((completed / total) * 100).toFixed(1)
});
}
})
);
await Promise.all(promises);
return {
results,
summary: { total, completed, failed, success: completed - failed }
};
}
// 使用:批量上传图片
const images = ['img1.jpg', 'img2.jpg', /* ... */ 'img100.jpg'];
const report = await batchProcess(
images,
async (image, index) => {
const formData = new FormData();
formData.append('file', image);
const response = await fetch('/api/upload', { method: 'POST', body: formData });
if (!response.ok) throw new Error(`上传失败: ${response.status}`);
return response.json();
},
{
concurrency: 3,
onProgress: ({ completed, total, percent }) => {
console.log(`进度: ${completed}/${total} (${percent}%)`);
updateProgressBar(percent);
},
onItemError: (image, error) => {
console.warn(`${image} 上传失败:`, error.message);
}
}
);
console.log(`上传完成: ${report.summary.success} 成功, ${report.summary.failed} 失败`);
16. 发布/订阅与事件驱动
16.1 EventEmitter 实现
javascript
class AsyncEventEmitter {
#listeners = new Map();
on(event, listener) {
if (!this.#listeners.has(event)) {
this.#listeners.set(event, []);
}
this.#listeners.get(event).push(listener);
return this; // 链式调用
}
off(event, listener) {
const listeners = this.#listeners.get(event);
if (listeners) {
const index = listeners.indexOf(listener);
if (index > -1) listeners.splice(index, 1);
}
return this;
}
once(event, listener) {
const wrapper = async (...args) => {
this.off(event, wrapper);
return listener(...args);
};
return this.on(event, wrapper);
}
// 异步 emit:等待所有监听器执行完毕
async emit(event, ...args) {
const listeners = this.#listeners.get(event) || [];
const results = [];
for (const listener of [...listeners]) {
results.push(await listener(...args));
}
return results;
}
// 并行 emit
async emitParallel(event, ...args) {
const listeners = this.#listeners.get(event) || [];
return Promise.all(listeners.map(fn => fn(...args)));
}
// 等待某个事件触发(转为 Promise)
waitFor(event, timeout = 0) {
return new Promise((resolve, reject) => {
let timer;
if (timeout > 0) {
timer = setTimeout(() => {
this.off(event, handler);
reject(new Error(`等待 "${event}" 事件超时 (${timeout}ms)`));
}, timeout);
}
const handler = (data) => {
clearTimeout(timer);
resolve(data);
};
this.once(event, handler);
});
}
}
// 使用
const bus = new AsyncEventEmitter();
// 注册异步监听器
bus.on('order:created', async (order) => {
console.log('发送确认邮件...');
await sendEmail(order.userId, '订单已创建');
});
bus.on('order:created', async (order) => {
console.log('更新库存...');
await updateInventory(order.items);
});
// 触发事件
await bus.emit('order:created', { id: 1, userId: 'u1', items: [...] });
console.log('所有后续处理完成');
// 等待事件
const userData = await bus.waitFor('user:login', 30000);
console.log('用户登录了:', userData);
16.2 异步队列
javascript
class AsyncQueue {
#queue = [];
#processing = false;
#concurrency;
#running = 0;
constructor(concurrency = 1) {
this.#concurrency = concurrency;
}
enqueue(task) {
return new Promise((resolve, reject) => {
this.#queue.push({ task, resolve, reject });
this.#process();
});
}
async #process() {
if (this.#running >= this.#concurrency || this.#queue.length === 0) {
return;
}
const { task, resolve, reject } = this.#queue.shift();
this.#running++;
try {
const result = await task();
resolve(result);
} catch (error) {
reject(error);
} finally {
this.#running--;
this.#process(); // 处理下一个
}
}
get size() { return this.#queue.length; }
get pending() { return this.#running; }
// 等待所有任务完成
async drain() {
if (this.#queue.length === 0 && this.#running === 0) return;
return new Promise(resolve => {
const check = () => {
if (this.#queue.length === 0 && this.#running === 0) {
resolve();
} else {
setTimeout(check, 50);
}
};
check();
});
}
}
// 使用:任务队列
const queue = new AsyncQueue(2); // 并发度 2
// 添加任务
for (let i = 0; i < 10; i++) {
queue.enqueue(async () => {
console.log(`开始任务 ${i}`);
await new Promise(r => setTimeout(r, 1000));
console.log(`完成任务 ${i}`);
return `result-${i}`;
}).then(result => {
console.log(`任务结果: ${result}`);
});
}
// 等待所有完成
await queue.drain();
console.log('所有任务已完成');
17. 可取消的异步操作
17.1 AbortController
javascript
// AbortController 是 Web API,用于取消异步操作
// 基本用法
const controller = new AbortController();
const { signal } = controller;
// 1. 取消 fetch 请求
fetch('/api/large-data', { signal })
.then(response => response.json())
.then(data => console.log(data))
.catch(err => {
if (err.name === 'AbortError') {
console.log('请求已取消');
} else {
console.error('请求失败:', err);
}
});
// 5秒后取消
setTimeout(() => controller.abort(), 5000);
// 2. 取消多个操作
const controller2 = new AbortController();
await Promise.all([
fetch('/api/data1', { signal: controller2.signal }),
fetch('/api/data2', { signal: controller2.signal }),
fetch('/api/data3', { signal: controller2.signal }),
]);
// controller2.abort() 会同时取消所有三个请求
// 3. 监听取消信号
signal.addEventListener('abort', () => {
console.log('收到取消信号');
console.log('取消原因:', signal.reason);
});
// 带原因的取消
controller.abort(new Error('用户取消了操作'));
17.2 自定义可取消操作
javascript
// 让任何异步操作都可以取消
function cancellable(asyncFn) {
const controller = new AbortController();
const promise = new Promise(async (resolve, reject) => {
// 监听取消
controller.signal.addEventListener('abort', () => {
reject(new DOMException('Operation cancelled', 'AbortError'));
});
try {
const result = await asyncFn(controller.signal);
resolve(result);
} catch (err) {
reject(err);
}
});
return {
promise,
cancel: (reason) => controller.abort(reason)
};
}
// 使用
const { promise, cancel } = cancellable(async (signal) => {
const response = await fetch('/api/data', { signal });
return response.json();
});
// 2秒后取消
setTimeout(cancel, 2000);
try {
const data = await promise;
console.log(data);
} catch (err) {
if (err.name === 'AbortError') {
console.log('已取消');
}
}
// 可取消的延迟
function delay(ms, signal) {
return new Promise((resolve, reject) => {
const timer = setTimeout(resolve, ms);
signal?.addEventListener('abort', () => {
clearTimeout(timer);
reject(new DOMException('Delay cancelled', 'AbortError'));
});
});
}
// 可取消的重试
async function fetchWithCancelableRetry(url, { signal, maxRetries = 3 } = {}) {
for (let i = 0; i <= maxRetries; i++) {
signal?.throwIfAborted(); // 检查是否已取消
try {
return await fetch(url, { signal });
} catch (err) {
if (err.name === 'AbortError') throw err; // 取消不重试
if (i === maxRetries) throw err;
await delay(1000 * Math.pow(2, i), signal);
}
}
}
18. 响应式编程简介(Observable)
javascript
// 简单的 Observable 实现
class Observable {
constructor(subscribe) {
this._subscribe = subscribe;
}
subscribe(observer) {
// 标准化 observer
const normalizedObserver = typeof observer === 'function'
? { next: observer, error: () => {}, complete: () => {} }
: { next: () => {}, error: () => {}, complete: () => {}, ...observer };
const subscription = this._subscribe(normalizedObserver);
return {
unsubscribe: () => {
if (subscription?.unsubscribe) subscription.unsubscribe();
}
};
}
// 操作符
map(fn) {
return new Observable(observer => {
return this.subscribe({
next: value => observer.next(fn(value)),
error: err => observer.error(err),
complete: () => observer.complete()
});
});
}
filter(predicate) {
return new Observable(observer => {
return this.subscribe({
next: value => predicate(value) && observer.next(value),
error: err => observer.error(err),
complete: () => observer.complete()
});
});
}
// 从各种来源创建 Observable
static fromEvent(element, eventName) {
return new Observable(observer => {
const handler = event => observer.next(event);
element.addEventListener(eventName, handler);
return {
unsubscribe: () => element.removeEventListener(eventName, handler)
};
});
}
static fromPromise(promise) {
return new Observable(observer => {
promise
.then(value => {
observer.next(value);
observer.complete();
})
.catch(err => observer.error(err));
});
}
static interval(ms) {
return new Observable(observer => {
let i = 0;
const id = setInterval(() => observer.next(i++), ms);
return { unsubscribe: () => clearInterval(id) };
});
}
}
// 使用示例:搜索框防抖
const searchInput = document.getElementById('search');
const subscription = Observable.fromEvent(searchInput, 'input')
.map(e => e.target.value)
.filter(text => text.length >= 2)
.subscribe({
next: async (query) => {
const results = await fetch(`/api/search?q=${query}`).then(r => r.json());
displayResults(results);
}
});
// 取消订阅
// subscription.unsubscribe();
第六部分:实战与最佳实践
19. 真实项目场景
19.1 完整的 API 客户端
javascript
class APIClient {
#baseUrl;
#defaultHeaders;
#interceptors = { request: [], response: [] };
#timeout;
constructor(config = {}) {
this.#baseUrl = config.baseUrl || '';
this.#defaultHeaders = config.headers || {};
this.#timeout = config.timeout || 30000;
}
// 拦截器
addRequestInterceptor(fn) {
this.#interceptors.request.push(fn);
return this;
}
addResponseInterceptor(fn) {
this.#interceptors.response.push(fn);
return this;
}
async #request(method, endpoint, options = {}) {
let config = {
method,
url: `${this.#baseUrl}${endpoint}`,
headers: { ...this.#defaultHeaders, ...options.headers },
body: options.body,
params: options.params,
timeout: options.timeout || this.#timeout,
signal: options.signal,
};
// 执行请求拦截器
for (const interceptor of this.#interceptors.request) {
config = await interceptor(config);
}
// 构建 URL(处理查询参数)
const url = new URL(config.url);
if (config.params) {
Object.entries(config.params).forEach(([key, value]) => {
url.searchParams.append(key, value);
});
}
// 超时控制
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), config.timeout);
// 合并 signal
const signal = config.signal
? anySignal([config.signal, controller.signal])
: controller.signal;
try {
const fetchOptions = {
method: config.method,
headers: config.headers,
signal,
};
if (config.body && method !== 'GET') {
fetchOptions.body = JSON.stringify(config.body);
fetchOptions.headers['Content-Type'] = 'application/json';
}
let response = await fetch(url.toString(), fetchOptions);
// 执行响应拦截器
for (const interceptor of this.#interceptors.response) {
response = await interceptor(response);
}
if (!response.ok) {
const error = new Error(`HTTP ${response.status}: ${response.statusText}`);
error.status = response.status;
error.response = response;
throw error;
}
const contentType = response.headers.get('content-type');
if (contentType?.includes('application/json')) {
return await response.json();
}
return await response.text();
} finally {
clearTimeout(timeoutId);
}
}
get(endpoint, options) { return this.#request('GET', endpoint, options); }
post(endpoint, body, options) { return this.#request('POST', endpoint, { ...options, body }); }
put(endpoint, body, options) { return this.#request('PUT', endpoint, { ...options, body }); }
patch(endpoint, body, options) { return this.#request('PATCH', endpoint, { ...options, body }); }
delete(endpoint, options) { return this.#request('DELETE', endpoint, options); }
}
// 使用
const api = new APIClient({
baseUrl: 'https://api.example.com',
headers: {
'Accept': 'application/json',
},
timeout: 10000,
});
// 添加认证拦截器
api.addRequestInterceptor(async (config) => {
const token = await getAuthToken();
config.headers['Authorization'] = `Bearer ${token}`;
return config;
});
// 添加日志拦截器
api.addResponseInterceptor(async (response) => {
console.log(`${response.url} → ${response.status}`);
return response;
});
// 调用
try {
const users = await api.get('/users', { params: { page: 1, limit: 20 } });
const newUser = await api.post('/users', { name: 'Alice', email: 'alice@example.com' });
} catch (err) {
if (err.status === 401) {
// 跳转登录
}
}
19.2 缓存与去重
javascript
// 请求去重 + 缓存
class RequestCache {
#cache = new Map(); // 结果缓存
#pending = new Map(); // 进行中的请求(去重)
#ttl;
constructor(ttl = 60000) { // 默认缓存1分钟
this.#ttl = ttl;
}
async get(key, fetcher) {
// 1. 检查缓存
const cached = this.#cache.get(key);
if (cached && Date.now() - cached.timestamp < this.#ttl) {
console.log(`[Cache HIT] ${key}`);
return cached.data;
}
// 2. 检查是否有相同的请求正在进行(去重)
if (this.#pending.has(key)) {
console.log(`[Cache DEDUP] ${key}`);
return this.#pending.get(key);
}
// 3. 发起新请求
console.log(`[Cache MISS] ${key}`);
const promise = fetcher().then(data => {
// 成功后缓存结果
this.#cache.set(key, { data, timestamp: Date.now() });
this.#pending.delete(key);
return data;
}).catch(err => {
this.#pending.delete(key);
throw err;
});
this.#pending.set(key, promise);
return promise;
}
invalidate(key) {
this.#cache.delete(key);
}
clear() {
this.#cache.clear();
}
}
// 使用
const cache = new RequestCache(30000); // 30秒缓存
async function getUser(id) {
return cache.get(`user:${id}`, () =>
fetch(`/api/users/${id}`).then(r => r.json())
);
}
// 即使同时调用多次,也只会发一个请求
const [user1, user2, user3] = await Promise.all([
getUser(1), // 发起请求
getUser(1), // 复用同一个请求(去重)
getUser(1), // 复用同一个请求(去重)
]);
// 后续调用使用缓存
const user4 = await getUser(1); // Cache HIT
19.3 WebSocket 封装
javascript
class ReconnectableWebSocket {
#url;
#ws = null;
#options;
#reconnectAttempts = 0;
#listeners = new Map();
#messageQueue = [];
#isConnected = false;
constructor(url, options = {}) {
this.#url = url;
this.#options = {
maxReconnectAttempts: 10,
reconnectInterval: 1000,
maxReconnectInterval: 30000,
...options
};
this.#connect();
}
#connect() {
this.#ws = new WebSocket(this.#url);
this.#ws.onopen = () => {
console.log('[WS] 连接成功');
this.#isConnected = true;
this.#reconnectAttempts = 0;
// 发送队列中的消息
while (this.#messageQueue.length > 0) {
const msg = this.#messageQueue.shift();
this.#ws.send(msg);
}
this.#emit('open');
};
this.#ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
this.#emit('message', data);
// 支持按类型分发
if (data.type) {
this.#emit(`message:${data.type}`, data.payload);
}
} catch {
this.#emit('message', event.data);
}
};
this.#ws.onclose = (event) => {
this.#isConnected = false;
console.log(`[WS] 连接关闭: ${event.code}`);
this.#emit('close', event);
if (event.code !== 1000) { // 非正常关闭
this.#reconnect();
}
};
this.#ws.onerror = (error) => {
console.error('[WS] 错误:', error);
this.#emit('error', error);
};
}
#reconnect() {
if (this.#reconnectAttempts >= this.#options.maxReconnectAttempts) {
console.error('[WS] 达到最大重连次数');
this.#emit('maxReconnectAttemptsReached');
return;
}
const delay = Math.min(
this.#options.reconnectInterval * Math.pow(2, this.#reconnectAttempts),
this.#options.maxReconnectInterval
);
console.log(`[WS] ${delay}ms 后重连 (第 ${this.#reconnectAttempts + 1} 次)`);
setTimeout(() => {
this.#reconnectAttempts++;
this.#connect();
}, delay);
}
send(data) {
const message = typeof data === 'string' ? data : JSON.stringify(data);
if (this.#isConnected) {
this.#ws.send(message);
} else {
this.#messageQueue.push(message); // 离线时先队列中
}
}
// 发送请求并等待响应
request(type, payload, timeout = 5000) {
return new Promise((resolve, reject) => {
const requestId = Math.random().toString(36).slice(2);
const timer = setTimeout(() => {
this.off(`message:${type}:${requestId}`, handler);
reject(new Error(`WebSocket 请求超时: ${type}`));
}, timeout);
const handler = (response) => {
clearTimeout(timer);
resolve(response);
};
this.once(`message:${type}:${requestId}`, handler);
this.send({ type, payload, requestId });
});
}
on(event, handler) {
if (!this.#listeners.has(event)) {
this.#listeners.set(event, new Set());
}
this.#listeners.get(event).add(handler);
return this;
}
off(event, handler) {
this.#listeners.get(event)?.delete(handler);
return this;
}
once(event, handler) {
const wrapper = (...args) => {
this.off(event, wrapper);
handler(...args);
};
return this.on(event, wrapper);
}
#emit(event, ...args) {
this.#listeners.get(event)?.forEach(handler => handler(...args));
}
close() {
this.#options.maxReconnectAttempts = 0;
this.#ws?.close(1000, 'Client closed');
}
}
// 使用
const ws = new ReconnectableWebSocket('wss://api.example.com/ws');
ws.on('open', () => console.log('已连接'));
ws.on('message:chat', (msg) => console.log('收到消息:', msg));
ws.on('message:notification', (notif) => showNotification(notif));
ws.send({ type: 'join', payload: { room: 'general' } });
// 请求-响应模式
const userList = await ws.request('getUserList', { room: 'general' });
20. 性能优化
20.1 防抖与节流
javascript
// 防抖(Debounce):等用户停止操作后再执行
function debounce(fn, delay, options = {}) {
const { leading = false, trailing = true } = options;
let timer = null;
let lastArgs = null;
function debounced(...args) {
lastArgs = args;
const callNow = leading && !timer;
clearTimeout(timer);
timer = setTimeout(() => {
timer = null;
if (trailing && lastArgs) {
fn(...lastArgs);
lastArgs = null;
}
}, delay);
if (callNow) {
fn(...args);
}
}
debounced.cancel = () => {
clearTimeout(timer);
timer = null;
lastArgs = null;
};
// 返回 Promise 版本
debounced.promise = (...args) => {
return new Promise((resolve) => {
debounced((...result) => resolve(fn(...result)));
});
};
return debounced;
}
// 异步防抖搜索
const debouncedSearch = debounce(async (query) => {
const results = await fetch(`/api/search?q=${query}`).then(r => r.json());
displayResults(results);
}, 300);
searchInput.addEventListener('input', (e) => {
debouncedSearch(e.target.value);
});
// 节流(Throttle):限制执行频率
function throttle(fn, interval) {
let lastTime = 0;
let timer = null;
return function(...args) {
const now = Date.now();
const remaining = interval - (now - lastTime);
if (remaining <= 0) {
clearTimeout(timer);
timer = null;
lastTime = now;
fn(...args);
} else if (!timer) {
timer = setTimeout(() => {
lastTime = Date.now();
timer = null;
fn(...args);
}, remaining);
}
};
}
// 滚动事件节流
window.addEventListener('scroll', throttle(async () => {
if (isNearBottom()) {
await loadMoreItems();
}
}, 200));
20.2 懒加载与预加载
javascript
// 懒加载模式
class LazyLoader {
#loaders = new Map();
#cache = new Map();
register(key, loader) {
this.#loaders.set(key, loader);
}
async get(key) {
// 已缓存
if (this.#cache.has(key)) {
return this.#cache.get(key);
}
const loader = this.#loaders.get(key);
if (!loader) throw new Error(`Unknown resource: ${key}`);
const value = await loader();
this.#cache.set(key, value);
return value;
}
// 预加载(后台提前加载)
preload(...keys) {
return Promise.allSettled(
keys.map(key => this.get(key))
);
}
}
// 使用
const resources = new LazyLoader();
resources.register('heavyModule', () => import('./heavy-module.js'));
resources.register('userProfile', () => fetch('/api/profile').then(r => r.json()));
resources.register('config', () => fetch('/api/config').then(r => r.json()));
// 只有在需要时才加载
const profile = await resources.get('userProfile');
// 路由跳转前预加载下一页的资源
router.beforeEach((to) => {
if (to.name === 'dashboard') {
resources.preload('config', 'userProfile');
}
});
// 图片懒加载
function lazyLoadImages() {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.classList.remove('lazy');
observer.unobserve(img);
}
});
}, { rootMargin: '200px' }); // 提前200px开始加载
document.querySelectorAll('img.lazy').forEach(img => {
observer.observe(img);
});
}
20.3 Web Worker 异步计算
javascript
// main.js --- 将耗时计算放到 Worker 线程
class WorkerPool {
#workers = [];
#queue = [];
#maxWorkers;
constructor(workerScript, maxWorkers = navigator.hardwareConcurrency || 4) {
this.#maxWorkers = maxWorkers;
for (let i = 0; i < maxWorkers; i++) {
this.#workers.push({
worker: new Worker(workerScript),
busy: false
});
}
}
execute(data) {
return new Promise((resolve, reject) => {
const task = { data, resolve, reject };
const freeWorker = this.#workers.find(w => !w.busy);
if (freeWorker) {
this.#runTask(freeWorker, task);
} else {
this.#queue.push(task);
}
});
}
#runTask(workerInfo, task) {
workerInfo.busy = true;
const handleMessage = (e) => {
workerInfo.worker.removeEventListener('message', handleMessage);
workerInfo.worker.removeEventListener('error', handleError);
workerInfo.busy = false;
task.resolve(e.data);
// 处理队列中的下一个任务
if (this.#queue.length > 0) {
const nextTask = this.#queue.shift();
this.#runTask(workerInfo, nextTask);
}
};
const handleError = (e) => {
workerInfo.worker.removeEventListener('message', handleMessage);
workerInfo.worker.removeEventListener('error', handleError);
workerInfo.busy = false;
task.reject(e.error || new Error(e.message));
};
workerInfo.worker.addEventListener('message', handleMessage);
workerInfo.worker.addEventListener('error', handleError);
workerInfo.worker.postMessage(task.data);
}
terminate() {
this.#workers.forEach(w => w.worker.terminate());
}
}
// worker.js
// self.onmessage = function(e) {
// const { type, payload } = e.data;
//
// switch (type) {
// case 'heavyComputation':
// const result = performHeavyWork(payload);
// self.postMessage(result);
// break;
// }
// };
// 使用
const pool = new WorkerPool('worker.js', 4);
const results = await Promise.all([
pool.execute({ type: 'heavyComputation', payload: data1 }),
pool.execute({ type: 'heavyComputation', payload: data2 }),
pool.execute({ type: 'heavyComputation', payload: data3 }),
]);
21. 常见陷阱与调试
21.1 常见陷阱
javascript
// 陷阱1:忘记 await
async function trap1() {
const promise = fetchData(); // ❌ 忘记 await
console.log(promise); // Promise {<pending>},不是数据!
const data = await fetchData(); // ✅
console.log(data); // 实际数据
}
// 陷阱2:forEach 中使用 async/await
async function trap2() {
const ids = [1, 2, 3];
// ❌ forEach 不会等待 async 回调
ids.forEach(async (id) => {
const data = await fetchData(id);
console.log(data);
});
console.log("完成"); // 在所有 fetchData 之前执行!
// ✅ 使用 for...of
for (const id of ids) {
const data = await fetchData(id);
console.log(data);
}
console.log("完成"); // 在所有 fetchData 之后执行
// ✅ 或 Promise.all + map(并行)
await Promise.all(ids.map(async (id) => {
const data = await fetchData(id);
console.log(data);
}));
console.log("完成");
}
// 陷阱3:async 函数中的返回值
async function trap3() {
// ❌ 在 try/catch 的 catch 中 return 但忘记前面的逻辑可能已执行
try {
const data = await riskyOperation();
updateUI(data);
return data;
} catch (err) {
return null; // 但 updateUI 可能已经部分执行了!
}
}
// 陷阱4:Promise 构造函数中的异步操作
// ❌ 在 Promise 构造函数中使用 async
const badPromise = new Promise(async (resolve, reject) => {
try {
const data = await fetchData();
resolve(data);
} catch (err) {
// 如果这里抛出错误,不会被外部 catch 到!
reject(err);
}
});
// ✅ 直接使用 async 函数
async function goodApproach() {
return await fetchData();
}
// 陷阱5:竞态条件
let currentData = null;
async function trap5(query) {
// ❌ 快速调用可能导致旧请求覆盖新请求
const data = await search(query);
currentData = data; // 如果之前的请求比后面的慢,会覆盖新数据
}
// ✅ 使用请求 ID 或 AbortController
let requestCounter = 0;
async function safeSearch(query) {
const myRequestId = ++requestCounter;
const data = await search(query);
if (myRequestId === requestCounter) {
currentData = data; // 只使用最新请求的结果
}
}
// 陷阱6:内存泄漏
class trap6Component {
constructor() {
this.controller = new AbortController();
}
async loadData() {
try {
const data = await fetch('/api/data', {
signal: this.controller.signal
});
this.render(data);
} catch (err) {
if (err.name !== 'AbortError') throw err;
}
}
// ✅ 组件销毁时取消未完成的请求
destroy() {
this.controller.abort();
}
}
// 陷阱7:错误吞噬
async function trap7() {
// ❌ catch 后不重新抛出,调用者不知道出错了
try {
await riskyOperation();
} catch (err) {
console.error(err); // 只是打印,没有抛出
}
// 调用者以为一切正常...
// ✅ 要么重新抛出,要么返回明确的错误标志
try {
await riskyOperation();
} catch (err) {
console.error(err);
throw err; // 重新抛出让调用者知道
}
}
21.2 调试技巧
javascript
// 1. 使用 async stack traces
// Chrome DevTools → Settings → Enable async stack traces
// 2. 给 Promise 打标签
function labeledFetch(label, url) {
const promise = fetch(url).then(r => r.json());
promise.label = label; // 调试用
return promise;
}
// 3. 日志包装器
function traced(fn, name) {
return async function(...args) {
const id = Math.random().toString(36).slice(2, 8);
console.log(`[${name}:${id}] 开始`, args);
const start = performance.now();
try {
const result = await fn.apply(this, args);
const duration = (performance.now() - start).toFixed(2);
console.log(`[${name}:${id}] 完成 (${duration}ms)`, result);
return result;
} catch (err) {
const duration = (performance.now() - start).toFixed(2);
console.error(`[${name}:${id}] 失败 (${duration}ms)`, err);
throw err;
}
};
}
const tracedFetch = traced(
(url) => fetch(url).then(r => r.json()),
'API'
);
await tracedFetch('/api/users');
// [API:k3m2n1] 开始 ["/api/users"]
// [API:k3m2n1] 完成 (234.56ms) [{...}, {...}]
// 4. Promise 状态检查
async function inspectPromise(promise) {
const unique = Symbol();
const result = await Promise.race([promise, Promise.resolve(unique)]);
if (result === unique) {
return 'pending';
}
return 'fulfilled';
}
// 5. 性能监测
class PerformanceTracker {
#marks = new Map();
start(label) {
this.#marks.set(label, performance.now());
}
end(label) {
const start = this.#marks.get(label);
if (!start) throw new Error(`No start mark for: ${label}`);
const duration = performance.now() - start;
this.#marks.delete(label);
console.log(`⏱️ ${label}: ${duration.toFixed(2)}ms`);
return duration;
}
async measure(label, fn) {
this.start(label);
try {
return await fn();
} finally {
this.end(label);
}
}
}
const perf = new PerformanceTracker();
await perf.measure('加载用户数据', async () => {
return fetch('/api/users').then(r => r.json());
});
// ⏱️ 加载用户数据: 156.78ms
22. 完整知识图谱总结
javascript
JavaScript 异步编程知识图谱
│
├── 基础概念
│ ├── 单线程模型
│ ├── 事件循环 ★★★
│ │ ├── 调用栈 (Call Stack)
│ │ ├── 宏任务队列 (setTimeout, setInterval, I/O)
│ │ ├── 微任务队列 (Promise.then, queueMicrotask, MutationObserver)
│ │ └── 执行顺序:同步 → 微任务(全部) → 宏任务(一个) → 微任务(全部) → ...
│ └── Node.js 事件循环(6个阶段)
│
├── 回调函数
│ ├── 错误优先回调
│ ├── 回调地狱
│ └── 控制反转问题
│
├── Promise ★★★
│ ├── 三种状态:pending → fulfilled / rejected
│ ├── 链式调用(.then 返回新 Promise)
│ ├── 错误处理(.catch 错误传播)
│ ├── 静态方法
│ │ ├── Promise.all (全部成功)
│ │ ├── Promise.allSettled (全部完成)
│ │ ├── Promise.race (最快)
│ │ ├── Promise.any (第一个成功)
│ │ └── Promise.withResolvers (ES2024)
│ └── 手写 Promise(面试)
│
├── Async/Await ★★★
│ ├── async 函数返回 Promise
│ ├── await 暂停执行等待 Promise
│ ├── 错误处理(try/catch)
│ ├── 串行 vs 并行
│ └── 循环中的 await
│
├── 高级模式
│ ├── Generator + 自动执行器
│ ├── 异步迭代器 (for await...of)
│ ├── 并发控制(限流器、Promise池)
│ ├── AbortController(取消操作)
│ ├── 发布/订阅模式
│ └── Observable(响应式编程)
│
└── 实战技巧
├── 重试机制(指数退避)
├── 请求去重与缓存
├── 防抖与节流
├── 竞态条件处理
├── Web Worker
└── 常见陷阱
├── forEach 中的 async
├── 忘记 await
├── 未处理的 rejection
└── 内存泄漏
学习路线建议:
- 入门:理解同步/异步 → 事件循环 → 回调
- 基础:Promise 创建/消费 → 链式调用 → 错误处理
- 进阶:async/await → 并行/串行 → 静态方法
- 高级:并发控制 → 取消操作 → Generator/异步迭代
- 精通:手写 Promise → 架构设计 → 性能优化 → 响应式编程