JavaScript Promise 与 async/await 实战:5 个高频异步编程场景的优雅解决方案
在前端开发中,异步编程是绕不开的核心技能。从早期的回调函数到 Promise,再到如今的 async/await,JavaScript 的异步处理方式不断进化。本文将通过 5 个实际开发中的高频场景,带你掌握 Promise 与 async/await 的优雅用法。
场景一:串行执行多个异步任务
需求:按顺序执行多个接口请求,后一个请求依赖前一个请求的结果。
错误写法(回调地狱):
getUserData(userId, function(user) {
getOrders(user.id, function(orders) {
getOrderDetails(orders[0].id, function(details) {
console.log(details);
});
});
});
优雅写法(async/await):
async function fetchOrderDetails(userId) {
try {
const user = await getUserData(userId);
const orders = await getOrders(user.id);
const details = await getOrderDetails(orders[0].id);
return details;
} catch (error) {
console.error('获取订单详情失败:', error);
throw error;
}
}
关键点:await 让异步代码看起来像同步代码,逻辑清晰,错误处理统一。
场景二:并行执行多个独立异步任务
需求:同时发起多个互不依赖的请求,等待所有请求完成后统一处理。
错误写法(串行执行,效率低):
async function fetchAllData() {
const user = await getUserData();
const orders = await getOrders();
const products = await getProducts();
return { user, orders, products };
}
优雅写法(Promise.all 并行执行):
async function fetchAllData() {
try {
const [user, orders, products] = await Promise.all([
getUserData(),
getOrders(),
getProducts()
]);
return { user, orders, products };
} catch (error) {
console.error('批量获取数据失败:', error);
throw error;
}
}
性能对比:假设每个请求耗时 100ms,串行需要 300ms,并行只需 100ms。
进阶技巧:处理部分失败的情况
async function fetchAllDataWithFallback() {
const results = await Promise.allSettled([
getUserData(),
getOrders(),
getProducts()
]);
const user = results[0].status === 'fulfilled' ? results[0].value : null;
const orders = results[1].status === 'fulfilled' ? results[1].value : [];
const products = results[2].status === 'fulfilled' ? results[2].value : [];
return { user, orders, products };
}
场景三:异步请求超时控制
需求:防止接口响应过慢导致页面卡死,设置超时自动取消。
优雅写法(Promise.race 实现超时):
function fetchWithTimeout(url, timeout = 5000) {
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('请求超时')), timeout);
});
return Promise.race([
fetch(url),
timeoutPromise
]);
}
async function fetchDataWithTimeout(url) {
try {
const response = await fetchWithTimeout(url, 5000);
const data = await response.json();
return data;
} catch (error) {
if (error.message === '请求超时') {
console.warn('请求超时,已自动取消');
// 可以显示友好的超时提示
}
throw error;
}
}
场景四:异步重试机制
需求:网络不稳定时自动重试,提高请求成功率。
优雅写法(指数退避重试):
async function fetchWithRetry(url, options = {}) {
const {
maxRetries = 3,
baseDelay = 1000,
timeout = 5000
} = options;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await fetchWithTimeout(url, timeout);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
if (attempt === maxRetries) {
console.error(`请求失败,已重试${maxRetries}次:`, error);
throw error;
}
// 指数退避:1s, 2s, 4s...
const delay = baseDelay * Math.pow(2, attempt - 1);
console.log(`第{attempt}次失败,{delay}ms 后重试...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
使用示例:
const data = await fetchWithRetry('/api/user/profile', {
maxRetries: 3,
baseDelay: 1000,
timeout: 5000
});
场景五:请求取消(AbortController)
需求:用户快速切换页面时,取消未完成的请求,避免内存泄漏和数据错乱。
优雅写法(AbortController 取消请求):
function createCancelableFetch() {
const controller = new AbortController();
const fetchCancelable = async (url) => {
const response = await fetch(url, {
signal: controller.signal
});
return await response.json();
};
const cancel = () => {
controller.abort();
};
return { fetchCancelable, cancel };
}
实际应用(React 组件中):
useEffect(() => {
const { fetchCancelable, cancel } = createCancelableFetch();
async function loadData() {
try {
const data = await fetchCancelable('/api/data');
setData(data);
} catch (error) {
if (error.name !== 'AbortError') {
console.error('加载失败:', error);
}
}
}
loadData();
// 组件卸载或依赖变化时取消请求
return () => cancel();
}, [userId]);
综合实战:封装通用请求函数
将以上技巧整合,封装一个生产级别的请求函数:
async function request(url, options = {}) {
const {
method = 'GET',
data = null,
timeout = 5000,
maxRetries = 3,
needAuth = true
} = options;
// 添加认证头
const headers = {
'Content-Type': 'application/json',
...options.headers
};
if (needAuth) {
const token = localStorage.getItem('token');
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
}
// 重试逻辑
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
const response = await fetch(url, {
method,
headers,
body: data ? JSON.stringify(data) : null,
signal: controller.signal
});
clearTimeout(timeoutId);
if (!response.ok) {
if (response.status === 401) {
// 未授权,跳转登录
window.location.href = '/login';
throw new Error('未授权');
}
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
if (error.name === 'AbortError') {
console.warn('请求已取消');
throw error;
}
if (attempt === maxRetries) {
console.error('请求失败:', error);
throw error;
}
const delay = 1000 * Math.pow(2, attempt - 1);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
使用示例:
const userData = await request('/api/user/profile');
const orders = await request('/api/orders', {
method: 'POST',
data: { productId: 123, quantity: 2 }
});
总结
-
串行依赖用 await,清晰直观
-
并行独立用 Promise.all,效率翻倍
-
超时控制用 Promise.race,防止卡死
-
网络不稳用指数退避重试,提高成功率
-
页面切换用 AbortController,避免泄漏
掌握这 5 个场景,你的异步代码将更加优雅、健壮、易维护。异步编程不再可怕,而是成为你的得力武器。
(完)