JavaScript 异步编程全面解析
JavaScript的单线程特性使得异步编程成为处理网络请求、文件操作等耗时任务的必然选择。从回调函数到Promise再到async/await,JavaScript异步编程模型持续演进,代码可读性和维护性不断提升。
一、事件循环机制
理解异步编程,必须先了解JavaScript事件循环(Event Loop):
- JavaScript单线程执行,有一个调用栈(Call Stack)
- 异步操作(setTimeout、fetch、I/O)完成后,回调函数进入任务队列
- 事件循环持续检查:调用栈为空时,从任务队列取出回调执行
- 微任务队列(Promise.then、MutationObserver)优先于宏任务队列(setTimeout、setInterval)
二、回调函数(Callbacks)
// 早期异步模式:回调函数
function getData(url, callback) {
XMLHttpRequest // ...省略XMLHttpRequest代码
}
// 回调地狱(Callback Hell)- 难以维护
getData('/api/user', function(user) {
getData('/api/posts/' + user.id, function(posts) {
getData('/api/comments/' + posts[0].id, function(comments) {
// 嵌套三层,难以阅读和维护
console.log(comments);
}, handleError);
}, handleError);
}, handleError);
三、Promise:链式调用
// Promise解决回调地狱
function getData(url) {
return new Promise((resolve, reject) => {
fetch(url)
.then(res => res.json())
.then(resolve)
.catch(reject);
});
}
// 链式调用,清晰易读
getData('/api/user')
.then(user => getData('/api/posts/' + user.id))
.then(posts => getData('/api/comments/' + posts[0].id))
.then(comments => console.log(comments))
.catch(err => console.error('Error:', err));
// Promise.all:并行执行多个Promise
Promise.all([
fetch('/api/users').then(r => r.json()),
fetch('/api/products').then(r => r.json()),
fetch('/api/orders').then(r => r.json()),
]).then(([users, products, orders]) => {
// 三个请求全部完成后执行
console.log(users, products, orders);
});
// Promise.allSettled:等待所有Promise完成(不管成功或失败)
Promise.allSettled([p1, p2, p3]).then(results => {
results.forEach(result => {
if (result.status === 'fulfilled') console.log(result.value);
else console.error(result.reason);
});
});
四、async/await:最佳实践
// async/await让异步代码看起来像同步代码
async function loadUserData(userId) {
try {
const user = await fetch('/api/user/' + userId).then(r => r.json());
const posts = await fetch('/api/posts/' + user.id).then(r => r.json());
const comments = await fetch('/api/comments/' + posts[0].id).then(r => r.json());
return { user, posts, comments };
} catch (err) {
console.error('Failed to load user data:', err);
throw err; // 重新抛出,让调用方处理
}
}
// 并行请求(不要用串行await!)
async function loadDashboard() {
// 错误写法:串行等待,总时间 = t1 + t2 + t3
const users = await getUsers();
const products = await getProducts();
const orders = await getOrders();
// 正确写法:并行请求,总时间 = max(t1, t2, t3)
const [users, products, orders] = await Promise.all([
getUsers(),
getProducts(),
getOrders(),
]);
}
五、错误处理最佳实践
// 统一错误处理包装器
async function safeAsync(fn) {
try {
const result = await fn();
return [null, result];
} catch (err) {
return [err, null];
}
}
// 使用方式
const [err, data] = await safeAsync(() => fetch('/api/data'));
if (err) {
console.error('Request failed:', err);
return;
}
console.log('Data:', data);
六、生成器与异步迭代
// 异步生成器:处理流式数据
async function* streamData(url) {
const response = await fetch(url);
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
yield decoder.decode(value);
}
}
// 使用 for await...of 消费
for await (const chunk of streamData('/api/stream')) {
processChunk(chunk);
}
七、总结
现代JavaScript异步编程的最佳实践:使用async/await让代码可读;用Promise.all并行执行独立的异步任务;始终处理错误(try/catch或.catch());避免在循环中使用await(改用Promise.all);使用AbortController实现请求取消。掌握这些模式,可以写出高效、可维护的异步代码。