从零实现axios(3.3小节-实现数据转换器1)

159 阅读3分钟

实现数据转换器1

转换器的作用就是实现对请求 data 和响应 data 的数据转换作用,我们在 dispatchRequest.js 函数中实现对请求数据的转换和响应数据的转换。

var utils = require("../utils");
var transformData = require("./transformData");
var defaults = require("../defaults");

module.exports = function dispatchRequest(config) {
  // 确保 headers 存在
  config.headers = config.headers || {};

  // 转换请求数据
  // config是transformData函数的this上下文
  config.data = transformData.call(
    config,
    config.data,
    config.headers,
    config.transformRequest
  );

  // 省略中间部分代码
  // ...

  return adapter(config).then(
    function onAdapterResolution(response) {
      // 转换响应数据
      // config是transformData函数的this上下文
      response.data = transformData.call(
        config,
        response.data,
        response.headers,
        config.transformResponse
      );

      return response;
    },
    function onAdapterRejection(reason) {
      // 响应失败的转换留到之后实现处理请求失败的功能时再做
      return Promise.reject(reason);
    }
  );
};

我们在 core 文件夹下创建 transformData 文件,我们在里面实现 transformData 函数。 transformData 函数接收三个参数,第一个是 data,即要被转换的数据;第二个是 headers,即请求头或响应头;第三个参数 fns,即一个函数或数组,里面包含真正实现数据转换的回调函数,从上面的代码中看出是 config.transformRequestconfig.transformResponse,我们在默认配置对象中实现这两个数组

"use strict";

var utils = require("../utils");
var defaults = require('../defaults');

/**
 *
 *
 * @param {Object|String} data 要被转换的数据
 * @param {Array} headers 请求头或响应头
 * @param {Array|Function} fns 一个函数或数组,里面包含真正实现转换的回调函数
 * @returns {*} 转换后的数据
 */
module.exports = function transformData(data, headers, fns) {
  var context = this || defaults;
  /*eslint no-param-reassign:0*/
  utils.forEach(fns, function transform(fn) {
    data = fn.call(context, data, headers);
  });

  return data;
};

我们完善defaults/index.js的转换函数功能,其中transformRequest是对请求 data 进行转换,transformResponse是对响应 data 进行转换

var utils = require("../utils");
var normalizeHeaderName = require("../helpers/normalizeHeaderName");
var transitionalDefaults = require('./transitional');

// 如果headers头没有设置Content-Type,则把value设为headers["Content-Type"]的值
function setContentTypeIfUnset(headers, value) {
  if (
    !utils.isUndefined(headers) &&
    utils.isUndefined(headers["Content-Type"])
  ) {
    headers["Content-Type"] = value;
  }
}

// 对数据进行安全的stringify
// parser是自定义的字符串解释器来对rawValue进行解释
// encoder是自定义的字符串编码器来对rawValue进行编码
function stringifySafely(rawValue, parser, encoder) {
  if (utils.isString(rawValue)) {
    try {
      // 如果rawValue是字符串,对其进行解释处理
      // 如果不报错,则说明rawValue已被正确stringify,则去掉首尾空格,直接返回
      (parser || JSON.parse)(rawValue);
      return utils.trim(rawValue);
    } catch (e) {
      if (e.name !== 'SyntaxError') {
        // 如果是自定义parser抛出的异常,则继续抛出该异常
        throw e;
      }
      // SyntaxError为JSON.parse抛出的异常,说明rawValue没被stringify
      // 继续执行,进行stringify
    }
  }

  return (encoder || JSON.stringify)(rawValue);
}

var defaults = {
  transitional: transitionalDefaults,
  transformRequest: [
    function transformRequest(data, headers) {
      // 如果用户自定义Accept头是小写形式,则转换为'Accept'
      normalizeHeaderName(headers, "Accept");
      // 如果用户自定义'Content-Type'头是小写形式,则转换为''Content-Type''
      normalizeHeaderName(headers, "Content-Type");

      // 是以下数据类型则直接返回
      if (
        // FormData类型
        utils.isFormData(data) ||
        // ArrayBuffer类型
        utils.isArrayBuffer(data) ||
        // Buffer类型
        utils.isBuffer(data) ||
        // Stream类型
        utils.isStream(data) ||
        // File类型
        utils.isFile(data) ||
        // Blob类型
        utils.isBlob(data)
      ) {
        return data;
      }

      // data 是 ArrayBufferView类型
      if (utils.isArrayBufferView(data)) {
        return data.buffer;
      }

      //data 是 URLSearchParams类型,则进行序列化
      if (utils.isURLSearchParams(data)) {
        // 如果header没有设置'Content-Type', 则添加默认值
        setContentTypeIfUnset(
          headers,
          "application/x-www-form-urlencoded;charset=utf-8"
        );
        return data.toString();
      }

      var isObjectPayload = utils.isObject(data);
      var contentType = headers && headers['Content-Type'];

      var isFileList;
      // 如果data是文件列表类型,或者是object类型,但Content-Type指定为'multipart/form-data'
      // 需要把data数据统一转为form-data类型
      if ((isFileList = utils.isFileList(data)) || (isObjectPayload && contentType === 'multipart/form-data')) {
        // 这是一个form-data npm包,用来创建'multipart/form-data'流
        var _FormData = this.env && this.env.FormData;
        // 把data转化为form-data类型
        return toFormData(isFileList ? {'files[]': data} : data,
                          _FormData && new _FormData());
      } else if (isObjectPayload || contentType === 'application/json') {
        // data 是 object类型 或 Content-Type指定为'application/json'
        // 需要对data进行字符串化

        setContentTypeIfUnset(headers, 'application/json');
        return stringifySafely(data);
      }
    },
  ],

  transformResponse: [function transformResponse(data) {
    // transitional用来指定对是否要对响应数据进行JSON.parse
    // 该对象既可以由用户配置对象指定,也可以使用默认值
    var transitional = this.transitional || defaults.transitional;
    // 对数据进行JSON.parse时,如果有异常,是否应该抛出,默认不抛出
    var silentJSONParsing = transitional && transitional.silentJSONParsing;
    // 强行对数据进行JSON.parse
    var forcedJSONParsing = transitional && transitional.forcedJSONParsing;
    // 严格的JSON.parse,会抛出异常
    var strictJSONParsing = !silentJSONParsing && this.responseType === 'json';

    if (strictJSONParsing || (forcedJSONParsing && utils.isString(data) && data.length)) {
      try {
        return JSON.parse(data);
      } catch (e) {
        if (strictJSONParsing) {
          throw e;
        }
      }
    }

    return data;
  }],

  env: {
    FormData: require('./env/FormData')
  },
};

我们在下一小节,实现用到的辅助函数