一、问题场景
今天在开发商品管理系统时,遇到了一个有趣的问题:当添加重复的商品编号时,页面同时弹出了两条 "商品编号已存在"
错误提示:
这个问题暴露了前端错误处理机制的混乱,让我们从这个问题出发,深入了解响应拦截器
的应用。
二、问题分析
原始代码
// 响应拦截器
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 || '商品编号已存在')
}
}
错误提示重复的原因
- 后端返回数据:{code: 0, msg: “商品编号已存在”, data: null}
- 响应拦截器发现 code !== 1,显示错误消息并 reject
- 业务代码的 catch 块又显示了一次错误消息
三、解决方案
统一的响应拦截器处理
// 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);
}
)
简化业务代码
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
}
}
四、响应拦截器
什么是响应拦截器
这里我进行一个类比,想象你在一个公司工作:
- 你就是业务部门(前端业务代码)
- 前台小姐姐就是响应拦截器
- 各种快递就是服务器返回的数据
场景一:正常快递
快递 → 前台验收 → 签字 → 转交给你
对应代码:
// 响应拦截器(前台小姐姐)
axios.interceptors.response.use(
response => {
if (response.data.code === 1) { // 检查快递是否完好
return response.data // 转交给业务部门
}
}
)
// 业务代码(你)
const res = await getProductList() // 直接收到处理好的快递
console.log(res.data) // 使用快递内容
场景二:问题快递
坏快递 → 前台拒收 → 你完全不用处理
对应代码:
// 响应拦截器(前台小姐姐)
axios.interceptors.response.use(
response => {
if (response.data.code !== 1) { // 发现快递有问题
ElMessage.error('快递有问题') // 通知你快递有问题
return Promise.reject() // 直接拒收,不给你添麻烦
}
}
)
// 业务代码(你)
try {
const res = await getProductList()
// 只需要处理正常情况
} catch {
// 问题已经被前台处理了,你不用管
}
响应拦截器就像这个"前台接待",它在服务器响应返回到我们的业务代码之前,对所有响应进行统一的处理
。
为什么需要响应拦截器?
- 没有响应拦截器时:
// 每个业务请求都需要重复处理这些情况
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) {
// 又要重复上面的代码...
}
}
- 使用响应拦截器后:
// 统一的响应处理
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) {
// 错误已经在拦截器中处理过了
}
}
四、请求流程图
请求发起 → 请求拦截器 → 服务器 → 响应拦截器
→ 业务代码
五、最佳实践总结
- 响应拦截器职责
- 统一处理响应数据格式
- 统一处理错误提示
- 处理特殊状态码(如401未登录)
- 转换服务端数据结构(如果需要)
- 业务代码职责
- 关注业务逻辑
- 处理成功场景
- 可以选择性地处理特定错误
- 不重复错误提示
- 错误处理原则
- 统一入口处理错误
- 避免重复提示
- 提供清晰的错误信息
- 合理使用 Promise.reject()
六、扩展应用
处理登录失效
if (err.response.status === 401) {
ElMessage.error('登录已过期,请重新登录')
router.push('/login')
}
处理网络错误
if (!error.response) {
ElMessage.error('网络连接失败,请检查网络设置')
}
处理特定业务错误
if (result.data.code === 100001) {
// 处理特定业务错误码
handleSpecialError(result.data)
return Promise.reject(result.data)
}
七、总结
响应拦截器就像一个尽职尽责的前台,帮你处理了所有烦琐的检查工作,让我们可以专注于核心业务。