JavaScript 异步编程
简介
JavaScript 是单线程语言,但通过异步编程可以处理耗时操作而不阻塞主线程。异步编程从最初的回调函数,发展到 Promise,再到现代的 async/await,让异步代码越来越简洁和易读。
回调函数(Callback)
基本概念
回调函数是作为参数传递给其他函数的函数,在特定事件发生或任务完成时被调用。
// 基本回调示例
function greet(name, callback) {
console.log(`Hello, ${name}!`);
callback();
}
function afterGreet() {
console.log('Nice to meet you!');
}
greet('Alice', afterGreet);
// 输出:
// Hello, Alice!
// Nice to meet you!
// 异步回调示例
function fetchData(callback) {
console.log('开始获取数据...');
setTimeout(() => {
const data = { id: 1, name: '用户数据' };
callback(data);
}, 2000);
}
fetchData((data) => {
console.log('数据获取完成:', data);
});
console.log('这行会先执行');
总结:回调函数是异步编程的基础,通过函数参数传递来处理异步操作的结果。
回调地狱问题
// 回调地狱示例
function getUser(userId, callback) {
setTimeout(() => {
callback({ id: userId, name: 'Alice' });
}, 1000);
}
function getPosts(userId, callback) {
setTimeout(() => {
callback([
{ id: 1, title: '文章1' },
{ id: 2, title: '文章2' },
]);
}, 1000);
}
function getComments(postId, callback) {
setTimeout(() => {
callback([{ id: 1, content: '评论内容' }]);
}, 1000);
}
// 嵌套调用导致回调地狱
getUser(1, (user) => {
console.log('用户:', user);
getPosts(user.id, (posts) => {
console.log('文章:', posts);
getComments(posts[0].id, (comments) => {
console.log('评论:', comments);
// 继续嵌套...
});
});
});
总结:多层嵌套的回调函数形成"回调地狱",代码难以阅读和维护。
Promise
基本概念
Promise 是表示异步操作最终完成或失败的对象,有三种状态:pending(进行中)、fulfilled(已成功)、rejected(已失败)。
// 创建 Promise
const myPromise = new Promise((resolve, reject) => {
const success = true;
setTimeout(() => {
if (success) {
resolve('操作成功!');
} else {
reject('操作失败!');
}
}, 1000);
});
// 使用 Promise
myPromise
.then((result) => {
console.log(result); // '操作成功!'
})
.catch((error) => {
console.log(error);
});
// Promise 状态演示
function createPromise(shouldResolve) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (shouldResolve) {
resolve('成功数据');
} else {
reject(new Error('失败原因'));
}
}, 1000);
});
}
createPromise(true)
.then((data) => console.log('成功:', data))
.catch((error) => console.log('失败:', error.message));
总结:Promise 提供了更优雅的异步处理方式,避免了回调地狱问题。
Promise 链式调用
// 重写回调地狱示例
function getUser(userId) {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ id: userId, name: 'Alice' });
}, 1000);
});
}
function getPosts(userId) {
return new Promise((resolve) => {
setTimeout(() => {
resolve([
{ id: 1, title: '文章1' },
{ id: 2, title: '文章2' },
]);
}, 1000);
});
}
function getComments(postId) {
return new Promise((resolve) => {
setTimeout(() => {
resolve([{ id: 1, content: '评论内容' }]);
}, 1000);
});
}
// 链式调用
getUser(1)
.then((user) => {
console.log('用户:', user);
return getPosts(user.id);
})
.then((posts) => {
console.log('文章:', posts);
return getComments(posts[0].id);
})
.then((comments) => {
console.log('评论:', comments);
})
.catch((error) => {
console.log('错误:', error);
});
总结:Promise 链式调用使异步代码更线性和易读,每个 then 处理前一步的结果。
Promise 静态方法
// Promise.all - 等待所有 Promise 完成
const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
const promise3 = Promise.resolve(3);
Promise.all([promise1, promise2, promise3]).then((values) => {
console.log(values); // [1, 2, 3]
});
// Promise.race - 返回最先完成的 Promise
const fastPromise = new Promise((resolve) => setTimeout(() => resolve('快'), 100));
const slowPromise = new Promise((resolve) => setTimeout(() => resolve('慢'), 1000));
Promise.race([fastPromise, slowPromise]).then((value) => {
console.log(value); // '快'
});
// Promise.allSettled - 等待所有 Promise 完成(无论成功失败)
const promises = [Promise.resolve('成功1'), Promise.reject('失败'), Promise.resolve('成功2')];
Promise.allSettled(promises).then((results) => {
console.log(results);
// [
// { status: 'fulfilled', value: '成功1' },
// { status: 'rejected', reason: '失败' },
// { status: 'fulfilled', value: '成功2' }
// ]
});
// Promise.any - 返回第一个成功的 Promise
const promises2 = [Promise.reject('错误1'), Promise.resolve('成功'), Promise.reject('错误2')];
Promise.any(promises2)
.then((value) => {
console.log(value); // '成功'
})
.catch((error) => {
console.log('所有都失败了');
});
总结:Promise 静态方法提供了处理多个异步操作的强大工具。
async/await
基本概念
async/await 是基于 Promise 的语法糖,让异步代码看起来像同步代码,更易读和维护。
// async 函数声明
async function fetchData() {
return '数据'; // 自动包装在 Promise 中
}
// 等价于
function fetchDataPromise() {
return Promise.resolve('数据');
}
// await 使用
async function getData() {
try {
const result = await fetchData();
console.log(result); // '数据'
} catch (error) {
console.log('错误:', error);
}
}
getData();
// 处理异步操作
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function sequentialTasks() {
console.log('开始任务');
await delay(1000);
console.log('任务1完成');
await delay(1000);
console.log('任务2完成');
return '所有任务完成';
}
sequentialTasks().then((result) => console.log(result));
总结:async/await 让异步代码更像同步代码,提高了代码的可读性和维护性。
错误处理
// try-catch 错误处理
async function handleErrors() {
try {
const data1 = await Promise.resolve('成功数据');
console.log(data1);
const data2 = await Promise.reject(new Error('网络错误'));
console.log(data2); // 不会执行
} catch (error) {
console.log('捕获错误:', error.message); // '网络错误'
}
}
handleErrors();
// 分别处理不同错误
async function handleMultipleErrors() {
try {
const userData = await getUser(1);
console.log('用户数据:', userData);
} catch (error) {
console.log('获取用户失败:', error);
return; // 提前返回
}
try {
const postsData = await getPosts(1);
console.log('文章数据:', postsData);
} catch (error) {
console.log('获取文章失败:', error);
// 继续执行其他代码
}
}
// 使用 Promise.catch() 的另一种方式
async function alternativeErrorHandling() {
const userData = await getUser(1).catch((error) => {
console.log('用户获取失败:', error);
return null; // 提供默认值
});
if (userData) {
console.log('用户数据:', userData);
} else {
console.log('使用默认用户数据');
}
}
总结:async/await 使用 try-catch 进行错误处理,比 Promise 的 catch 更直观。
并发执行
// 串行执行(等待每个完成)
async function serialExecution() {
console.time('串行执行');
const result1 = await delay(1000);
const result2 = await delay(1000);
const result3 = await delay(1000);
console.timeEnd('串行执行'); // 约 3000ms
return [result1, result2, result3];
}
// 并行执行(同时开始)
async function parallelExecution() {
console.time('并行执行');
const promise1 = delay(1000);
const promise2 = delay(1000);
const promise3 = delay(1000);
const results = await Promise.all([promise1, promise2, promise3]);
console.timeEnd('并行执行'); // 约 1000ms
return results;
}
// 实际应用示例
async function fetchUserData(userId) {
// 并行获取用户基本信息和设置
const [userInfo, userSettings, userPosts] = await Promise.all([
getUser(userId),
getUserSettings(userId),
getPosts(userId),
]);
return {
info: userInfo,
settings: userSettings,
posts: userPosts,
};
}
// 有条件的并行执行
async function conditionalParallel(userId) {
const user = await getUser(userId);
// 根据用户类型决定获取哪些数据
const promises = [getPosts(userId)];
if (user.isPremium) {
promises.push(getPremiumContent(userId));
}
if (user.isAdmin) {
promises.push(getAdminData(userId));
}
const results = await Promise.all(promises);
return results;
}
总结:理解串行和并行执行的区别,合理使用 Promise.all 提高性能。
实际应用场景
1. API 调用
// 封装 HTTP 请求
class ApiClient {
constructor(baseURL) {
this.baseURL = baseURL;
}
async request(endpoint, options = {}) {
const url = `${this.baseURL}${endpoint}`;
const config = {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
...options,
};
try {
const response = await fetch(url, config);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
} catch (error) {
console.error('API 请求失败:', error);
throw error;
}
}
async get(endpoint) {
return this.request(endpoint);
}
async post(endpoint, data) {
return this.request(endpoint, {
method: 'POST',
body: JSON.stringify(data),
});
}
async put(endpoint, data) {
return this.request(endpoint, {
method: 'PUT',
body: JSON.stringify(data),
});
}
async delete(endpoint) {
return this.request(endpoint, {
method: 'DELETE',
});
}
}
// 使用示例
const api = new ApiClient('https://api.example.com');
async function handleUserOperations() {
try {
// 获取用户列表
const users = await api.get('/users');
console.log('用户列表:', users);
// 创建新用户
const newUser = await api.post('/users', {
name: 'Alice',
email: 'alice@example.com',
});
console.log('新用户:', newUser);
// 更新用户
const updatedUser = await api.put(`/users/${newUser.id}`, {
name: 'Alice Smith',
});
console.log('更新后用户:', updatedUser);
} catch (error) {
console.error('操作失败:', error);
}
}
总结:封装 API 调用使异步操作更加规范和易于维护。
2. 文件操作
// 文件上传
async function uploadFile(file) {
const formData = new FormData();
formData.append('file', file);
try {
// 显示上传进度
const response = await fetch('/upload', {
method: 'POST',
body: formData,
});
if (!response.ok) {
throw new Error('上传失败');
}
const result = await response.json();
console.log('上传成功:', result);
return result;
} catch (error) {
console.error('上传错误:', error);
throw error;
}
}
// 批量文件上传
async function uploadMultipleFiles(files) {
const uploadPromises = Array.from(files).map((file) => uploadFile(file));
try {
const results = await Promise.allSettled(uploadPromises);
const successful = results.filter((result) => result.status === 'fulfilled');
const failed = results.filter((result) => result.status === 'rejected');
console.log(`成功上传 ${successful.length} 个文件`);
console.log(`失败 ${failed.length} 个文件`);
return { successful, failed };
} catch (error) {
console.error('批量上传错误:', error);
}
}
// 文件读取
function readFileAsText(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (event) => resolve(event.target.result);
reader.onerror = (error) => reject(error);
reader.readAsText(file);
});
}
async function processFiles(files) {
for (const file of files) {
try {
const content = await readFileAsText(file);
console.log(`文件 ${file.name} 内容:`, content);
} catch (error) {
console.error(`读取文件 ${file.name} 失败:`, error);
}
}
}
总结:异步编程在文件操作中处理上传、下载、读取等耗时操作。
3. 定时任务和动画
// 定时任务管理
class TaskScheduler {
constructor() {
this.tasks = [];
this.isRunning = false;
}
// 添加延时任务
addDelayedTask(fn, delay) {
return new Promise((resolve) => {
setTimeout(async () => {
const result = await fn();
resolve(result);
}, delay);
});
}
// 添加重复任务
addRepeatingTask(fn, interval) {
const task = {
id: Date.now(),
fn,
interval,
isActive: true,
};
this.tasks.push(task);
this.runRepeatingTask(task);
return task.id;
}
async runRepeatingTask(task) {
while (task.isActive) {
try {
await task.fn();
} catch (error) {
console.error('任务执行错误:', error);
}
await this.delay(task.interval);
}
}
// 停止任务
stopTask(taskId) {
const task = this.tasks.find((t) => t.id === taskId);
if (task) {
task.isActive = false;
}
}
delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
}
// 使用示例
const scheduler = new TaskScheduler();
// 延时任务
scheduler.addDelayedTask(() => {
console.log('3秒后执行的任务');
}, 3000);
// 重复任务
const taskId = scheduler.addRepeatingTask(() => {
console.log('每2秒执行一次:', new Date().toLocaleTimeString());
}, 2000);
// 10秒后停止重复任务
setTimeout(() => {
scheduler.stopTask(taskId);
console.log('重复任务已停止');
}, 10000);
// 动画帧处理
class AnimationController {
constructor() {
this.animations = [];
}
async animate(element, properties, duration) {
return new Promise((resolve) => {
const startTime = performance.now();
const startValues = {};
// 获取初始值
for (const prop in properties) {
startValues[prop] = parseFloat(getComputedStyle(element)[prop]) || 0;
}
const animate = (currentTime) => {
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / duration, 1);
// 应用缓动函数
const eased = this.easeInOutCubic(progress);
// 更新样式
for (const prop in properties) {
const startValue = startValues[prop];
const endValue = properties[prop];
const currentValue = startValue + (endValue - startValue) * eased;
element.style[prop] = `${currentValue}px`;
}
if (progress < 1) {
requestAnimationFrame(animate);
} else {
resolve();
}
};
requestAnimationFrame(animate);
});
}
easeInOutCubic(t) {
return t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1;
}
async sequence(animations) {
for (const animation of animations) {
await this.animate(animation.element, animation.properties, animation.duration);
}
}
async parallel(animations) {
const promises = animations.map((animation) =>
this.animate(animation.element, animation.properties, animation.duration)
);
await Promise.all(promises);
}
}
总结:异步编程在定时任务和动画中提供流畅的用户体验。
错误处理最佳实践
全局错误处理
// 全局 Promise 错误处理
window.addEventListener('unhandledrejection', (event) => {
console.error('未处理的 Promise 错误:', event.reason);
// 可以选择阻止错误显示在控制台
event.preventDefault();
// 发送错误报告
reportError(event.reason);
});
// 错误报告函数
async function reportError(error) {
try {
await fetch('/api/errors', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
message: error.message,
stack: error.stack,
timestamp: new Date().toISOString(),
}),
});
} catch (reportingError) {
console.error('错误报告失败:', reportingError);
}
}
// 统一错误处理装饰器
function withErrorHandling(fn) {
return async function (...args) {
try {
return await fn.apply(this, args);
} catch (error) {
console.error(`函数 ${fn.name} 执行错误:`, error);
// 可以在这里添加通用错误处理逻辑
throw error; // 重新抛出错误
}
};
}
// 使用装饰器
const safeApiCall = withErrorHandling(async function (url) {
const response = await fetch(url);
return response.json();
});
总结:建立完善的错误处理机制,包括全局捕获和错误报告。
性能优化技巧
避免常见陷阱
// ❌ 错误:在循环中使用 await(串行执行)
async function slowProcessing(items) {
const results = [];
for (const item of items) {
const result = await processItem(item); // 逐个等待
results.push(result);
}
return results;
}
// ✅ 正确:并行处理
async function fastProcessing(items) {
const promises = items.map((item) => processItem(item));
return Promise.all(promises);
}
// ✅ 如果需要控制并发数量
async function controlledProcessing(items, concurrency = 3) {
const results = [];
for (let i = 0; i < items.length; i += concurrency) {
const batch = items.slice(i, i + concurrency);
const batchPromises = batch.map((item) => processItem(item));
const batchResults = await Promise.all(batchPromises);
results.push(...batchResults);
}
return results;
}
// 超时处理
function withTimeout(promise, ms) {
const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error('操作超时')), ms));
return Promise.race([promise, timeout]);
}
// 使用示例
async function fetchWithTimeout(url) {
try {
const response = await withTimeout(fetch(url), 5000);
return response.json();
} catch (error) {
if (error.message === '操作超时') {
console.log('请求超时');
}
throw error;
}
}
// 重试机制
async function withRetry(fn, maxAttempts = 3, delay = 1000) {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await fn();
} catch (error) {
if (attempt === maxAttempts) {
throw error;
}
console.log(`尝试 ${attempt} 失败,${delay}ms 后重试`);
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
}
总结:理解串行和并行的性能差异,合理使用超时和重试机制。
核心要点总结
异步编程演进
- 回调函数 - 最基础的异步处理方式,容易形成回调地狱
- Promise - 解决回调地狱,提供链式调用和更好的错误处理
- async/await - 让异步代码看起来像同步代码,最易读的方式
选择指南
- 简单异步操作 - 使用 async/await
- 多个并行操作 - 使用 Promise.all/Promise.allSettled
- 复杂流程控制 - 结合使用 async/await 和 Promise 方法
- 兼容性要求 - 根据目标环境选择合适的方案
最佳实践
- 错误处理 - 始终处理 Promise 的 rejection
- 性能优化 - 区分串行和并行执行
- 代码组织 - 封装异步操作为可复用的函数
- 调试友好 - 使用有意义的函数名和错误信息
实际应用
- API 调用 - 网络请求和数据获取
- 文件操作 - 上传、下载、读取文件
- 用户交互 - 动画、定时任务、事件处理
- 状态管理 - 异步状态更新和同步
理解异步编程是现代 JavaScript 开发的核心技能,它让我们能够构建响应式和高性能的应用程序!