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

背景

在现代 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('所有请求失败');
        });
}
相关推荐
Nan_Shu_6147 分钟前
学习:uniapp全栈微信小程序vue3后台(28)
前端·学习·微信小程序·小程序·uni-app
珍宝商店17 分钟前
原生 JavaScript 方法实战指南
开发语言·前端·javascript
蓝莓味的口香糖27 分钟前
【企业微信】VUE项目在企微中自定义转发内容
前端·vue.js·企业微信
IT_陈寒27 分钟前
告别低效!用这5个Python技巧让你的数据处理速度提升300% 🚀
前端·人工智能·后端
—Qeyser29 分钟前
Laravel + UniApp AES加密/解密
前端·uni-app·laravel
C++chaofan32 分钟前
游标查询在对话历史场景下的独特优势
java·前端·javascript·数据库·spring boot
cg.family34 分钟前
Vue3 v-slot 详解与示例
前端·javascript·vue.js
FreeBuf_1 小时前
新型域名前置攻击利用Google Meet、YouTube、Chrome及GCP构建流量隧道
前端·chrome
c0detrend1 小时前
技术架构设计:如何打造一个高性能的Chrome截图插件
前端·chrome
幽络源小助理1 小时前
8、幽络源微服务项目实战:前端登录跨域同源策略处理+axios封装+权限的递归查询增删改+鉴权测试
前端·微服务·架构