JavaScript异步编程模式详解
JavaScript是单线程语言,但通过异步编程,我们可以处理耗时操作而不阻塞主线程。让我们深入了解JavaScript异步编程的演进历程。
回调函数(Callbacks)
最初的异步解决方案是回调函数:
function fetchData(callback) {
setTimeout(() => {
const data = { id: 1, name: '用户数据' };
callback(null, data);
}, 1000);
}
fetchData((error, data) => {
if (error) {
console.error('获取数据失败:', error);
} else {
console.log('数据:', data);
}
});
回调地狱问题
当需要处理多个异步操作时,回调函数会导致”回调地狱”:
fetchUser(userId, (userError, user) => {
if (userError) {
handleError(userError);
return;
}
fetchUserPosts(user.id, (postsError, posts) => {
if (postsError) {
handleError(postsError);
return;
}
fetchPostComments(posts[0].id, (commentsError, comments) => {
if (commentsError) {
handleError(commentsError);
return;
}
// 终于可以处理数据了...
console.log('用户:', user);
console.log('帖子:', posts);
console.log('评论:', comments);
});
});
});
Promise
Promise为异步编程带来了更优雅的解决方案:
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const success = Math.random() > 0.3;
if (success) {
resolve({ id: 1, name: '用户数据' });
} else {
reject(new Error('网络错误'));
}
}, 1000);
});
}
// 使用Promise
fetchData()
.then(data => {
console.log('数据:', data);
return processData(data);
})
.then(processedData => {
console.log('处理后的数据:', processedData);
})
.catch(error => {
console.error('错误:', error.message);
});
Promise链式调用
Promise可以很好地解决回调地狱问题:
fetchUser(userId)
.then(user => {
console.log('用户:', user);
return fetchUserPosts(user.id);
})
.then(posts => {
console.log('帖子:', posts);
return fetchPostComments(posts[0].id);
})
.then(comments => {
console.log('评论:', comments);
})
.catch(error => {
console.error('请求失败:', error);
});
Promise.all 并行处理
当需要同时处理多个独立的异步操作时:
const promises = [
fetchUser(userId),
fetchUserSettings(userId),
fetchUserNotifications(userId)
];
Promise.all(promises)
.then(([user, settings, notifications]) => {
console.log('用户信息:', user);
console.log('用户设置:', settings);
console.log('用户通知:', notifications);
})
.catch(error => {
console.error('至少一个请求失败:', error);
});
Async/Await
ES2017引入的async/await使异步代码看起来像同步代码:
async function loadUserData(userId) {
try {
const user = await fetchUser(userId);
console.log('用户:', user);
const posts = await fetchUserPosts(user.id);
console.log('帖子:', posts);
const comments = await fetchPostComments(posts[0].id);
console.log('评论:', comments);
return { user, posts, comments };
} catch (error) {
console.error('加载用户数据失败:', error);
throw error;
}
}
// 使用async函数
loadUserData(123)
.then(data => {
console.log('所有数据加载完成:', data);
})
.catch(error => {
console.error('处理失败:', error);
});
并行处理的async/await
async function loadAllData(userId) {
try {
// 并行执行多个异步操作
const [user, settings, notifications] = await Promise.all([
fetchUser(userId),
fetchUserSettings(userId),
fetchUserNotifications(userId)
]);
return { user, settings, notifications };
} catch (error) {
console.error('加载数据失败:', error);
throw error;
}
}
错误处理最佳实践
1. 统一错误处理
class ApiError extends Error {
constructor(message, status) {
super(message);
this.name = 'ApiError';
this.status = status;
}
}
async function apiRequest(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new ApiError(`HTTP错误: ${response.status}`, response.status);
}
return await response.json();
} catch (error) {
if (error instanceof ApiError) {
// API错误
console.error('API请求失败:', error.message);
} else {
// 网络错误或其他错误
console.error('请求异常:', error.message);
}
throw error;
}
}
2. 重试机制
async function retryRequest(fn, maxRetries = 3, delay = 1000) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
console.log(`尝试 ${i + 1} 失败:`, error.message);
if (i === maxRetries - 1) {
throw error; // 最后一次重试失败,抛出错误
}
// 等待一段时间后重试
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
// 使用重试机制
async function fetchDataWithRetry() {
return retryRequest(() => apiRequest('/api/data'), 3, 2000);
}
实际应用示例
文件上传进度追踪
async function uploadFileWithProgress(file, onProgress) {
return new Promise((resolve, reject) => {
const formData = new FormData();
formData.append('file', file);
const xhr = new XMLHttpRequest();
// 监听上传进度
xhr.upload.addEventListener('progress', (event) => {
if (event.lengthComputable) {
const progress = (event.loaded / event.total) * 100;
onProgress(progress);
}
});
xhr.addEventListener('load', () => {
if (xhr.status === 200) {
resolve(JSON.parse(xhr.responseText));
} else {
reject(new Error(`上传失败: ${xhr.status}`));
}
});
xhr.addEventListener('error', () => {
reject(new Error('网络错误'));
});
xhr.open('POST', '/api/upload');
xhr.send(formData);
});
}
// 使用示例
async function handleFileUpload(file) {
try {
const result = await uploadFileWithProgress(file, (progress) => {
console.log(`上传进度: ${progress.toFixed(2)}%`);
});
console.log('上传成功:', result);
} catch (error) {
console.error('上传失败:', error.message);
}
}
性能优化建议
- 避免不必要的await: 如果不需要等待结果,不要使用await
- 合理使用Promise.all: 对于独立的异步操作,使用并行处理
- 控制并发数量: 避免同时发起过多请求
- 使用适当的错误处理: 不要忽略错误,但也不要过度处理
异步编程是JavaScript的核心特性之一。通过理解和掌握这些模式,我们可以编写出更高效、更可维护的代码。