从一个Bug谈前端响应拦截器的应用

一、问题场景

今天在开发商品管理系统时,遇到了一个有趣的问题:当添加重复的商品编号时,页面同时弹出了两条 "商品编号已存在" 错误提示:

这个问题暴露了前端错误处理机制的混乱,让我们从这个问题出发,深入了解响应拦截器的应用。

二、问题分析

原始代码

js 复制代码
// 响应拦截器
instance.interceptors.response.use(
    result => {
        if (result.data.code === 1) {
            return result.data;
        }
        ElMessage.error(result.data.msg || '服务异常');
        return Promise.reject(result.data);
    },
    err => {
        if(err.response.status === 401){
            ElMessage.error('请先登录!')
            router.push('/login')
        }else{
            ElMessage.error('服务异常');
        }
        return Promise.reject(err);
    }
)

// 业务代码
const saveProduct = async () => {
    try {
        // ... 表单验证等代码 ...
        const res = await productAddService(submitData)
        if (res.code === 1) {
            ElMessage.success('添加成功')
            // ... 其他成功处理 ...
        } else {
            ElMessage.error(res.msg || '添加失败')
        }
    } catch (error) {
        ElMessage.error(error.msg || '商品编号已存在')
    }
}

错误提示重复的原因

  1. 后端返回数据:{code: 0, msg: "商品编号已存在", data: null}
  2. 响应拦截器发现 code !== 1,显示错误消息并 reject
  3. 业务代码的 catch 块又显示了一次错误消息

三、解决方案

统一的响应拦截器处理

js 复制代码
// src/utils/request.js
instance.interceptors.response.use(
    result => {
        // 业务成功
        if (result.data.code === 1) {
            return result.data;
        }
        // 业务失败,统一处理错误提示
        ElMessage.error(result.data.msg || '操作失败');
        return Promise.reject(result.data);
    },
    err => {
        // 处理HTTP错误
        if(err.response.status === 401){
            ElMessage.error('请先登录!')
            router.push('/login')
        }else{
            ElMessage.error('服务异常');
        }
        return Promise.reject(err);
    }
)

简化业务代码

js 复制代码
const saveProduct = async () => {
    try {
        await productForm.value.validate()
        const submitData = {
            ...productModel.value,
            price: Number(productModel.value.price)
        }
        
        const service = isEditMode.value ? productEditService : productAddService
        const res = await service(submitData)
        
        // 只处理成功情况
        ElMessage.success(`${isEditMode.value ? '编辑' : '添加'}成功`)
        dialogVisible.value = false
        productList()
        resetProductModel()
    } catch (error) {
        // 错误已在拦截器中处理,这里不需要重复处理
        return
    }
}

四、响应拦截器

什么是响应拦截器

这里我进行一个类比,想象你在一个公司工作:

  • 你就是业务部门(前端业务代码)
  • 前台小姐姐就是响应拦截器
  • 各种快递就是服务器返回的数据

场景一:正常快递

快递 → 前台验收 → 签字 → 转交给你

对应代码:

js 复制代码
// 响应拦截器(前台小姐姐)
axios.interceptors.response.use(
    response => {
        if (response.data.code === 1) {  // 检查快递是否完好
            return response.data          // 转交给业务部门
        }
    }
)

// 业务代码(你)
const res = await getProductList()  // 直接收到处理好的快递
console.log(res.data)              // 使用快递内容

场景二:问题快递

坏快递 → 前台拒收 → 你完全不用处理

对应代码:

js 复制代码
// 响应拦截器(前台小姐姐)
axios.interceptors.response.use(
    response => {
        if (response.data.code !== 1) {  // 发现快递有问题
            ElMessage.error('快递有问题')  // 通知你快递有问题
            return Promise.reject()       // 直接拒收,不给你添麻烦
        }
    }
)

// 业务代码(你)
try {
    const res = await getProductList()
    // 只需要处理正常情况
} catch {
    // 问题已经被前台处理了,你不用管
}

响应拦截器就像这个"前台接待",它在服务器响应返回到我们的业务代码之前对所有响应进行统一的处理

为什么需要响应拦截器?

  1. 没有响应拦截器时:
