什么是异步编程
异步编程是一种编程模式,允许程序在等待某些操作(如网络请求、文件读取)完成的同时继续执行其他代码。这对于构建高性能的用户界面至关重要。
JavaScript 是单线程的
JavaScript 运行在单个线程上,这意味着它一次只能执行一个任务。但是,通过异步编程,JavaScript 可以在等待耗时操作时处理其他任务。
事件循环(Event Loop)
事件循环是 JavaScript 实现异步的核心机制:
console.log('1');
setTimeout(() => {
console.log('2');
}, 0);
console.log('3');
// 输出顺序:1 -> 3 -> 2
执行顺序
- 同步任务:在主线程上执行
- 微任务:Promise.then、MutationObserver 等
- 宏任务:setTimeout、setInterval、I/O 等
console.log('start');
setTimeout(() => {
console.log('timeout');
}, 0);
Promise.resolve().then(() => {
console.log('promise');
});
console.log('end');
// 输出:start -> end -> promise -> timeout
回调函数(Callbacks)
回调是最原始的异步处理方式:
function fetchData(callback) {
setTimeout(() => {
callback('数据加载完成');
}, 1000);
}
fetchData((data) => {
console.log(data);
});
回调地狱
过多的嵌套回调会导致”回调地狱”:
// ❌ 不推荐
getData((data) => {
processData(data, (processed) => {
saveData(processed, (saved) => {
// 继续嵌套...
});
});
});
Promise
Promise 是对回调的改进,提供了更好的错误处理和链式调用:
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('数据加载完成');
}, 1000);
});
promise
.then(data => {
console.log(data);
return data.toUpperCase();
})
.then(data => {
console.log(data);
})
.catch(error => {
console.error(error);
});
Promise 链
Promise 支持链式调用:
fetchUser(userId)
.then(user => fetchPosts(user.id))
.then(posts => processPosts(posts))
.then(processed => displayPosts(processed))
.catch(error => handleError(error));
Promise.all
并行执行多个 Promise:
const promises = [
fetch('/api/users'),
fetch('/api/posts'),
fetch('/api/comments'),
];
Promise.all(promises)
.then(responses => Promise.all(responses.map(r => r.json())))
.then(data => console.log(data));
Promise.race
使用最先完成的 Promise:
const timeout = new Promise((resolve, reject) => {
setTimeout(() => reject('请求超时'), 5000);
});
Promise.race([fetch('/api/data'), timeout])
.then(data => console.log(data))
.catch(error => console.error(error));
async/await
async/await 是 Promise 的语法糖,让异步代码看起来像同步代码:
async function fetchData() {
try {
const response = await fetch('/api/data');
const data = await response.json();
return data;
} catch (error) {
console.error(error);
}
}
错误处理
使用 try-catch 处理错误:
async function handleRequest() {
try {
const user = await fetchUser(userId);
const posts = await fetchPosts(user.id);
return posts;
} catch (error) {
console.error('请求失败:', error);
throw error; // 可以选择重新抛出或处理错误
}
}
并行执行
使用 Promise.all + async/await:
async function fetchAllData() {
const [users, posts, comments] = await Promise.all([
fetch('/api/users').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
fetch('/api/comments').then(r => r.json()),
]);
return { users, posts, comments };
}
实际应用场景
1. 数据获取
async function loadUserProfile(userId) {
const user = await fetch(`/api/users/${userId}`).then(r => r.json());
const posts = await fetch(`/api/users/${userId}/posts`).then(r => r.json());
return { user, posts };
}
2. 表单提交
async function submitForm(formData) {
try {
const response = await fetch('/api/submit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData),
});
if (!response.ok) {
throw new Error('提交失败');
}
const result = await response.json();
return result;
} catch (error) {
console.error(error);
alert('提交失败,请重试');
}
}
3. 图片懒加载
async function loadImage(src) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = reject;
img.src = src;
});
}
最佳实践
1. 始终处理错误
// ❌ 错误示例
fetchData().then(data => console.log(data));
// ✅ 正确示例
fetchData()
.then(data => console.log(data))
.catch(error => console.error(error));
2. 避免在循环中使用 async/await
// ❌ 串行执行,慢
async function processItems(items) {
for (const item of items) {
await processItem(item); // 每次都等待
}
}
// ✅ 并行执行,快
async function processItems(items) {
return Promise.all(items.map(item => processItem(item)));
}
3. 使用 AbortController 取消请求
const controller = new AbortController();
async function fetchData() {
try {
const response = await fetch('/api/data', {
signal: controller.signal,
});
return await response.json();
} catch (error) {
if (error.name === 'AbortError') {
console.log('请求已取消');
}
throw error;
}
}
// 取消请求
controller.abort();
总结
JavaScript 异步编程是前端开发的核心概念:
- 🔄 事件循环:理解异步执行的基础
- 📞 回调:最初的异步处理方式
- 🎯 Promise:改进的异步处理,支持链式调用
- ⚡ async/await:最优雅的异步语法
- 🛡️ 错误处理:始终处理可能的错误
掌握异步编程将帮助你构建更快、更可靠的 JavaScript 应用!