你的 `Promise.then` 可能白写了,新语法让异步代码更优雅
优雅处理异步操作:告别Promise.then的3种新方案 传统的Promise.then写法存在三大痛点:错误处理不精确、无法并行执行异步操作、流程控制困难。本文介绍了三种更优雅的解决方案: async/await - 让异步代码像同步代码一样清晰易读,通过try-catch实现精确的错误处理 Promise.all - 并行执行多个异步操作,大幅提升执行效率 现代Promise API - 包
·
你的 Promise.then 可能白写了,新语法让异步代码更优雅
先看这段代码,你是不是也这样写过?
// 经典的 Promise.then 链式调用
fetch('/api/user/1')
.then(response => response.json())
.then(user => {
return fetch(`/api/posts?userId=${user.id}`);
})
.then(response => response.json())
.then(posts => {
return fetch(`/api/comments?postId=${posts[0].id}`);
})
.then(response => response.json())
.then(comments => {
console.log('用户的第一篇文章的评论:', comments);
})
.catch(error => {
console.error('出错了:', error);
})
.finally(() => {
console.log('请求结束');
});
这种代码被称为"回调地狱的现代版"——Promise 地狱。虽然比传统回调好一些,但依然有这几个问题:
- 可读性差:需要一层层理解每个
.then在做什么 - 错误处理麻烦:一个
.catch处理所有错误,难以定位问题 - 调试困难:断点设置在哪里?错误发生在哪一步?
- 代码冗长:为了几行逻辑,写了十几行代码
今天我要告诉你:你完全可以写出更优雅的异步代码。
Promise.then 的 3 大痛点
1. 错误处理不精确
// ❌ 一个 catch 处理所有错误
fetch('/api/user')
.then(response => response.json())
.then(user => updateUI(user))
.then(() => fetch('/api/posts'))
.then(response => response.json())
.then(posts => renderPosts(posts))
.catch(error => {
// 哪个步骤出错了?不知道!
console.error('出错了:', error);
});
// 实际开发中,我们经常需要:
// 1. 记录哪个步骤出错
// 2. 某些错误可以继续执行
// 3. 给用户不同的错误提示
2. 无法同时执行多个异步操作
// ❌ 顺序执行,效率低下
fetch('/api/user')
.then(user => {
return fetch('/api/posts'); // 等用户信息返回后再请求文章
})
.then(posts => {
return fetch('/api/comments'); // 等文章返回后再请求评论
});
// 总共耗时 = 三个请求时间之和
// 这三个请求明明可以同时发出!
3. 流程控制困难
// ❌ 复杂的条件判断让代码混乱
let userData;
fetch('/api/user')
.then(user => {
userData = user;
if (user.role === 'admin') {
return fetch('/api/admin-data');
} else {
return fetch('/api/user-data');
}
})
.then(data => {
// 这里需要知道 data 是 admin-data 还是 user-data
// 还需要访问 userData 变量
return processData(userData, data);
})
.then(result => {
// 更复杂的分支...
});
// 变量在 Promise 链外部声明,破坏了封装性
新方案:async/await + 现代 Promise API
方案1:async/await - 让异步代码像同步一样
// ✅ 用 async/await 重写上面的例子
async function getUserComments() {
try {
// 1. 获取用户信息
const userResponse = await fetch('/api/user/1');
const user = await userResponse.json();
// 2. 获取用户的文章
const postsResponse = await fetch(`/api/posts?userId=${user.id}`);
const posts = await postsResponse.json();
// 3. 获取第一篇文章的评论
const commentsResponse = await fetch(`/api/comments?postId=${posts[0].id}`);
const comments = await commentsResponse.json();
console.log('用户的第一篇文章的评论:', comments);
return comments;
} catch (error) {
console.error('出错了:', error);
throw error;
} finally {
console.log('请求结束');
}
}
// 使用
getUserComments();
对比一下:
- 之前:7 个
.then,嵌套 4 层,需要横向滚动阅读 - 之后:顺序执行,清晰明了,像写同步代码一样
方案2:并行执行 - 用 Promise.all 加速
// ❌ 顺序执行(慢)
async function fetchDataSequentially() {
const user = await fetch('/api/user'); // 等待 100ms
const posts = await fetch('/api/posts'); // 等待 200ms
const comments = await fetch('/api/comments'); // 等待 150ms
// 总时间:100 + 200 + 150 = 450ms
}
// ✅ 并行执行(快)
async function fetchDataParallel() {
// 三个请求同时发出
const [user, posts, comments] = await Promise.all([
fetch('/api/user').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
fetch('/api/comments').then(r => r.json())
]);
// 总时间:max(100, 200, 150) = 200ms
// 快了 125%!
return { user, posts, comments };
}
方案3:更灵活的并发控制
// ✅ Promise.allSettled - 所有请求都完成,不管成功失败
async function fetchMultipleData(urls) {
const promises = urls.map(url => fetch(url).then(r => r.json()));
const results = await Promise.allSettled(promises);
// 分类处理结果
const successful = results.filter(r => r.status === 'fulfilled');
const failed = results.filter(r => r.status === 'rejected');
console.log(`成功: ${successful.length}, 失败: ${failed.length}`);
return {
data: successful.map(r => r.value),
errors: failed.map(r => r.reason)
};
}
// ✅ Promise.race - 哪个先完成就用哪个(超时控制)
async function fetchWithTimeout(url, timeout = 5000) {
const fetchPromise = fetch(url).then(r => r.json());
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('请求超时')), timeout);
});
// 谁先完成就用谁的结果
return await Promise.race([fetchPromise, timeoutPromise]);
}
// ✅ Promise.any - 只要有一个成功就返回(类似竞速)
async function fetchFromFastestServer(servers) {
const promises = servers.map(server =>
fetch(server).then(r => r.json())
);
try {
// 只要有一个成功就返回,忽略所有失败
const result = await Promise.any(promises);
return result;
} catch (error) {
// 所有请求都失败了
console.error('所有服务器都失败了:', error.errors);
throw error;
}
}
实战:重构真实业务代码
场景1:电商购物车结算流程
// ❌ Promise.then 版本(混乱)
function checkoutOld(userId, cartItems) {
let userInfo, inventoryStatus, shippingOptions;
getUser(userId)
.then(user => {
userInfo = user;
return checkInventory(cartItems);
})
.then(inventory => {
inventoryStatus = inventory;
return calculateShipping(userInfo.address, cartItems);
})
.then(shipping => {
shippingOptions = shipping;
return calculateTotal(cartItems, shippingOptions);
})
.then(total => {
return processPayment(userInfo.paymentMethod, total);
})
.then(paymentResult => {
return createOrder(userInfo, cartItems, inventoryStatus, shippingOptions, paymentResult);
})
.then(order => {
sendConfirmationEmail(userInfo.email, order);
return order;
})
.catch(error => {
console.error('结算失败:', error);
// 哪个步骤出错了?很难知道!
});
}
// ✅ async/await 版本(清晰)
async function checkoutNew(userId, cartItems) {
try {
// 1. 获取用户信息
const user = await getUser(userId);
// 2. 并行检查库存和计算运费(提高速度)
const [inventory, shippingOptions] = await Promise.all([
checkInventory(cartItems),
calculateShipping(user.address, cartItems)
]);
// 3. 如果有缺货商品,提前返回
const outOfStockItems = inventory.filter(item => !item.inStock);
if (outOfStockItems.length > 0) {
throw new Error(`以下商品缺货: ${outOfStockItems.map(i => i.name).join(', ')}`);
}
// 4. 计算总价并处理支付
const total = calculateTotal(cartItems, shippingOptions);
const paymentResult = await processPayment(user.paymentMethod, total);
// 5. 创建订单
const order = await createOrder(user, cartItems, inventory, shippingOptions, paymentResult);
// 6. 发送确认邮件(不阻塞主流程)
sendConfirmationEmail(user.email, order).catch(emailError => {
console.warn('邮件发送失败,但不影响订单:', emailError);
});
return order;
} catch (error) {
// 精确的错误处理
if (error.message.includes('缺货')) {
console.error('库存问题:', error.message);
throw new Error('CART_OUT_OF_STOCK');
} else if (error.message.includes('支付失败')) {
console.error('支付问题:', error.message);
throw new Error('PAYMENT_FAILED');
} else {
console.error('结算流程出错:', error);
throw new Error('CHECKOUT_FAILED');
}
}
}
场景2:分页加载数据
// ❌ 传统方式:一次加载所有页
async function loadAllPagesBadly(totalPages) {
const allData = [];
for (let page = 1; page <= totalPages; page++) {
const data = await fetch(`/api/data?page=${page}`).then(r => r.json());
allData.push(...data.items);
}
return allData;
// 问题:顺序执行,很慢!
// 100页 × 200ms = 20秒!
}
// ✅ 现代方式:并发控制加载
async function loadAllPagesSmartly(totalPages, concurrency = 5) {
const allData = [];
// 创建所有页面的请求
const pagePromises = [];
for (let page = 1; page <= totalPages; page++) {
pagePromises.push(
fetch(`/api/data?page=${page}`).then(r => r.json())
);
}
// 分批并发执行
for (let i = 0; i < pagePromises.length; i += concurrency) {
const batch = pagePromises.slice(i, i + concurrency);
const batchResults = await Promise.all(batch);
// 收集数据
batchResults.forEach(result => {
allData.push(...result.items);
});
// 显示进度
const progress = Math.min(i + concurrency, pagePromises.length);
console.log(`加载进度: ${progress}/${pagePromises.length}`);
}
return allData;
// 优点:5个请求并发,100页只需要 20 批
// 100页 ÷ 5并发 = 20批 × 200ms = 4秒!
}
// ✅ 更优雅的版本:使用 async 池
async function asyncPool(poolLimit, array, iteratorFn) {
const ret = [];
const executing = [];
for (const item of array) {
const p = Promise.resolve().then(() => iteratorFn(item));
ret.push(p);
if (poolLimit <= array.length) {
const e = p.then(() => executing.splice(executing.indexOf(e), 1));
executing.push(e);
if (executing.length >= poolLimit) {
await Promise.race(executing);
}
}
}
return Promise.all(ret);
}
// 使用 async 池加载
async function loadWithPool(totalPages, poolSize = 5) {
const pages = Array.from({ length: totalPages }, (_, i) => i + 1);
const results = await asyncPool(poolSize, pages, async (page) => {
const response = await fetch(`/api/data?page=${page}`);
return response.json();
});
return results.flatMap(r => r.items);
}
场景3:带重试机制的请求
// ❌ 传统的重试逻辑(复杂)
function fetchWithRetryOld(url, retries = 3) {
return fetch(url)
.then(response => {
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
})
.catch(error => {
if (retries > 0) {
console.log(`重试剩余次数: ${retries}`);
return fetchWithRetryOld(url, retries - 1);
} else {
throw error;
}
});
}
// ✅ 现代的 async/await 重试(清晰)
async function fetchWithRetry(url, retries = 3, delay = 1000) {
for (let attempt = 1; attempt <= retries; attempt++) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
console.log(`请求失败 (尝试 ${attempt}/${retries}):`, error.message);
if (attempt === retries) {
throw new Error(`请求失败,已重试 ${retries} 次: ${error.message}`);
}
// 指数退避策略
const waitTime = delay * Math.pow(2, attempt - 1);
console.log(`等待 ${waitTime}ms 后重试...`);
await sleep(waitTime);
}
}
}
// ✅ 更高级的:带超时和重试的组合
async function robustFetch(url, options = {}) {
const { timeout = 10000, retries = 3, retryDelay = 1000 } = options;
for (let attempt = 1; attempt <= retries; attempt++) {
try {
// 创建超时控制器
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
const response = await fetch(url, {
...options,
signal: controller.signal
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
console.log(`请求失败 (尝试 ${attempt}/${retries}):`, error.name, error.message);
if (attempt === retries) {
throw new Error(`请求最终失败: ${error.message}`);
}
// 如果是中止错误(超时),立即重试
if (error.name === 'AbortError') {
console.log('请求超时,立即重试...');
} else {
// 其他错误,使用指数退避
const waitTime = retryDelay * Math.pow(2, attempt - 1);
await sleep(waitTime);
}
}
}
}
// 辅助函数:等待指定时间
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
高级技巧:让异步代码更强大
1. 错误边界模式
// 创建错误边界,让部分错误不影响整体
async function withErrorBoundary(promise, fallbackValue = null) {
try {
return await promise;
} catch (error) {
console.error('操作失败,使用回退值:', error.message);
return fallbackValue;
}
}
// 使用
async function loadDashboard() {
const [user, notifications, recommendations] = await Promise.all([
withErrorBoundary(fetchUser(), { name: '访客' }),
withErrorBoundary(fetchNotifications(), []),
withErrorBoundary(fetchRecommendations(), [])
]);
// 即使某个请求失败,其他数据也能正常显示
return { user, notifications, recommendations };
}
2. 进度追踪
// 追踪多个异步操作的进度
async function withProgress(tasks, onProgress) {
let completed = 0;
const total = tasks.length;
// 包装每个任务,完成后更新进度
const wrappedTasks = tasks.map(task => async () => {
const result = await task();
completed++;
if (onProgress) {
onProgress({
completed,
total,
percent: Math.round((completed / total) * 100)
});
}
return result;
});
// 并行执行所有任务
const results = await Promise.all(wrappedTasks.map(t => t()));
return results;
}
// 使用
const tasks = [
() => fetch('/api/data1').then(r => r.json()),
() => fetch('/api/data2').then(r => r.json()),
() => fetch('/api/data3').then(r => r.json())
];
const results = await withProgress(tasks, ({ percent }) => {
console.log(`加载进度: ${percent}%`);
});
3. 竞态条件防护
// 防止多次点击导致的重复请求
function createRequestLock() {
let currentRequest = null;
return async function(requestFn) {
// 如果已经有请求在进行,返回同一个 Promise
if (currentRequest) {
console.log('请求已在进行中,等待结果...');
return currentRequest;
}
try {
currentRequest = requestFn();
return await currentRequest;
} finally {
currentRequest = null;
}
};
}
// 使用
const lockedFetch = createRequestLock();
// 多次快速调用只会执行一次
button.addEventListener('click', async () => {
const data = await lockedFetch(() =>
fetch('/api/data').then(r => r.json())
);
console.log('数据:', data);
});
什么时候还需要 Promise.then?
虽然 async/await 很好,但在某些场景下,.then() 仍然有用:
1. 简单的链式调用
// 当只有一两个步骤时,.then() 更简洁
fetch('/api/data')
.then(response => response.json())
.then(data => console.log(data));
// 如果用 async/await,需要多写几行
async function fetchData() {
const response = await fetch('/api/data');
const data = await response.json();
console.log(data);
}
2. 需要立即执行,不等待
// 使用 .then() 可以立即返回,不阻塞
function fetchDataAndDoSomething() {
// 立即返回 Promise,不等待
return fetch('/api/data')
.then(response => response.json())
.then(data => {
// 这里会在数据返回后执行
processData(data);
return data;
});
// 后面的代码会立即执行
console.log('请求已发出,继续执行其他代码...');
}
3. 在非 async 函数中处理 Promise
// 在类方法或构造函数中
class DataLoader {
constructor() {
this.data = null;
// 不能在构造函数中用 await
this.loadData()
.then(data => {
this.data = data;
})
.catch(error => {
console.error('加载失败:', error);
});
}
loadData() {
return fetch('/api/data').then(r => r.json());
}
}
性能对比
可读性对比
// .then() 链
fetchA()
.then(a => fetchB(a))
.then(b => fetchC(b))
.then(c => fetchD(c))
.then(d => console.log(d))
.catch(e => console.error(e));
// async/await
try {
const a = await fetchA();
const b = await fetchB(a);
const c = await fetchC(b);
const d = await fetchD(c);
console.log(d);
} catch (e) {
console.error(e);
}
// 明显后者更易读
错误处理对比
// .then() 的错误处理分散
fetchA()
.then(a => {
return fetchB(a).catch(e => ({ error: 'B失败', fallback: true }));
})
.then(b => {
if (b.error) {
return fetchC2();
}
return fetchC(b);
})
.catch(e => console.error('最终错误:', e));
// async/await 的错误处理集中
try {
const a = await fetchA();
let b;
try {
b = await fetchB(a);
} catch {
b = { fallback: true };
}
const c = b.fallback ? await fetchC2() : await fetchC(b);
} catch (e) {
console.error('错误:', e);
}
迁移指南:从 Promise.then 到 async/await
步骤1:识别可以简化的 Promise 链
// 查找多层 .then()
grep -r "\.then.*\.then" src/ --include="*.js" --include="*.ts"
步骤2:逐步重构
// 之前
function getUserData(userId) {
return fetch(`/api/users/${userId}`)
.then(response => response.json())
.then(user => {
return fetch(`/api/posts?userId=${user.id}`);
})
.then(response => response.json())
.then(posts => {
return { user: userId, posts };
});
}
// 之后
async function getUserData(userId) {
const userResponse = await fetch(`/api/users/${userId}`);
const user = await userResponse.json();
const postsResponse = await fetch(`/api/posts?userId=${user.id}`);
const posts = await postsResponse.json();
return { user: userId, posts };
}
步骤3:并行优化
// 识别可以并行的操作
// 之前
async function getDashboardData() {
const user = await getUser();
const notifications = await getNotifications(); // 等待用户数据后才执行
const messages = await getMessages(); // 等待通知后才执行
return { user, notifications, messages };
}
// 之后
async function getDashboardData() {
const [user, notifications, messages] = await Promise.all([
getUser(),
getNotifications(),
getMessages()
]);
return { user, notifications, messages };
}
最后的建议
记住这些最佳实践:
- 默认使用 async/await:让代码更清晰
- 并行能用就用:用
Promise.all加速 - 错误处理要具体:不要一个
catch抓所有 - 适当使用现代 API:
Promise.allSettled、Promise.any等
今天就可以开始的 3 件事:
-
找一个复杂的 Promise 链重写
// 选一个多层 .then() 的函数 // 用 async/await 重写,体验清晰度的提升 -
检查项目中的顺序请求
// 找出可以并行执行的请求 // 用 Promise.all 改造,体验速度的提升 -
统一错误处理模式
// 创建一个错误处理工具函数 // 让所有异步操作都有统一的错误处理
记住这个进化路线:
回调地狱 → Promise.then → async/await → 现代并发模式
从今天开始,告别冗长的 .then() 链。你会发现,异步代码不仅可以很强大,还可以很优雅。
好的异步代码应该像流水一样自然,而不是像迷宫一样复杂。 用对工具,你也能写出这样的代码。
更多推荐



所有评论(0)