这个问题你一定见过:页面一进来打了十几个接口,其中好几个因为 401/500 挂了,然后 UI 像“放鞭炮”一样连弹十几个 Toast。
用户第一反应不是“哦有些接口失败了”,而是“这页面炸了”。所以更合理的体验是:短时间内同类错误合并提示,最多弹一次(或者合并成一个更清晰的提示)。
实现思路
- 维护一个全局状态,用于记录是否已经弹出 Toast。
- 使用节流函数(
throttle)或定时器,确保短时间内多个请求失败时,仅触发一次 Toast。
- 支持批量请求的全局错误拦截,如拦截 Axios 的响应错误,或者在 Fetch API 中封装统一的错误处理。
具体实现
1. 使用全局状态记录 Toast
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| import axios from "axios"; import { message } from "antd";
let toastShown = false;
function showGlobalToast(msg: string) { if (!toastShown) { toastShown = true; message.error(msg); setTimeout(() => { toastShown = false; }, 3000); } }
axios.interceptors.response.use( (response) => response, (error) => { showGlobalToast("请求失败,请稍后重试"); return Promise.reject(error); } );
async function fetchBatchData() { try { await Promise.all([ axios.get("/api/data1"), axios.get("/api/data2"), axios.get("/api/data3"), ]); } catch (error) { console.error("批量请求发生错误", error); } }
|
- 全局状态
toastShown:用来记录当前是否已经有 Toast 被弹出,避免在多个请求失败时重复弹出提示。
showGlobalToast 方法:在 Toast 未显示的情况下调用 message.error 弹出提示,并设置一个 3 秒的定时器,在定时器到期后将 toastShown 重置为 false,这样在下一次错误时可以重新弹出提示。
- Axios 拦截器:捕获所有响应错误,并调用
showGlobalToast 方法,确保全局只有一次 Toast 弹出。
2. 使用 throttle 限制 Toast 触发频率
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| import axios from "axios"; import { message } from "antd"; import throttle from "lodash/throttle";
const showToast = throttle( (msg: string) => { message.error(msg); }, 3000, { leading: true, trailing: false } );
axios.interceptors.response.use( (response) => response, (error) => { showToast("请求失败,请稍后重试"); return Promise.reject(error); } );
async function fetchBatchData() { try { await Promise.all([ axios.get("/api/data1"), axios.get("/api/data2"), axios.get("/api/data3"), ]); } catch (error) { console.error("批量请求发生错误", error); } }
|
throttle 确保 showToast 在 3 秒内最多执行一次,即使多个请求失败,也只会弹出一个 Toast。
优化
如何只限制文案相同的 Toast
1 2 3 4 5 6 7 8 9 10 11 12 13
| const messageTextSet = new Set();
const showToast = (msg: string) => { if (!messageTextSet.has(msg)) { messageTextSet.add(msg); message.error(msg); setTimeout(() => { messageTextSet.delete(msg); }, 3000); } }
|
思路很简单:把“正在提示中的文案”放到一个集合里,命中就不弹;过一段时间再释放,允许后续再次提示。
实际项目里你还可以把 key 做得更精细一点,比如按 errorCode/status/接口域名 合并,而不是只按文案合并,这样提示会更准确。
Happy Coding!