axios - 10 - 使用姿势

一般需求

axios 对外暴露了基本的 6 种请求方式,对付一般的需求差不多足够了:

  1. axios.delete()
  2. axios.get()
  3. axios.head()
  4. axios.post()
  5. axios.put()
  6. axios.patch()

当然,如果我们需要更改或添加请求头信息的话,那就不能使用这已经包装好的 6 个方法了。

需要这样使用:

axios({
    method:'POST/GET/PUT...',
    url:'...',
    data:'body data',
    params:'query string',
    headers:{
        /* 需要更改/添加的请求头 */
    }
})

中级需求

上面的这几种方式,并没有过滤请求和响应,所以如果把 axios 作为项目中需要使用的 ajax 请求库的话,改造还需要继续下去。

过滤请求和响应

  • 基于 Promise 的过滤
// 过滤请求
axios.interceptors.request.use((config) => {

    /* 发送请求前可以对 config 中的内容进行改造
       比如添加头信息,特定请求不允许通过等等 */
    
    /* 方式一:直接返回处理后的 config */
    return config
    /* 方式二:返回一个 Promise 对象 */
    return new Promise((resolve,reject) =>{
        // do something
        // resolve 的参数即为处理完的 config
        // reject 调用会终止请求的调用
        resolve(config) / reject(err)
    })
})

// 过滤响应
axios.interceptors.response.use(result => {

    /* result 为服务器响应回来的数据 */
    
    /* 方式一:直接返回处理后的 result */
    return result
    /* 方式二:返回一个 Promise 对象 可以根据服务端返回的错误码进行判断 */
    if(result.data.code != 0){
        return Promise.reject(result)
    }
},err => {
    /* 当响应出错的时候 */
    return Promise.reject(result)
})

这种过滤方式是在请求的两端分别加上了一串的 Promise 先执行 interceptors.request 中注册的处理函数然后发送请求,最后执行 interceptors.response 中注册的函数。

interceptors.request 中的处理函数中的参数为调用 axios 传入的 config

interceptors.response 中的处理函数中的参数为服务器返回的结果。

  • 基于配置的过滤

由于 axios 足够的灵活,过滤响应请求不仅仅是上面这种方式,但是不太建议使用以下方式进行过滤,因为这会改变默认的行为。

在配置 config 对象时,我们说过在该对象下,有这么两个属性 transformRequest transformResponse 具体见该篇文章 axios - 2 - 参数字段

所以在配置 config 对象时,我们只需要改变这两项对应的数组就可以了

{
    url: '/user',

    // 请求方式
    method: 'get', // default
    
    // 用于改变请求的函数数组
    transformRequest: [function (data, header) {
        // 改变请求内容
        // 这里不支持异步的调用,仅仅使用于同步,所以更改 header 的内容必须同步进行
        // 不用返回 header 返回 data 即可
        return data;
    }],
    
    // 用于改变响应内容的函数数组
    transformResponse: [function (data) {
        // 改变响应内容
        // 同样不支持异步操作
        return data;
    }],
    
    // 头信息
    headers: {'X-Requested-With': 'XMLHttpRequest'},
}

注:之所以不推荐大家这么写的原因在于这两个有默认值,在 defaults 中,有这么一段:

{
  transformRequest: [function transformRequest(data, headers) {
    normalizeHeaderName(headers, 'Content-Type');
    if (utils.isFormData(data) ||
      utils.isArrayBuffer(data) ||
      utils.isBuffer(data) ||
      utils.isStream(data) ||
      utils.isFile(data) ||
      utils.isBlob(data)
    ) {
      return data;
    }
    if (utils.isArrayBufferView(data)) {
      return data.buffer;
    }
    if (utils.isURLSearchParams(data)) {
      setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8');
      return data.toString();
    }
    if (utils.isObject(data)) {
      setContentTypeIfUnset(headers, 'application/json;charset=utf-8');
      return JSON.stringify(data);
    }
    return data;
  }],

  transformResponse: [function transformResponse(data) {
    /*eslint no-param-reassign:0*/
    if (typeof data === 'string') {
      try {
        data = JSON.parse(data);
      } catch (e) { /* Ignore */ }
    }
    return data;
  }],
}

所以当你在 config 中添加了这两个属性时,会将默认给替换掉,所以你会很悲剧的发现为啥我给的参数都没有过去,响应回来的竟然需要我手动 JSON.parse 一下,当然还是有解决办法的,由于是数组,所以只需要把这两个默认的给带上就可以了,但不得不说这很麻烦,不是吗?

高级需求

当你准备把 axios 这个库作为你项目的底层库时,你不得不考虑方法的通用以及一致性时,就会有一下几点思考:

  1. 在不改变 axios 这个库的默认行为的情况下(也就是不改变原有的 axios 的行为,生成一个新的 axios),去做一些请求的封装。
  2. 基本上所有的请求都是同一域名,当服务器迁移的时候,如何能更便捷的去修改请求,以适应当前的服务器环境。
  3. 一些必要的请求头信息是所有请求都要附带的,比如 token auth 等等。
  4. 服务器返回的是有规则的响应,如何统一过滤掉,而不必每个请求单独写。
  5. 这个新生成的库与调用时应该是无影响的,也就是说可以足够的可配置。

所以在考虑了这些之后,便有了以下文件

import axios from 'axios'

/* 创建一个新的 axios 对象,确保原有的对象不变 */
let axiosWrap = axios.create({
    baseURL: /* 服务器的根路径 */,
    headers: {
        /* 一些公用的 header */
        'token': appInfo.token
    },
    transformRequest:[function (data, header){
        /* 自定义请求参数解析方式(如果必要的话) */
    }],
    paramsSerializer:function(params){
        /* 自定义链接参数解析方式(如果必要的话) */
    }
})

/* 过滤请求 */
axiosWrap.interceptors.request.use((config) => {
    return config
})

/* 过滤响应 */
axiosWrap.interceptors.response.use((result) => {
    /* 假设当code为0时代表响应成功 */
    if (result.data.code != 0) {
        return Promise.reject(result)
    }
    return result.data.data
}, result => {
    return Promise.reject(result)
})

export default axiosWrap

虽然挺简单的,但很实用,不是吗?