从一个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)
}

七、总结

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

相关推荐
花生侠20 分钟前
记录:前端项目使用pnpm+husky(v9)+commitlint,提交代码格式化校验
前端
一涯28 分钟前
Cursor操作面板改为垂直
前端
我要让全世界知道我很低调35 分钟前
记一次 Vite 下的白屏优化
前端·css
1undefined236 分钟前
element中的Table改造成虚拟列表,并封装成hooks
前端·javascript·vue.js
蓝倾1 小时前
淘宝批量获取商品SKU实战案例
前端·后端·api
comelong1 小时前
Docker容器启动postgres端口映射失败问题
前端
花海如潮淹1 小时前
硬件产品研发管理工具实战指南
前端·python
用户3802258598241 小时前
vue3源码解析:依赖收集
前端·vue.js
WaiterL1 小时前
一文读懂 MCP 与 Agent
前端·人工智能·cursor