掌握现代JavaScript异步编程的核心概念和最佳实践
JavaScript异步编程简介
JavaScript是单线程语言,但通过异步编程模式可以处理耗时操作而不阻塞主线程。了解异步编程是成为高级JavaScript开发者的关键一步。
异步编程的演进
- 回调函数 (Callback):最基础的异步处理方式,但容易导致”回调地狱”
- Promise:ES6引入的更优雅的异步处理方案
- Async/Await:ES2017提供的让异步代码看起来像同步代码的语法糖
事件循环机制
JavaScript通过事件循环处理异步操作,理解调用栈、任务队列和微任务队列是关键:
console.log('开始');
setTimeout(() => {
console.log('setTimeout 回调');
}, 0);
Promise.resolve().then(() => {
console.log('Promise 回调');
});
console.log('结束');
// 输出顺序:
// 开始
// 结束
// Promise 回调 (微任务)
// setTimeout 回调 (宏任务)
Promise深入解析
Promise是表示异步操作最终完成或失败的对象,它有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。
创建Promise
// 创建Promise
const fetchData = new Promise((resolve, reject) => {
// 模拟异步操作
setTimeout(() => {
const success = Math.random() > 0.3;
if (success) {
resolve({ data: '获取的数据', status: 200 });
} else {
reject({ error: '获取数据失败', status: 500 });
}
}, 1000);
});
使用Promise
// 使用then和catch处理Promise
fetchData
.then(response => {
console.log('成功:', response);
return processData(response.data); // 可以返回新值或新Promise
})
.then(processedData => {
console.log('处理后的数据:', processedData);
})
.catch(error => {
console.error('失败:', error);
})
.finally(() => {
console.log('操作完成(无论成功或失败)');
});
Promise实用方法
// Promise.all - 等待所有Promise完成
Promise.all([promise1, promise2, promise3])
.then(results => {
console.log('所有操作完成:', results);
})
.catch(error => {
console.error('有一个操作失败:', error);
});
// Promise.race - 竞速,第一个完成或失败的Promise
Promise.race([promise1, promise2])
.then(result => {
console.log('第一个完成的操作:', result);
});
// Promise.allSettled - 等待所有Promise完成(无论成功或失败)
Promise.allSettled([promise1, promise2])
.then(results => {
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log('成功:', result.value);
} else {
console.log('失败:', result.reason);
}
});
});
Async/Await实战
Async/Await是基于Promise的语法糖,让异步代码看起来和同步代码类似,提高了代码的可读性和可维护性。
基本用法
// async函数总是返回Promise
async function fetchUserData(userId) {
try {
// await会暂停执行,直到Promise解决
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error('网络响应不正常');
}
const userData = await response.json();
const userPosts = await fetchUserPosts(userId);
return { ...userData, posts: userPosts };
} catch (error) {
console.error('获取用户数据失败:', error);
throw error; // 重新抛出错误以便外部捕获
}
}
// 使用async函数
async function displayUserData() {
try {
const userData = await fetchUserData(123);
renderUserProfile(userData);
} catch (error) {
showErrorMessage('无法加载用户数据');
}
}
并行操作优化
// 顺序执行 - 慢
async function sequentialFetch() {
const user = await fetch('/api/user');
const posts = await fetch('/api/posts');
const comments = await fetch('/api/comments');
return { user, posts, comments };
}
// 并行执行 - 快
async function parallelFetch() {
// 同时启动所有请求
const userPromise = fetch('/api/user');
const postsPromise = fetch('/api/posts');
const commentsPromise = fetch('/api/comments');
// 等待所有请求完成
const [user, posts, comments] = await Promise.all([
userPromise,
postsPromise,
commentsPromise
]);
return { user, posts, comments };
}
错误处理模式
// 方法1: try/catch
async function withTryCatch() {
try {
const result = await asyncOperation();
return result;
} catch (error) {
console.error('操作失败:', error);
return null;
}
}
// 方法2: 使用catch方法
async function withCatchMethod() {
const result = await asyncOperation().catch(error => {
console.error('操作失败:', error);
return null;
});
return result;
}
// 方法3: 工具函数包装
function asyncHandler(promise) {
return promise
.then(data => [data, null])
.catch(error => [null, error]);
}
// 使用工具函数
async function withHandler() {
const [data, error] = await asyncHandler(asyncOperation());
if (error) {
console.error('操作失败:', error);
return null;
}
return data;
}
综合项目实战:天气查询应用
下面我们将使用现代JavaScript异步编程技术构建一个完整的天气查询应用。
HTML结构
<div class="weather-app">
<h1>城市天气查询</h1>
<div class="search-container">
<input type="text" id="city-input" placeholder="输入城市名称">
<button id="search-btn">查询</button>
</div>
<div id="loading" class="hidden">加载中...</div>
<div id="weather-result" class="hidden">
<h2 id="city-name"></h2>
<div id="weather-info"></div>
<div id="error-message" class="hidden"></div>
</div>
</div>
JavaScript实现
// 模拟API函数
const weatherAPI = {
// 获取城市天气数据
async getWeatherData(city) {
// 模拟API调用延迟
await new Promise(resolve => setTimeout(resolve, 800));
// 模拟API响应数据
const mockData = {
'北京': { temperature: 22, condition: '晴', humidity: 45 },
'上海': { temperature: 25, condition: '多云', humidity: 60 },
'广州': { temperature: 28, condition: '小雨', humidity: 75 },
'深圳': { temperature: 27, condition: '阴', humidity: 70 }
};
if (mockData[city]) {
return mockData[city];
} else {
throw new Error(`找不到城市 "${city}" 的天气数据`);
}
},
// 搜索城市建议
async searchCities(query) {
await new Promise(resolve => setTimeout(resolve, 300));
const allCities = ['北京', '上海', '广州', '深圳', '杭州', '南京', '成都', '重庆'];
return allCities.filter(city =>
city.toLowerCase().includes(query.toLowerCase())
);
}
};
// 天气应用类
class WeatherApp {
constructor() {
this.cityInput = document.getElementById('city-input');
this.searchBtn = document.getElementById('search-btn');
this.loadingElem = document.getElementById('loading');
this.weatherResult = document.getElementById('weather-result');
this.cityNameElem = document.getElementById('city-name');
this.weatherInfoElem = document.getElementById('weather-info');
this.errorMessageElem = document.getElementById('error-message');
this.init();
}
init() {
this.searchBtn.addEventListener('click', () => this.searchWeather());
this.cityInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') this.searchWeather();
});
// 添加输入建议功能
this.cityInput.addEventListener('input', this.debounce(() => {
this.showSuggestions();
}, 300));
}
// 防抖函数
debounce(func, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(this, args), delay);
};
}
// 显示搜索建议
async showSuggestions() {
const query = this.cityInput.value.trim();
if (query.length {
setTimeout(() => {
reject(new Error(`操作超时 (${timeoutMs}ms)`));
}, timeoutMs);
});
return Promise.race([promise, timeoutPromise]);
}
// 带重试的异步操作
async function withRetry(operation, maxRetries = 3, delayMs = 1000) {
for (let attempt = 1; attempt setTimeout(resolve, delayMs));
// 指数退避策略
delayMs *= 2;
}
}
}
// 初始化应用
document.addEventListener('DOMContentLoaded', () => {
const app = new WeatherApp();
// 使用高级异步功能示例
const fetchWithTimeout = () => withTimeout(
weatherAPI.getWeatherData('北京'),
2000
);
const fetchWithRetry = () => withRetry(
() => weatherAPI.getWeatherData('上海'),
3
);
// 测试高级功能
fetchWithTimeout()
.then(data => console.log('带超时的请求成功:', data))
.catch(error => console.error('带超时的请求失败:', error));
fetchWithRetry()
.then(data => console.log('带重试的请求成功:', data))
.catch(error => console.error('带重试的请求失败:', error));
});
项目总结与最佳实践
- 使用async/await让异步代码更清晰易读
- 合理使用Promise.all进行并行操作提高性能
- 实现错误处理机制增强应用健壮性
- 使用防抖技术优化用户输入处理
- 实现超时和重试机制处理不可靠的网络请求
// 这里可以添加一些简单的样式和交互功能
document.addEventListener(‘DOMContentLoaded’, function() {
// 为导航菜单添加交互效果
const navItems = document.querySelectorAll(‘nav ul li a’);
navItems.forEach(item => {
item.addEventListener(‘click’, function(e) {
e.preventDefault();
const targetId = this.getAttribute(‘href’);
const targetElement = document.querySelector(targetId);
if (targetElement) {
targetElement.scrollIntoView({
behavior: ‘smooth’
});
}
});
});
// 为代码块添加复制功能
const codeBlocks = document.querySelectorAll(‘pre’);
codeBlocks.forEach(block => {
block.addEventListener(‘click’, function() {
const text = this.textContent;
navigator.clipboard.writeText(text).then(() => {
const originalText = this.textContent;
this.textContent = ‘代码已复制!’;
setTimeout(() => {
this.textContent = originalText;
}, 1500);
});
});
});
});