js 复制代码
// 每个业务请求都需要重复处理这些情况
const getProductList = async () => {
    try {
        const res = await axios.get('/api/products')
        if (res.data.code === 1) {  // 成功
            return res.data.data
        } else if (res.data.code === 401) {  // 未登录
            ElMessage.error('请先登录')
            router.push('/login')
        } else {  // 其他错误
            ElMessage.error(res.data.msg)
        }
    } catch (error) {
        ElMessage.error('网络错误')
    }
}

const addProduct = async () => {
    try {
        const res = await axios.post('/api/product/add')
        // 又要重复上面的代码...
    } catch (error) {
        // 又要重复上面的代码...
    }
}
  1. 使用响应拦截器后:
js 复制代码
// 统一的响应处理
axios.interceptors.response.use(
    response => {
        // 统一处理成功和失败
        if (response.data.code === 1) {
            return response.data
        }
        // 统一处理未登录
        if (response.data.code === 401) {
            ElMessage.error('请先登录')
            router.push('/login')
            return Promise.reject(response.data)
        }
        // 统一处理错误提示
        ElMessage.error(response.data.msg)
        return Promise.reject(response.data)
    },
    error => {
        // 统一处理网络错误
        ElMessage.error('网络错误')
        return Promise.reject(error)
    }
)

// 业务代码变得简洁
const getProductList = async () => {
    try {
        const res = await axios.get('/api/products')
        return res.data  // 只需处理成功的情况
    } catch (error) {
        // 错误已经在拦截器中处理过了
    }
}

四、请求流程图

请求发起 → 请求拦截器 → 服务器 → 响应拦截器 → 业务代码

五、最佳实践总结

  1. 响应拦截器职责
    • 统一处理响应数据格式
    • 统一处理错误提示
    • 处理特殊状态码(如401未登录)
    • 转换服务端数据结构(如果需要)
  2. 业务代码职责
    • 关注业务逻辑
    • 处理成功场景
    • 可以选择性地处理特定错误
    • 不重复错误提示
  3. 错误处理原则
    • 统一入口处理错误
    • 避免重复提示
    • 提供清晰的错误信息
    • 合理使用 Promise.reject()

六、扩展应用

处理登录失效

js 复制代码
if (err.response.status === 401) {
    ElMessage.error('登录已过期,请重新登录')
    router.push('/login')
}

处理网络错误

js 复制代码
if (!error.response) {
    ElMessage.error('网络连接失败,请检查网络设置')
}

处理特定业务错误

js 复制代码
if (result.data.code === 100001) {
    // 处理特定业务错误码
    handleSpecialError(result.data)
    return Promise.reject(result.data)
}

七、总结

响应拦截器就像一个尽职尽责的前台,帮你处理了所有烦琐的检查工作,让我们可以专注于核心业务。

相关推荐
NoneCoder1 分钟前
CSS系列(29)-- Scroll Snap详解
前端·css
无言非影5 分钟前
vtie项目中使用到了TailwindCSS,如何打包成一个单独的CSS文件(优化、压缩)
前端·css
我曾经是个程序员31 分钟前
鸿蒙学习记录
开发语言·前端·javascript
羊小猪~~1 小时前
前端入门之VUE--ajax、vuex、router,最后的前端总结
前端·javascript·css·vue.js·vscode·ajax·html5
摸鱼了1 小时前
🚀 从零开始搭建 Vue 3+Vite+TypeScript+Pinia+Vue Router+SCSS+StyleLint+CommitLint+...项目
前端·vue.js
程序员shen1616111 小时前
抖音短视频saas矩阵源码系统开发所需掌握的技术
java·前端·数据库·python·算法
Ling_suu2 小时前
SpringBoot3——Web开发
java·服务器·前端
Yvemil72 小时前
《开启微服务之旅:Spring Boot Web开发》(二)
前端·spring boot·微服务
hanglove_lucky2 小时前
本地摄像头视频流在html中打开
前端·后端·html
维李设论2 小时前
Node.js的Web服务在Nacos中的实践
前端·spring cloud·微服务·eureka·nacos·node.js·express