背景
在现代 Web 开发中,前端通常需要向服务器发送多个异步请求(如批量数据获取、并行 API 调用等)。然而,由于网络波动、服务器错误或接口限制,部分或全部请求可能会失败。
常见问题
-
批量请求失败时,如何避免重复弹窗?
- 如果每个失败的请求都单独弹出一个错误提示(如 Toast),会导致短时间内出现多个弹窗,影响用户体验。
- 例如:用户点击"批量删除"按钮,发送 10 个删除请求,其中 5 个失败。如果每个错误都弹窗,用户会看到多个重复的提示。
-
如何优化错误提示策略?
- 单次提示:无论有多少个请求失败,只弹出一个 Toast(如"部分操作失败"或"网络异常")。
- 智能合并错误:如果多个请求失败,可以合并错误信息(如"3/10 个操作失败")。
适用场景
- 批量操作(如批量删除、批量上传)
- 并行数据加载(如同时加载多个模块的数据)
- 接口轮询/重试(如多个接口轮询时统一处理错误)
目标
设计一个方案,确保在批量请求失败时: ✅ 只弹出一个 Toast (避免重复提示) ✅ 合理合并错误信息 (如统计失败数量) ✅ 不影响正常业务逻辑(错误仍能被捕获和处理)
介绍几种实现方案
1. 使用全局标志位
通过设置一个布尔变量 isToastShown
来标记是否已弹出 Toast。在所有请求完成后,检查该变量,如果未弹出,则弹出一个 Toast 并设置标志位为 true
。这种方法适用于简单的单次通知场景。
示例代码:
ini
let isToastShown = false;
function fetchJSON(url) {
return fetch(url)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.catch(error => {
if (!isToastShown) {
showToast('请求失败');
isToastShown = true;
}
throw error;
});
}
function makeRequests(urls) {
Promise.all(urls.map(fetchJSON))
.catch(() => {
if (!isToastShown) {
showToast('所有请求失败');
isToastShown = true;
}
});
}
2. 使用防抖函数
通过创建一个防抖函数 debounce
,限制通知函数的执行频率,确保在一定时间内只执行一次通知。这种方法适用于需要限制通知频率的场景。
示例代码:
javascript
function debounce(fn, delay) {
let timer;
return function(...args) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
const showToastDebounced = debounce(showToast, 3000);
function fetchJSON(url) {
return fetch(url)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.catch(error => {
showToastDebounced('请求失败');
throw error;
});
}
function makeRequests(urls) {
Promise.all(urls.map(fetchJSON))
.catch(() => {
showToastDebounced('所有请求失败');
});
}
3. 使用计数器
通过维护一个计数器来跟踪未处理的请求数量。每当发起一个请求,计数器加一;请求完成时,无论成功或失败,计数器减一。在请求失败时,先检查计数器值,如果仍大于零,则不弹出新的 Toast;只有当计数器归零时,才弹出一个 Toast 提示请求失败。这种方法适用于批量请求的错误统一管理。
示例代码:
ini
let pendingRequests = 0;
function fetchJSON(url) {
pendingRequests++;
return fetch(url)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.catch(error => {
pendingRequests--;
if (pendingRequests === 0) {
showToast('所有请求失败');
}
throw error;
})
.finally(() => {
pendingRequests--;
if (pendingRequests === 0) {
showToast('所有请求失败');
}
});
}
function makeRequests(urls) {
urls.forEach(url => fetchJSON(url));
}
4. 使用 Promise.allSettled
使用 Promise.allSettled
来处理批量请求,然后检查结果中是否有错误。如果有错误,只弹出第一个错误的 Toast。这种方法适用于批量请求的错误统一管理。
示例代码:
javascript
function fetchJSON(url) {
return fetch(url)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
});
}
function makeRequests(urls) {
Promise.allSettled(urls.map(fetchJSON))
.then(results => {
const hasError = results.some(result => result.status === 'rejected');
if (hasError) {
showToast('所有请求失败');
}
});
}
5. 使用 Toast 组件的单例模式
许多前端框架和库中的 Toast 组件默认采用单例模式,即同一时间只会存在一个 Toast。如果需要在同一时间弹出多个 Toast,可以参考相应文档进行配置。这种方法适用于需要避免重复弹出 Toast 的场景。
示例代码(Vue.js 中使用 Vant Toast 组件):
javascript
import { showToast } from 'vant';
function fetchJSON(url) {
return fetch(url)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.catch(error => {
showToast('请求失败');
throw error;
});
}
function makeRequests(urls) {
Promise.all(urls.map(fetchJSON))
.catch(() => {
showToast('所有请求失败');
});
}