引言
JavaScript作为单线程语言,异步编程是其核心特性之一。从最初的回调函数,到Promise,再到Async/Await语法糖,JavaScript的异步编程方式经历了多次演进。本文将深入探讨JavaScript异步编程的8大核心概念,帮助你全面掌握异步编程的艺术。
回调函数时代
1. 基础回调模式
回调函数是JavaScript最早的异步处理方式,虽然简单但容易产生"回调地狱"。
javascript
// 基础回调示例
function fetchData(callback) {
setTimeout(() => {
const data = { id: 1, name: 'JavaScript' };
callback(data);
}, 1000);
}
// 使用回调
fetchData(function(data) {
console.log('获取到数据:', data);
});
2. 回调地狱问题
当多个异步操作需要按顺序执行时,回调函数会嵌套过深,代码难以维护。
javascript
// 回调地狱示例
fetchData(function(data1) {
processData(data1, function(data2) {
saveData(data2, function(result) {
notifyUser(result, function() {
console.log('所有操作完成');
});
});
});
});
3. 错误处理困境
回调函数的错误处理比较复杂,需要通过额外的参数传递错误信息。
javascript
// Node.js风格的错误优先回调
function readFile(filename, callback) {
fs.readFile(filename, (err, data) => {
if (err) {
callback(err, null);
} else {
callback(null, data);
}
});
}
// 使用示例
readFile('config.json', (err, data) => {
if (err) {
console.error('读取文件失败:', err);
return;
}
console.log('文件内容:', data);
});
Promise革命
4. Promise基础用法
Promise的出现解决了回调地狱的问题,提供了更优雅的异步处理方式。
javascript
// 创建Promise
function fetchUserData(userId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (userId > 0) {
resolve({ id: userId, name: '用户' + userId });
} else {
reject(new Error('无效的用户ID'));
}
}, 1000);
});
}
// 使用Promise
fetchUserData(1)
.then(user => {
console.log('用户信息:', user);
return user;
})
.catch(error => {
console.error('获取用户失败:', error);
});
5. Promise链式调用
Promise支持链式调用,可以优雅地处理多个异步操作。
javascript
// Promise链式调用
fetchUserData(1)
.then(user => {
console.log('步骤1: 获取用户', user);
return fetchUserPosts(user.id);
})
.then(posts => {
console.log('步骤2: 获取文章', posts);
return fetchPostComments(posts[0].id);
})
.then(comments => {
console.log('步骤3: 获取评论', comments);
})
.catch(error => {
console.error('发生错误:', error);
});
6. Promise静态方法
Promise提供了多个静态方法,用于处理多个Promise实例。
javascript
// Promise.all - 所有Promise都成功才成功
Promise.all([
fetchUserData(1),
fetchUserData(2),
fetchUserData(3)
])
.then(users => {
console.log('所有用户:', users);
})
.catch(error => {
console.error('至少有一个请求失败:', error);
});
// Promise.race - 返回最先完成的Promise
Promise.race([
fetchFromServer1(),
fetchFromServer2()
])
.then(result => {
console.log('最快的结果:', result);
});
// Promise.allSettled - 等待所有Promise完成
Promise.allSettled([
fetchUserData(1),
fetchUserData(-1) // 这个会失败
])
.then(results => {
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`请求${index}成功:`, result.value);
} else {
console.log(`请求${index}失败:`, result.reason);
}
});
});
// Promise.any - 返回第一个成功的Promise
Promise.any([
fetchFromBackup1(),
fetchFromBackup2(),
fetchFromBackup3()
])
.then(result => {
console.log('第一个成功的备份:', result);
})
.catch(error => {
console.error('所有备份都失败:', error);
});
Async/Await语法糖
7. Async/Await基础
Async/Await是Promise的语法糖,让异步代码看起来像同步代码。
javascript
// Async函数定义
async function getUserData() {
try {
const user = await fetchUserData(1);
console.log('用户信息:', user);
const posts = await fetchUserPosts(user.id);
console.log('用户文章:', posts);
return posts;
} catch (error) {
console.error('获取数据失败:', error);
throw error;
}
}
// 调用Async函数
getUserData()
.then(posts => console.log('最终结果:', posts))
.catch(error => console.error('错误:', error));
8. 并行异步操作
使用Promise.all配合Async/Await可以实现并行异步操作。
javascript
// 串行执行(慢)
async function sequentialExecution() {
const user1 = await fetchUserData(1);
const user2 = await fetchUserData(2);
const user3 = await fetchUserData(3);
return [user1, user2, user3];
}
// 并行执行(快)
async function parallelExecution() {
const [user1, user2, user3] = await Promise.all([
fetchUserData(1),
fetchUserData(2),
fetchUserData(3)
]);
return [user1, user2, user3];
}
// 批量处理
async function batchProcessing(userIds) {
const batchSize = 5;
const results = [];
for (let i = 0; i < userIds.length; i += batchSize) {
const batch = userIds.slice(i, i + batchSize);
const batchResults = await Promise.all(
batch.map(id => fetchUserData(id))
);
results.push(...batchResults);
}
return results;
}
高级异步模式
9. 异步迭代器
异步迭代器可以处理异步的数据流。
javascript
// 异步生成器函数
async function* asyncGenerator() {
let i = 0;
while (i < 5) {
await new Promise(resolve => setTimeout(resolve, 1000));
yield i++;
}
}
// 使用异步迭代器
async function consumeAsyncGenerator() {
for await (const value of theAsyncGenerator()) {
console.log('接收到值:', value);
}
}
// 实际应用:分页获取数据
async function* fetchPaginatedData(url) {
let page = 1;
let hasMore = true;
while (hasMore) {
const response = await fetch(`${url}?page=${page}`);
const data = await response.json();
yield data.items;
hasMore = data.hasMore;
page++;
}
}
// 使用分页数据
async function processAllData() {
for await (const items of fetchPaginatedData('/api/data')) {
items.forEach(item => processItem(item));
}
}
10. 异步队列
实现一个异步队列,控制并发请求数量。
javascript
class AsyncQueue {
constructor(concurrency = 5) {
this.concurrency = concurrency;
this.running = 0;
this.queue = [];
}
async add(task) {
return new Promise((resolve, reject) => {
this.queue.push({ task, resolve, reject });
this.runNext();
});
}
async runNext() {
if (this.running >= this.concurrency || this.queue.length === 0) {
return;
}
this.running++;
const { task, resolve, reject } = this.queue.shift();
try {
const result = await task();
resolve(result);
} catch (error) {
reject(error);
} finally {
this.running--;
this.runNext();
}
}
}
// 使用异步队列
const queue = new AsyncQueue(3); // 最多3个并发
const urls = [
'https://api.example.com/data1',
'https://api.example.com/data2',
'https://api.example.com/data3',
'https://api.example.com/data4',
'https://api.example.com/data5'
];
const promises = urls.map(url =>
queue.add(() => fetch(url).then(res => res.json()))
);
const results = await Promise.all(promises);
11. 异步重试机制
实现自动重试的异步操作。
javascript
async function retryAsyncOperation(
operation,
maxRetries = 3,
delay = 1000
) {
let lastError;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error;
if (attempt < maxRetries) {
console.log(`第${attempt}次尝试失败,${delay}ms后重试...`);
await new Promise(resolve => setTimeout(resolve, delay));
delay *= 2; // 指数退避
}
}
}
throw new Error(`操作失败,已重试${maxRetries}次: ${lastError.message}`);
}
// 使用示例
async function fetchWithRetry(url) {
return retryAsyncOperation(
() => fetch(url).then(res => {
if (!res.ok) throw new Error('请求失败');
return res.json();
}),
3,
1000
);
}
const data = await fetchWithRetry('https://api.example.com/data');
12. 异步超时控制
为异步操作设置超时时间。
javascript
function withTimeout(promise, timeoutMs) {
return Promise.race([
promise,
new Promise((_, reject) =>
setTimeout(() => reject(new Error('操作超时')), timeoutMs)
)
]);
}
// 使用示例
async function fetchWithTimeout(url, timeout = 5000) {
try {
const response = await withTimeout(
fetch(url),
timeout
);
return response.json();
} catch (error) {
if (error.message === '操作超时') {
console.error('请求超时,请检查网络连接');
}
throw error;
}
}
// AbortController实现可取消的请求
async function fetchWithAbort(url, timeout = 5000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, {
signal: controller.signal
});
clearTimeout(timeoutId);
return response.json();
} catch (error) {
if (error.name === 'AbortError') {
throw new Error('请求超时');
}
throw error;
}
}
错误处理最佳实践
13. 全局错误处理
设置全局的异步错误处理器。
javascript
// 未捕获的Promise错误
window.addEventListener('unhandledrejection', event => {
console.error('未处理的Promise拒绝:', event.reason);
// 发送到错误监控服务
sendToErrorTracking({
type: 'unhandledRejection',
error: event.reason,
stack: event.reason?.stack
});
});
// 全局错误
window.addEventListener('error', event => {
console.error('全局错误:', event.error);
sendToErrorTracking({
type: 'globalError',
error: event.error,
message: event.message
});
});
14. 错误边界模式
在React等框架中实现错误边界。
javascript
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
console.error('组件错误:', error, errorInfo);
// 记录错误
logErrorToService(error, errorInfo);
}
render() {
if (this.state.hasError) {
return <ErrorFallback error={this.state.error} />;
}
return this.props.children;
}
}
// 使用ErrorBoundary
<ErrorBoundary>
<MyComponent />
</ErrorBoundary>
性能优化技巧
15. 防抖与节流
在异步操作中使用防抖和节流优化性能。
javascript
// 异步防抖
function asyncDebounce(func, delay) {
let timer = null;
let pendingPromise = null;
return function(...args) {
return new Promise((resolve, reject) => {
if (timer) {
clearTimeout(timer);
if (pendingPromise) {
pendingPromise.reject(new Error('取消'));
}
}
pendingPromise = { resolve, reject };
timer = setTimeout(async () => {
try {
const result = await func.apply(this, args);
pendingPromise.resolve(result);
} catch (error) {
pendingPromise.reject(error);
}
timer = null;
pendingPromise = null;
}, delay);
});
};
}
// 使用示例
const debouncedSearch = asyncDebounce(
async (query) => {
const response = await fetch(`/api/search?q=${query}`);
return response.json();
},
300
);
// 搜索输入框
searchInput.addEventListener('input', async (e) => {
try {
const results = await debouncedSearch(e.target.value);
displayResults(results);
} catch (error) {
if (error.message !== '取消') {
console.error('搜索失败:', error);
}
}
});
16. 请求缓存
实现异步请求的缓存机制。
javascript
class AsyncCache {
constructor(ttl = 60000) {
this.cache = new Map();
this.ttl = ttl;
}
async get(key, fetcher) {
const cached = this.cache.get(key);
if (cached && Date.now() - cached.timestamp < this.ttl) {
return cached.data;
}
const data = await fetcher();
this.cache.set(key, {
data,
timestamp: Date.now()
});
return data;
}
clear() {
this.cache.clear();
}
clearExpired() {
const now = Date.now();
for (const [key, value] of this.cache.entries()) {
if (now - value.timestamp >= this.ttl) {
this.cache.delete(key);
}
}
}
}
// 使用示例
const cache = new AsyncCache(300000); // 5分钟缓存
async function getUserInfo(userId) {
return cache.get(`user:${userId}`, async () => {
const response = await fetch(`/api/users/${userId}`);
return response.json();
});
}
总结
JavaScript异步编程经历了从回调函数到Promise,再到Async/Await的演进,每种方式都有其适用场景:
1. 选择合适的异步方式
- 简单场景:回调函数仍然适用
- 链式操作:Promise提供更好的可读性
- 复杂逻辑:Async/Await让代码更清晰
2. 最佳实践
- 错误处理:始终使用try/catch或.catch()
- 并发控制:合理使用Promise.all和队列
- 性能优化:使用缓存、防抖、节流等技术
- 可读性:保持异步代码的清晰和简洁
3. 进阶技巧
- 异步迭代器:处理异步数据流
- 重试机制:提高操作可靠性
- 超时控制:防止操作卡死
- 错误边界:优雅处理错误
掌握JavaScript异步编程,将帮助你构建出更高效、更可靠的前端应用。记住,异步编程的核心是理解事件循环和Promise的工作原理,这样才能写出真正优秀的异步代码。
本文首发于掘金,欢迎关注我的专栏获取更多前端技术干货!