当部分请求失败时,前端如何保证用户体验不崩溃?

背景

在现代 Web 开发中,前端通常需要向服务器发送多个异步请求(如批量数据获取、并行 API 调用等)。然而,由于网络波动、服务器错误或接口限制,部分或全部请求可能会失败。

常见问题

  1. 批量请求失败时,如何避免重复弹窗?

    • 如果每个失败的请求都单独弹出一个错误提示(如 Toast),会导致短时间内出现多个弹窗,影响用户体验。
    • 例如:用户点击"批量删除"按钮,发送 10 个删除请求,其中 5 个失败。如果每个错误都弹窗,用户会看到多个重复的提示。
  2. 如何优化错误提示策略?

    • 单次提示:无论有多少个请求失败,只弹出一个 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('所有请求失败');
        });
}
相关推荐
袁煦丞21 分钟前
DS file文件管家远程自由:cpolar内网穿透实验室第492个成功挑战
前端·程序员·远程工作
用户0137412843722 分钟前
九个鲜为人知却极具威力的 CSS 功能:提升前端开发体验的隐藏技巧
前端
永远不打烊25 分钟前
Window环境 WebRTC demo 运行
前端
风舞27 分钟前
一文搞定JS所有类型判断最佳实践
前端·javascript
coding随想27 分钟前
哈希值变化的魔法:深入解析HTML5 hashchange事件的奥秘与实战
前端
一树山茶34 分钟前
uniapp在微信小程序中实现 SSE进行通信
前端·javascript
coding随想34 分钟前
小程序中的pageshow与pagehide事件,HTML5中也有?揭秘浏览器往返缓存(BFCache)
前端
SimonKing35 分钟前
想搭建知识库?Dify、MaxKB、Pandawiki 到底哪家强?
java·后端·程序员
萌萌哒草头将军40 分钟前
Rspack 1.5 版本更新速览!🚀🚀🚀
前端·javascript·vue.js
阿卡不卡44 分钟前
基于多场景的通用单位转换功能实现
前端·javascript