Vue 中统一处理 ajax 请求错误

通常说, ajax 请求错误有两种, 一种是网络问题或者代码问题所造成的 400, 500错误等, 另外一种是请求参数后端通不过验证, 由后端抛出的错误

第二种根据不同的后端框架或者程序猿又可以分成两种, 一种是直接返回 json, 用一个 特别的 code 来区别正常请求返回的数据, 如:

{
  code: -404,
  message: '这是错误信息',
  data: '',
}

还有一种就是抛出 http 404 之类的, 然后把错误原因放在 header 里.

在组件写调用 ajax时, 通常都是这么写(这里以 axios 为例):

import axios from 'axios'
axios.get('/user?ID=12345')
  .then(function (response) {
    if (response.data.code === 200) {
        console.log(response.data)
    } else {
        // 由后端抛出的错误
        alert(response.data.message)
    }
  }).catch(function (error) {
       // 由网络或者服务器抛出的错误
     alert(error.toString())
  })


随着请求越来越多, 这么写也就显得麻烦…所以我们需要封装下, 做一些预处理
下面代码以 axios 为例:

1. 创建 api.js 文件

import axios from 'axios'
import qs from 'qs'

引入我们需要的两个库, 为什么需要用到 qs, 后面会说到

2. 利用拦截器做预处理

请求时的拦截器

axios.interceptors.request.use(config => {
    // 这里可以加一些动作, 比如来个进度条开始动作,
    NProgress.start()
    return config
}, error => {
    return Promise.reject(error)
})

请求完成后的拦截器

axios.interceptors.response.use(response => {
    return response
}, error => {
    // 这里我们把错误信息扶正, 后面就不需要写 catch 了
    return Promise.resolve(error.response)
})

这里的return response返回的是一个对象, 内容如下:

{
  // 服务器提供的响应
  data: {},
  // 服务器响应的HTTP状态代码
  status: 200,
  // 服务器响应的HTTP状态消息
  statusText: 'OK',
  // 服务器响应头
  headers: {},
  // axios 的配置
  config: {}
}

做 api 封装

这里一般封装两种方法, 一个是 get 请求, 一个是 post 请求, 有其他情况也可以自行添加

export default {
    post(url, data) {
        return axios({
            method: 'post', // 请求协议
            url: url, // 请求的地址
            data: qs.stringify(data), // post 请求的数据
            timeout: 30000, // 超时时间, 单位毫秒
            headers: {
                'X-Requested-With': 'XMLHttpRequest',
                'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
            }
        })
    },
    get(url, params) {
        return axios({
            method: 'get',
            url: url,
            params, // get 请求时带的参数
            timeout: 30000,
            headers: {
                'X-Requested-With': 'XMLHttpRequest'
            }
        })
    }
}

这里的 data 为什么需要用qs.stringify(data)包一下, 主要是配合下面headers里的Content-Type, 转成表单提交, 让后端可以直接用 $_POST 拿到数据
这样, 一个大概的封装就完成了

4. 数据统一处理

我们先处理来自网络或者服务器的错误, 定义一个checkStatus函数

function checkStatus(response) {
    // 这里可以加一些动作, 比如来个进度条结束动作
    NProgress.done()
    // 如果 http 状态码正常, 则直接返回数据
    if (response.status === 200 || response.status === 304) {
        return response
        // 这里, 如果不需要除 data 外的其他数据, 可以直接 return response.data, 这样可以让后面的代码精简一些
    }
    // 异常状态下, 把错误信息返回去
    // 因为前面我们把错误扶正了, 不然像 404, 500 这样的错误是走不到这里的
    return {
        data: {
            code: -404,
            message: response.statusText,
            data: response.statusText,
        }
    }
    // 如果上面你 return 的是 response.data, 那么这里可以写成
    // return {
    //    code: -404,
    //    message: response.statusText,
    //    data: response.statusText,
    //}
}

再来处理来自程序端的错误, 创建一个checkCode的函数

function checkCode(res) {
    // 如果状态 code 异常(这里已经包括网络错误, 服务器错误, 后端抛出的错误), 可以弹出一个错误提示, 告诉用户
    if (res.data.code !== 200) { // 或者是res.code, 视上面你返回的数据来决定
        alert(res.data.message)
    }
    return res
}

5. 最终代码

import axios from 'axios'
import qs from 'qs'
import NProgress from 'nprogress'

axios.interceptors.request.use(config => {
    NProgress.start()
    return config
}, error => {
    return Promise.reject(error)
})

axios.interceptors.response.use(response => response, error => Promise.resolve(error.response))

function checkStatus(response) {
    NProgress.done()
    if (response.status === 200 || response.status === 304) {
        return response
    }
    return {
        data: {
            code: -404,
            message: response.statusText,
            data: response.statusText,
        }
    }
}

function checkCode(res) {
    if (res.data.code !== 200) {
        alert(res.data.message)
    }
    return res
}

export default {
    post(url, data) {
        return axios({
            method: 'post',
            url,
            data: qs.stringify(data),
            timeout: 30000,
            headers: {
                'X-Requested-With': 'XMLHttpRequest',
                'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
            }
        }).then(checkStatus).then(checkCode)
    },
    get(url, params) {
        return axios({
            method: 'get',
            url,
            params,
            timeout: 30000,
            headers: {
                'X-Requested-With': 'XMLHttpRequest'
            }
        }).then(checkStatus).then(checkCode)
    }
}

6. 在组件中使用

import api from '../api.js' // 改成对应的路径
export default {
  async mounted() {
    const { data: { code, data }} = await api.post('/api/comment/post', {title: 'title'})
    if (code === 200) {
        console.log(data)
    }
    const { data: { code, data }} = await api.get('/api/comment/get', {page: 1})
    if (code === 200) {
        console.log(data)
    }
  }
}