异步编程最佳实践
掌握异步编程的最佳实践,写出更健壮、更高效的异步代码。
1. 一句话概括主题
异步编程最佳实践是一系列经验和技巧,帮助你写出更清晰、更健壮、更高效的异步代码。
2. 它是什么
就像做菜有”最佳实践”(比如先热锅再放油),写异步代码也有”最佳实践”:
- 错误处理:就像做菜要准备灭火器,写异步代码要处理错误
- 避免阻塞:就像做菜时不要一直盯着一个锅,要合理利用时间
- 代码清晰:就像做菜步骤要清晰,代码也要易读
这些”最佳实践”是很多开发者总结出来的经验,遵循它们可以让你:
- 代码更不容易出错
- 代码更容易维护
- 代码性能更好
简单理解:异步编程最佳实践 = 写异步代码的”经验总结”和”注意事项”。
3. 能解决什么问题 + 为什么重要
解决的问题
- 错误处理混乱:异步操作的错误容易被忽略或处理不当
- 性能问题:不当的异步处理导致程序变慢
- 代码难以维护:异步代码结构混乱,难以理解和修改
- 资源泄漏:异步操作没有正确清理,导致内存泄漏
为什么重要
- 提高代码质量:遵循最佳实践可以写出更健壮的代码
- 减少 Bug:避免常见的异步编程陷阱
- 提高性能:优化异步操作的执行方式
- 团队协作:统一的实践让团队代码更一致
4. 核心知识点拆解
4.1 错误处理最佳实践
总是处理错误
// ❌ 错误:没有错误处理
async function fetchData() {
const data = await fetch('/api/data');
return data.json();
}
// ✅ 正确:总是处理错误
async function fetchData() {
try {
const response = await fetch('/api/data');
if (!response.ok) {
throw new Error(`HTTP错误!状态码:${response.status}`);
}
return await response.json();
} catch (error) {
console.error('获取数据失败:', error);
// 根据错误类型处理
if (error.name === 'TypeError') {
// 网络错误
} else if (error.name === 'SyntaxError') {
// JSON 解析错误
}
throw error; // 重新抛出,让调用者处理
}
}使用统一的错误处理
// 创建统一的错误处理函数
function handleAsyncError(error, context) {
console.error(`[${context}] 错误:`, error);
// 可以在这里添加错误上报
// reportError(error, context);
// 根据错误类型返回用户友好的消息
if (error.message.includes('网络')) {
return '网络连接失败,请检查网络';
} else if (error.message.includes('超时')) {
return '请求超时,请稍后重试';
} else {
return '操作失败,请稍后重试';
}
}
// 使用
async function loadData() {
try {
const data = await fetchData();
return data;
} catch (error) {
const message = handleAsyncError(error, 'loadData');
alert(message);
throw error;
}
}4.2 并发控制最佳实践
并行执行独立操作
// ❌ 错误:串行执行,很慢
async function loadAllData() {
const users = await fetch('/api/users'); // 等 1 秒
const posts = await fetch('/api/posts'); // 再等 1 秒
const comments = await fetch('/api/comments'); // 再等 1 秒
// 总共 3 秒
}
// ✅ 正确:并行执行,很快
async function loadAllData() {
const [users, posts, comments] = await Promise.all([
fetch('/api/users'),
fetch('/api/posts'),
fetch('/api/comments')
]);
// 总共 1 秒(三个请求同时进行)
}控制并发数量
// 限制同时进行的请求数量
async function limitConcurrency(tasks, limit) {
const results = [];
for (let i = 0; i < tasks.length; i += limit) {
// 每次处理 limit 个任务
const batch = tasks.slice(i, i + limit);
const batchResults = await Promise.all(
batch.map(task => task())
);
results.push(...batchResults);
}
return results;
}
// 使用
const urls = ['/api/1', '/api/2', '/api/3', '/api/4', '/api/5'];
const tasks = urls.map(url => () => fetch(url));
// 每次最多 2 个并发请求
const results = await limitConcurrency(tasks, 2);4.3 超时处理
// 为异步操作添加超时
function withTimeout(promise, timeoutMs) {
const timeout = new Promise((_, reject) => {
setTimeout(() => {
reject(new Error('操作超时'));
}, timeoutMs);
});
return Promise.race([promise, timeout]);
}
// 使用
async function fetchWithTimeout(url) {
try {
const response = await withTimeout(
fetch(url),
5000 // 5 秒超时
);
return await response.json();
} catch (error) {
if (error.message === '操作超时') {
console.error('请求超时');
} else {
console.error('请求失败:', error);
}
throw error;
}
}4.4 取消异步操作
// 创建可取消的 Promise
function cancellablePromise(promise) {
let cancelled = false;
const wrappedPromise = new Promise((resolve, reject) => {
promise
.then(result => {
if (!cancelled) {
resolve(result);
}
})
.catch(error => {
if (!cancelled) {
reject(error);
}
});
});
return {
promise: wrappedPromise,
cancel: () => {
cancelled = true;
}
};
}
// 使用
const { promise, cancel } = cancellablePromise(
fetch('/api/data')
);
// 5 秒后取消
setTimeout(() => {
cancel();
console.log('请求已取消');
}, 5000);
promise
.then(data => console.log('数据:', data))
.catch(error => console.error('错误:', error));4.5 重试机制
// 带重试的异步函数
async function retry(fn, maxRetries = 3, delay = 1000) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (i === maxRetries - 1) {
throw error; // 最后一次重试失败,抛出错误
}
console.log(`第 ${i + 1} 次尝试失败,${delay}ms 后重试...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
// 使用
async function fetchWithRetry(url) {
return await retry(
() => fetch(url).then(r => r.json()),
3, // 最多重试 3 次
1000 // 每次重试间隔 1 秒
);
}4.6 避免内存泄漏
// ❌ 错误:没有清理定时器
function startPolling() {
setInterval(async () => {
const data = await fetch('/api/data');
updateUI(data);
}, 1000);
// 如果组件卸载,定时器还在运行
}
// ✅ 正确:保存引用,可以清理
function startPolling() {
const intervalId = setInterval(async () => {
const data = await fetch('/api/data');
updateUI(data);
}, 1000);
// 返回清理函数
return () => {
clearInterval(intervalId);
};
}
// 使用
const cleanup = startPolling();
// 组件卸载时清理
// cleanup();5. 示例代码(可运行 + 逐行注释)
示例 1:完整的错误处理
// 封装带完整错误处理的 API 请求
async function apiRequest(url, options = {}) {
try {
// 添加超时
const controller = new AbortController();
const timeoutId = setTimeout(() => {
controller.abort();
}, options.timeout || 5000);
// 发送请求
const response = await fetch(url, {
...options,
signal: controller.signal
});
// 清除超时定时器
clearTimeout(timeoutId);
// 检查响应状态
if (!response.ok) {
throw new Error(`HTTP错误!状态码:${response.status}`);
}
// 解析 JSON
const data = await response.json();
return data;
} catch (error) {
// 处理不同类型的错误
if (error.name === 'AbortError') {
throw new Error('请求超时');
} else if (error.name === 'TypeError') {
throw new Error('网络错误,请检查网络连接');
} else if (error.name === 'SyntaxError') {
throw new Error('服务器返回的数据格式错误');
} else {
throw error;
}
}
}
// 使用
async function loadUserData() {
try {
const user = await apiRequest('/api/user', { timeout: 3000 });
console.log('用户数据:', user);
return user;
} catch (error) {
console.error('加载失败:', error.message);
// 显示用户友好的错误提示
alert(error.message);
throw error;
}
}示例 2:并发控制
// 限制并发数量的请求函数
async function fetchWithLimit(urls, limit = 3) {
const results = [];
const executing = [];
for (const url of urls) {
// 创建 Promise
const promise = fetch(url)
.then(response => response.json())
.then(data => {
// 请求完成后,从执行队列中移除
executing.splice(executing.indexOf(promise), 1);
return data;
});
results.push(promise);
executing.push(promise);
// 如果达到并发限制,等待一个完成
if (executing.length >= limit) {
await Promise.race(executing);
}
}
// 等待所有请求完成
return Promise.all(results);
}
// 使用
const urls = [
'/api/user/1',
'/api/user/2',
'/api/user/3',
'/api/user/4',
'/api/user/5'
];
fetchWithLimit(urls, 2) // 每次最多 2 个并发
.then(results => {
console.log('所有请求完成:', results);
})
.catch(error => {
console.error('请求失败:', error);
});示例 3:带重试的请求
// 带重试和退避策略的请求函数
async function fetchWithRetry(url, options = {}) {
const {
maxRetries = 3,
initialDelay = 1000,
backoffFactor = 2
} = options;
let lastError;
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
lastError = error;
// 最后一次尝试失败,不再重试
if (attempt === maxRetries - 1) {
break;
}
// 计算延迟时间(指数退避)
const delay = initialDelay * Math.pow(backoffFactor, attempt);
console.log(`第 ${attempt + 1} 次尝试失败,${delay}ms 后重试...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw lastError;
}
// 使用
fetchWithRetry('/api/data', {
maxRetries: 3,
initialDelay: 1000,
backoffFactor: 2
})
.then(data => {
console.log('成功:', data);
})
.catch(error => {
console.error('最终失败:', error);
});6. 常见错误与踩坑
错误 1:忘记处理 Promise 拒绝
// ❌ 错误:Promise 被拒绝但没有处理
async function fetchData() {
const data = await fetch('/api/data');
return data.json();
}
fetchData(); // 如果失败,会有未处理的 Promise 拒绝警告
// ✅ 正确:总是处理错误
fetchData()
.then(data => console.log(data))
.catch(error => console.error(error));错误 2:在循环中使用 await
// ❌ 错误:串行执行,很慢
async function processItems(items) {
const results = [];
for (const item of items) {
const result = await processItem(item); // 一个一个等
results.push(result);
}
return results;
}
// ✅ 正确:并行执行
async function processItems(items) {
const results = await Promise.all(
items.map(item => processItem(item))
);
return results;
}错误 3:没有清理异步操作
// ❌ 错误:定时器没有清理
function startPolling() {
setInterval(() => {
fetchData();
}, 1000);
// 组件卸载后,定时器还在运行
}
// ✅ 正确:提供清理方法
function startPolling() {
const intervalId = setInterval(() => {
fetchData();
}, 1000);
return () => clearInterval(intervalId);
}错误 4:错误处理不当
// ❌ 错误:吞掉错误
async function fetchData() {
try {
const data = await fetch('/api/data');
return data.json();
} catch (error) {
// 错误被吞掉了,调用者不知道
console.error(error);
}
}
// ✅ 正确:重新抛出错误或返回错误状态
async function fetchData() {
try {
const data = await fetch('/api/data');
return { success: true, data: await data.json() };
} catch (error) {
console.error(error);
return { success: false, error: error.message };
}
}7. 实际应用场景
场景 1:API 请求封装
// 完整的 API 请求封装
class ApiClient {
constructor(baseURL, options = {}) {
this.baseURL = baseURL;
this.timeout = options.timeout || 5000;
this.retries = options.retries || 3;
}
async request(endpoint, options = {}) {
const url = `${this.baseURL}${endpoint}`;
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
const response = await fetch(url, {
...options,
signal: controller.signal
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
if (error.name === 'AbortError') {
throw new Error('请求超时');
}
throw error;
}
}
async requestWithRetry(endpoint, options = {}) {
let lastError;
for (let i = 0; i < this.retries; i++) {
try {
return await this.request(endpoint, options);
} catch (error) {
lastError = error;
if (i < this.retries - 1) {
await new Promise(r => setTimeout(r, 1000 * (i + 1)));
}
}
}
throw lastError;
}
}
// 使用
const api = new ApiClient('https://api.example.com', {
timeout: 5000,
retries: 3
});
api.requestWithRetry('/users')
.then(users => console.log(users))
.catch(error => console.error(error));场景 2:批量处理
// 批量处理数据,控制并发
async function batchProcess(items, processor, concurrency = 3) {
const results = [];
const executing = [];
for (const item of items) {
const promise = processor(item)
.then(result => {
executing.splice(executing.indexOf(promise), 1);
return result;
});
results.push(promise);
executing.push(promise);
if (executing.length >= concurrency) {
await Promise.race(executing);
}
}
return Promise.all(results);
}
// 使用
const items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
batchProcess(items, async (item) => {
// 处理每个项目
await new Promise(r => setTimeout(r, 1000));
return `处理完成:${item}`;
}, 3) // 每次处理 3 个
.then(results => {
console.log('全部完成:', results);
});8. 给新手的练习题(可立即实践)
基础题:添加错误处理
要求:
- 创建一个
fetchData函数 - 添加 try/catch 错误处理
- 根据错误类型显示不同的错误消息
进阶题:实现重试机制
要求:
- 创建一个带重试的请求函数
- 最多重试 3 次
- 每次重试间隔递增(1秒、2秒、3秒)
9. 用更简单的话再总结一遍(方便复习)
异步编程最佳实践是什么:
- 就是写异步代码的”经验总结”
- 让你写出更健壮、更高效的代码
核心要点:
- 总是处理错误,不要忽略
- 独立操作要并行执行,不要串行
- 添加超时,避免无限等待
- 清理资源,避免内存泄漏
- 添加重试,提高成功率
记住:
- 错误处理很重要
- 合理使用并发
- 及时清理资源
10. 知识体系延伸 & 继续学习方向
相关知识点
继续学习方向
- 性能优化:学习如何优化异步代码的性能
- 实际项目:在真实项目中应用这些最佳实践
- 高级模式:学习更高级的异步编程模式
- 工具使用:学习使用工具来帮助异步编程
最后更新:2025