axios内部的数据转换方法

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第二天,点击查看活动详情

题引:

公司终于决定把vue2的项目升级到vue3,那就少不了配置文件和封装。 在axios的二次封装中,对post请求的参数通过算法进行加密处理成了36位的字符串。是的,字符串,就是因为data被处理成了字符串类型传递过去,且在axios^0.27的新版本里,默认的transformRequest方法对data是不是对象类型进行了更丰富的判断,下面开始讲解一下。

正文:

在报错的情况下通过调试,一步一步的寻找问题,后面定位了问题的所在,顺便贴出axios的执行流程。

axios流程.png

整体流程:

  1. axios/axios.create() -> request(config) -> request interceptors -> dispatchRequest(config) -> xhrAdapter(config) -> resolve()/reject() -> response interceptors -> 请求的onResolve或onRejectd

函数分析:

  1. request(config)

    将请求拦截器 / dispatchRequest() / 响应拦截器 通过 promise 串连起来, 返回 promise。

  2. dispatchRequest(config)

    转换请求数据 ===> 调用 xhrAdapter()发请求 ===> 请求返回后转换响应数据. 返回 promise。

  3. xhrAdapter(config)

    创建 XHR 对象, 根据 config 进行相应设置, 发送特定请求, 并接收响应数据, 返回 promise、

    是的,我们通过上面的流程图,我们直接前往axios/lib/core/dispatchRequest.js,可以看到这一段代码

module.exports = function dispatchRequest(config) {
  ...其他代码
  
  // NOTE 这里就是将请求数据进行数据转换的地址入口
  // Transform request data
  config.data = transformData(
    config.data,
    config.headers,
    config.transformRequest
  );
  
  ...其他代码

  return adapter(config).then(function onAdapterResolution(response) {
    throwIfCancellationRequested(config);
    
    // NOTE 这里就是将响应数据进行数据转换的地址入口
    // Transform response data
    response.data = transformData(
      response.data,
      response.headers,
      config.transformResponse
    );
    
    return response;
  }, function onAdapterRejection(reason) {
    ...其他代码
  });
};

我们可以看到,这里有两个转换数据的函数调用,一个是请求数据、一个是响应式数据。 如果我们没有定义transformRequest或者transformResponse方法,那么axios就会采用默认的方法来执行。我们可以前往axios/lib/default.js

这是以前的vue2用的版本的默认转换方法
var 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();
    }
    //如果是对象,则会调用JSON.stringify方法
    if (utils.isObject(data)) {
      setContentTypeIfUnset(headers, 'application/json;charset=utf-8');
      return JSON.stringify(data);
    }
    return data;
  }],
  ...其他代码
}

vue3用的axios版本代码
  transformRequest: [function transformRequest(data, headers) {
    normalizeHeaderName(headers, 'Accept');
    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();
    }
    // 这里开始就跟旧版本不一样了,旧版本是直接判断是对象就 return JSON.stringify(data);
    // 新版本新增了对file类型的判断
    var isObjectPayload = utils.isObject(data);
    var contentType = headers && headers['Content-Type'];

    var isFileList;
    // 这里是文件类型的判断
    if ((isFileList = utils.isFileList(data)) || (isObjectPayload && contentType === 'multipart/form-data')) {
      var _FormData = this.env && this.env.FormData;
      return toFormData(isFileList ? {'files[]': data} : data, _FormData && new _FormData());
    } 
    // 这里不管 isObjectPayload 是不是true,只要类型是 pplication/json 都会触发
    else if (isObjectPayload || contentType === 'application/json') {
      setContentTypeIfUnset(headers, 'application/json');
      return stringifySafely(data);
    }

    return data;
  }],

function stringifySafely(rawValue, parser, encoder) {
  // 只要是字符串就会触发
  if (utils.isString(rawValue)) {
    try {
      // 由于是字符串(字母+数字),调用JSON.parse会报错
      (parser || JSON.parse)(rawValue);
      return utils.trim(rawValue);
    } catch (e) {
      if (e.name !== 'SyntaxError') {
        throw e;
      }
    }
  }
  // 所以会执行到这里 eg:JSON.stringify('32131asdas') = '"32131asdas"' 
  // 也就是为什么后端那边解析字符串的时候还是字符串,就会报接口错误404
  return (encoder || JSON.stringify)(rawValue);
}

公司的问题就是出现在这里。
一开始传递的参数是对象,但是在请求拦截器那里被加密变成了字符串,且contentTypeapplication/json类型,最后一定会调用stringifySafely方法,变成两层引号的字符串,后端那边解析字符串的时候还是字符串,就会报接口错误。

接下来我们进入transformData函数内部看看里面的逻辑

module.exports = function transformData(data, headers, fns) {
  // fns : 是转换方法
  utils.forEach(fns, function transform(fn) {
    data = fn(data, headers);
  });
  return data;
};

// utils.foeEach 方法
function forEach(obj, fn) {
  // obj是转换方法 
  // fn是回调函数
  // NOTE 如果转换方法是null或者undefined 直接返回
 if (obj === null || typeof obj === 'undefined') {
   return;
 }
 // NOTE 如果不是对象 则变成数组形式
 if (typeof obj !== 'object') {
   /*eslint no-param-reassign:0*/
   obj = [obj];
 }
 // NOTE 如果是数组 则执行已有的转换方法
 if (isArray(obj)) {
   // Iterate over array values
   for (var i = 0, l = obj.length; i < l; i++) {
     fn.call(null, obj[i], i, obj);
   }
 } else {
   // Iterate over object keys
   for (var key in obj) {
     if (Object.prototype.hasOwnProperty.call(obj, key)) {
       fn.call(null, obj[key], key, obj);
     }
   }
 }
}

这也就是axios内部会对我们的data进行处理的一个流程。

© 版权声明
THE END
喜欢就支持一下吧
点赞8 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容