axios源码阅读以及工具函数学习

170 阅读14分钟

axios源码学习

axios是什么

axios是一个很优秀的基于promise的HTTP库,可以应用于浏览器端和node端

axios可以干什么

1)支持promise API。
(2)拦截请求与响应,比如:在请求前添加授权和响应前做一些事情。
(3)转换请求数据和响应数据,比如:进行请求加密或者响应数据加密。
(4)取消请求。
(5)自动转换JSON数据。
(6)客户端支持防御XSRF。

上述两条是百度查的,在我看来就是http请求库,xmlhttprequest的封装。

axios的目录结构

├── /dist/                     # 项目输出目录

├── /lib/                      # 项目源码目录

│ ├── /cancel/                 # 定义取消功能

│ ├── /core/                   # 一些核心功能

│ │ ├── Axios.js               # axios的核心主类

│ │ ├── dispatchRequest.js     # 用来调用http请求适配器方法发送请求

│ │ ├── InterceptorManager.js  # 拦截器构造函数

│ │ └── settle.js              # 根据http响应状态,改变Promise的状态

│ ├── /helpers/                # 一些辅助方法

│ ├── /adapters/               # 定义请求的适配器 xhr、http

│ │ ├── http.js                # 实现http适配器

│ │ └── xhr.js                 # 实现xhr适配器

│ ├── axios.js                 # 对外暴露接口

│ ├── defaults.js              # 默认配置

│ └── utils.js                 # 公用工具

├── package.json               # 项目信息

├── index.d.ts                 # 配置TypeScript的声明文件

└── index.js                   # 入口文件

源码分析

看源码先看README.md和package.json,这是基础常识。 axios是有很大一部分的东西都是基于promise的,所以建议深入学习一下promise(liubin.org/promises-bo…), 这样有利于了解源码里面的细节。

入口文件:

// index.js
import axios from './lib/axios.js';
export default axios;

./lib/axios.js是源码的入口:

// 代码结构
'use strict';

// 引入很多工具函数和一些核心的功能方法
import utils from './utils.js';
import bind from './helpers/bind.js';
import Axios from './core/Axios.js';
import mergeConfig from './core/mergeConfig.js';
import defaults from './defaults/index.js';
import formDataToJSON from './helpers/formDataToJSON.js';
import CanceledError from './cancel/CanceledError.js';
import CancelToken from'./cancel/CancelToken.js';
import isCancel from'./cancel/isCancel.js';
import {VERSION} from './env/data.js';
import toFormData from './helpers/toFormData.js';
import AxiosError from './core/AxiosError.js';
import spread from './helpers/spread.js';
import isAxiosError from './helpers/isAxiosError.js';

/**
 * Create an instance of Axios
 *
 * @param {Object} defaultConfig The default config for the instance
 *
 * @returns {Axios} A new instance of Axios
 */
 // 此处是生成并返回了一个function
function createInstance(defaultConfig) {
  const context = new Axios(defaultConfig);
  const instance = bind(Axios.prototype.request, context);

  // Copy axios.prototype to instance
  utils.extend(instance, Axios.prototype, context, {allOwnKeys: true});

  // Copy context to instance
  utils.extend(instance, context, null, {allOwnKeys: true});

  // Factory for creating new instances
  instance.create = function create(instanceConfig) {
    return createInstance(mergeConfig(defaultConfig, instanceConfig));
  };

  return instance;
}

// Create the default instance to be exported
const axios = createInstance(defaults);

// Expose Axios class to allow class inheritance
axios.Axios = Axios;

// 将对应的方法全部挂到axios这个方法上
// Expose Cancel & CancelToken
axios.CanceledError = CanceledError;
axios.CancelToken = CancelToken;
axios.isCancel = isCancel;
axios.VERSION = VERSION;
axios.toFormData = toFormData;

// Expose AxiosError class
axios.AxiosError = AxiosError;

// alias for CanceledError for backward compatibility
axios.Cancel = axios.CanceledError;

// Expose all/spread
axios.all = function all(promises) {
  return Promise.all(promises);
};

axios.spread = spread;

// Expose isAxiosError
axios.isAxiosError = isAxiosError;

axios.formToJSON = thing => {
  return formDataToJSON(utils.isHTMLForm(thing) ? new FormData(thing) : thing);
};

export default axios;

能够实现axios的多种使用方式的核心是createInstance方法(需要结合./core/Axios.js文件阅读更加清晰):

// 主要是创建一个Axios实例,最终会被作为对象导出
function createInstance(defaultConfig) {
  // 创建一个Axios实例
  const context = new Axios(defaultConfig);
  // 以下代码也可以这样实现:var instance = Axios.prototype.request.bind(context);

  // 这样instance就指向了request方法,且上下文指向context,所以可以直接以 instance(option) 方式调用

  // Axios.prototype.request 内对第一个参数的数据类型判断,使我们能够以 instance(url, option) 方式调用
  const instance = bind(Axios.prototype.request, context);

  // 把Axios.prototype上的方法扩展到instance对象上,

  // 这样 instance 就有了 get、post、put等方法

  // 并指定上下文为context,这样执行Axios原型链上的方法时,this会指向context
  utils.extend(instance, Axios.prototype, context, {allOwnKeys: true});

  // 把context对象上的自身属性和方法扩展到instance上

  // 注:因为extend内部使用的forEach方法对对象做for in 遍历时,只遍历对象本身的属性,而不会遍历原型链上的属性

  // 这样,instance 就有了  defaults、interceptors 属性。
  utils.extend(instance, context, null, {allOwnKeys: true});

  // 一般情况,项目使用默认导出的axios实例就可以满足需求了,  
  // 如果不满足需求需要创建新的axios实例,axios包也预留了接口
  instance.create = function create(instanceConfig) {
    return createInstance(mergeConfig(defaultConfig, instanceConfig));
  };

  return instance;
}

以上代码看上去很绕,其实createInstance最终是希望拿到一个Function,这个Function指向Axios.prototype.request,这个Function还会携带有Axios.prototype上的每个方法作为静态方法,且这些方法的上下文都是指向同一个对象

Axios、Axios.prototype.request的源码

Axios是axios包的核心,一个Axios实例就是一个axios应用,其他方法都是对Axios内容的扩展
而Axios构造函数的核心方法是request方法,各种axios的调用方式最终都是通过request方法发请求的

// /lib/core/Axios.js

class Axios {
  constructor(instanceConfig) {
    this.defaults = instanceConfig;
    this.interceptors = {
      request: new InterceptorManager(),
      response: new InterceptorManager()
    };
  }
  // 所有请求都是调用它来发起
  request(configOrUrl, config) {
    // 省略
  }

  getUri(config) {
    config = mergeConfig(this.defaults, config);
    const fullPath = buildFullPath(config.baseURL, config.url);
    return buildURL(fullPath, config.params, config.paramsSerializer);
  }
}

 
// 此处及下面主要是为了实现axios.get,axios.post等方法
// 为支持的请求方法提供别名

utils.forEach(['delete''get''head''options'], function forEachMethodNoData(method) {

  Axios.prototype[method] = function(url, config) {

    return this.request(utils.merge(config || {}, {

      method: method,

      url: url

    }));

  };

});

utils.forEach(['post''put''patch'], function forEachMethodWithData(method) {

  Axios.prototype[method] = function(url, data, config) {

    return this.request(utils.merge(config || {}, {

      method: method,

      url: url,

      data: data

    }));

  };

});

axios.prototype.request

这里的代码比较复杂,需要对promise有一些较深的理解才可以,先上代码然后简单的分析一下,后续对拦截器,dispatchRequest做详细的解释。

request(configOrUrl, config) {
    /*eslint no-param-reassign:0*/
    // Allow for axios('example/url'[, config]) a la fetch API
    // 前面主要是对参数的判断,转换,校验等
    if (typeof configOrUrl === 'string') {
      config = config || {};
      config.url = configOrUrl;
    } else {
      config = configOrUrl || {};
    }

    config = mergeConfig(this.defaults, config);

    const {transitional, paramsSerializer} = config;

    if (transitional !== undefined) {
      validator.assertOptions(transitional, {
        silentJSONParsing: validators.transitional(validators.boolean),
        forcedJSONParsing: validators.transitional(validators.boolean),
        clarifyTimeoutError: validators.transitional(validators.boolean)
      }, false);
    }

    if (paramsSerializer !== undefined) {
      validator.assertOptions(paramsSerializer, {
        encode: validators.function,
        serialize: validators.function
      }, true);
    }

    // Set config.method
    config.method = (config.method || this.defaults.method || 'get').toLowerCase();

    // Flatten headers
    const defaultHeaders = config.headers && utils.merge(
      config.headers.common,
      config.headers[config.method]
    );

    defaultHeaders && utils.forEach(
      ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
      function cleanHeaderConfig(method) {
        delete config.headers[method];
      }
    );

    config.headers = new AxiosHeaders(config.headers, defaultHeaders);

    // 这里是重点的开始
    // filter out skipped interceptors
    const requestInterceptorChain = [];
    let synchronousRequestInterceptors = true;
    // 将所有的请求拦截器加入到requestInterceptorChain数组中
    this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
      if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) {
        return;
      }

      synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous;

      requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
    });

    const responseInterceptorChain = [];
    // 将所有的回应拦截器加入到responseInterceptorChain数组中
    this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
      responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
    });

    let promise;
    let i = 0;
    let len;
    
    // 如果有请求拦截器的时候将会进入到if语句中
    if (!synchronousRequestInterceptors) {
      // chain中放入的dispatchRequest是真正的请求
      const chain = [dispatchRequest.bind(this), undefined];
      // 在dispatchRequest之前放入请求拦截器
      chain.unshift.apply(chain, requestInterceptorChain);
      // 在dispatchRequest之后放入回应拦截器
      chain.push.apply(chain, responseInterceptorChain);
      len = chain.length;

      promise = Promise.resolve(config);
      
      // 此处开始依次执行请求拦截器,请求,回应拦截器的对应函数
      while (i < len) {
        promise = promise.then(chain[i++], chain[i++]);
      }

      return promise;
    }
    
    // 如果没有请求拦截器将会进入到如下的执行中
    len = requestInterceptorChain.length;

    let newConfig = config;

    i = 0;
    // 存在请求拦截器的时候将会执行请求拦截器,然后try catch中器执行真正的请求
    while (i < len) {
      const onFulfilled = requestInterceptorChain[i++];
      const onRejected = requestInterceptorChain[i++];
      try {
        newConfig = onFulfilled(newConfig);
      } catch (error) {
        onRejected.call(this, error);
        break;
      }
    }

    try {
      promise = dispatchRequest.call(this, newConfig);
    } catch (error) {
      return Promise.reject(error);
    }
    
    // 存在回应拦截器的时候将会执行,与请求拦截器类似
    i = 0;
    len = responseInterceptorChain.length;

    while (i < len) {
      promise = promise.then(responseInterceptorChain[i++], responseInterceptorChain[i++]);
    }

    return promise;
  }

拦截器

拦截器主要分为请求与回应拦截器,分别是针对请求前与回应后的数据进行拦截修改。

首先是如何添加拦截器,删除拦截器的:

// 添加请求拦截器

const myRequestInterceptor = axios.interceptors.request.use(config => {

    // 在发送http请求之前做些什么

    return config; // 有且必须有一个config对象被返回

}, error => {

    // 对请求错误做些什么

    return Promise.reject(error);

});

 

// 添加响应拦截器

axios.interceptors.response.use(response => {

  // 对响应数据做点什么

  return response; // 有且必须有一个response对象被返回

}, error => {

  // 对响应错误做点什么

  return Promise.reject(error);

});

 

// 移除某次拦截器

axios.interceptors.request.eject(myRequestInterceptor);

下面是拦截器的源码:

class Axios {
  constructor(instanceConfig) {
    // 每个axios实例都有一个interceptors实例属性,  
    // interceptors对象上有两个属性request、response。
    // 这两个属性都是一个InterceptorManager实例
    // 而这个InterceptorManager构造函数就是用来管理拦截器的
    this.interceptors = {
      request: new InterceptorManager(),
      response: new InterceptorManager()
    };
  }
 }

InterceptorManager构造函数:

InterceptorManager构造函数就是用来实现拦截器的,这个构造函数原型上有3个方法:use、eject、forEach。 关于源码,其实是比较简单的,都是用来操作该构造函数的handlers实例属性的

'use strict';

import utils from './../utils.js';

class InterceptorManager {
  constructor() {
    // 存放拦截器方法,数组内每一项都是有两个属性的对象,两个属性分别对应成功和失败后执行的函数。
    this.handlers = [];
  }

  /**
   * Add a new interceptor to the stack
   *
   * @param {Function} fulfilled The function to handle `then` for a `Promise`
   * @param {Function} rejected The function to handle `reject` for a `Promise`
   *
   * @return {Number} An ID used to remove interceptor later
   */
  // 往拦截器里添加拦截方法
  use(fulfilled, rejected, options) {
    this.handlers.push({
      fulfilled,
      rejected,
      synchronous: options ? options.synchronous : false,
      runWhen: options ? options.runWhen : null
    });
    return this.handlers.length - 1;
  }

  /**
   * Remove an interceptor from the stack
   *
   * @param {Number} id The ID that was returned by `use`
   *
   * @returns {Boolean} `true` if the interceptor was removed, `false` otherwise
   */
  // 用来注销指定的拦截器
  eject(id) {
    if (this.handlers[id]) {
      this.handlers[id] = null;
    }
  }

  /**
   * Clear all interceptors from the stack
   *
   * @returns {void}
   */
  clear() {
    if (this.handlers) {
      this.handlers = [];
    }
  }

  /**
   * Iterate over all the registered interceptors
   *
   * This method is particularly useful for skipping over any
   * interceptors that may have become `null` calling `eject`.
   *
   * @param {Function} fn The function to call for each interceptor
   *
   * @returns {void}
   */
  // 遍历this.handlers,并将this.handlers里的每一项作为参数传给fn执行
  forEach(fn) {
    utils.forEach(this.handlers, function forEachHandler(h) {
      if (h !== null) {
        fn(h);
      }
    });
  }
}

export default InterceptorManager;

axios内部又是怎么让这些拦截器能够在请求前、请求后拿到我们想要的数据的呢?

(一定要熟悉promise的链式执行才可以明白下面的执行逻辑,否则可能会不明白何时发送的请求) 还记得之前的/lib/core/Axios.js文件吗,下面就回顾一下具体的流程:首先使用foreach函数将对应的拦截器放入对应的数组,然后声明一个chain,里面放入真正的ajax请求(dispatchRequest.bind(this)),将请求拦截放入dispatchRequest的前面,回应拦截放入dispatchRequest的后面,然后开始使用promise的链式执行依次执行函数。

const requestInterceptorChain = [];
    let synchronousRequestInterceptors = true;
    this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
      if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) {
        return;
      }

      synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous;

      requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
    });

    const responseInterceptorChain = [];
    this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
      responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
    });

    let promise;
    let i = 0;
    let len;

    if (!synchronousRequestInterceptors) {
      const chain = [dispatchRequest.bind(this), undefined];
      chain.unshift.apply(chain, requestInterceptorChain);
      chain.push.apply(chain, responseInterceptorChain);
      
      // 添加了拦截器后的chain数组大概会是这样的:
      // [
      //   requestFulfilledFn, requestRejectedFn, ...,
      //   dispatchRequest, undefined,
      //   responseFulfilledFn, responseRejectedFn, ....,
      // ]
      len = chain.length;

      promise = Promise.resolve(config);

    // 数组的 shift() 方法用于把数组的第一个元素从其中删除,并返回第一个元素的值。
    // 每次执行while循环,从chain数组里按序取出两项,并分别作为promise.then方法的第一个和第二个参数
    // 按照我们使用InterceptorManager.prototype.use添加拦截器的规则,正好每次添加的就是我们通过InterceptorManager.prototype.use方法添加的成功和失败回调
    // 通过InterceptorManager.prototype.use往拦截器数组里添加拦截器时使用的数组的push方法,
    // 对于请求拦截器,从拦截器数组按序读到后是通过unshift方法往chain数组数里添加的,又通过shift方法从chain数组里取出的,所以得出结论:对于请求拦截器,先添加的拦截器会后执行
    // 对于响应拦截器,从拦截器数组按序读到后是通过push方法往chain数组里添加的,又通过shift方法从chain数组里取出的,所以得出结论:对于响应拦截器,添加的拦截器先执行
    // 第一个请求拦截器的fulfilled函数会接收到promise对象初始化时传入的config对象,而请求拦截器又规定用户写的fulfilled函数必须返回一个config对象,所以通过promise实现链式调用时,每个请求拦截器的fulfilled函数都会接收到一个config对象
    // 第一个响应拦截器的fulfilled函数会接受到dispatchRequest(也就是我们的请求方法)请求到的数据(也就是response对象),而响应拦截器又规定用户写的fulfilled函数必须返回一个response对象,所以通过promise实现链式调用时,每个响应拦截器的fulfilled函数都会接收到一个response对象
    // 任何一个拦截器的抛出的错误,都会被下一个拦截器的rejected函数收到,所以dispatchRequest抛出的错误才会被响应拦截器接收到。
    // 因为axios是通过promise实现的链式调用,所以我们可以在拦截器里进行异步操作,而拦截器的执行顺序还是会按照我们上面说的顺序执行,也就是 dispatchRequest 方法一定会等待所有的请求拦截器执行完后再开始执行,响应拦截器一定会等待 dispatchRequest 执行完后再开始执行。
      while (i < len) {
        promise = promise.then(chain[i++], chain[i++]);
      }

      return promise;
    }

上面已经讲述了拦截器那么到底位于中游的dispatchRequest是如何发送http请求的呢?

dispatchRequest

dispatchRequest主要做了3件事:
1,拿到config对象,对config进行传给http请求适配器前的最后处理;
2,http请求适配器根据config配置,发起请求
3,http请求适配器请求完成后,如果成功则根据header、data、和config.transformResponse拿到数据转换后的response,并return。

export default function dispatchRequest(config) {
  throwIfCancellationRequested(config);

  config.headers = AxiosHeaders.from(config.headers);

  // 对请求data进行转换
  config.data = transformData.call(
    config,
    config.transformRequest
  );
  // http请求适配器会优先使用config上自定义的适配器,没有配置时才会使用默认的XHR或http适配器,不过大部分时候,axios提供的默认适配器是能够满足我们的
  const adapter = config.adapter || defaults.adapter;

  return adapter(config).then(function onAdapterResolution(response) {
    throwIfCancellationRequested(config);

    // Transform response data
    response.data = transformData.call(
      config,
      config.transformResponse,
      response
    );

    response.headers = AxiosHeaders.from(response.headers);

    return response;
  }, function onAdapterRejection(reason) {
    if (!isCancel(reason)) {
      throwIfCancellationRequested(config);

      // Transform response data
      if (reason && reason.response) {
        reason.response.data = transformData.call(
          config,
          config.transformResponse,
          reason.response
        );
        reason.response.headers = AxiosHeaders.from(reason.response.headers);
      }
    }

    return Promise.reject(reason);
  });
}

看了上面的代码,我们知道其实dispatchRequest调用adapter,那么adapter是怎么工作的呢?

adapter

dispatchRequest方法会调用xhrAdapter或者是httpAdapter方法(就以xhrAdapter为例),xhrAdapter方法返回的是还一个Promise对象

// /lib/adapters/xhr.js

export default function xhrAdapter(config) {

  return new Promise(function dispatchXhrRequest(resolve, reject) {

    // ... 省略代码

  });

};

xhrAdapter内的XHR发送请求成功后会执行这个Promise对象的resolve方法,并将请求的数据传出去,
反之则执行reject方法,并将错误信息作为参数传出去。 下面是xhr的主要代码(不完整):

// /lib/adapters/xhr.js

var request = new XMLHttpRequest();

function onloadend() {
      if (!request) {
        return;
      }
      // Prepare the response
      const responseHeaders = AxiosHeaders.from(
        'getAllResponseHeaders' in request && request.getAllResponseHeaders()
      );
      const responseData = !responseType || responseType === 'text' ||  responseType === 'json' ?
        request.responseText : request.response;
      const response = {
        data: responseData,
        status: request.status,
        statusText: request.statusText,
        headers: responseHeaders,
        config,
        request
      };

      settle(function _resolve(value) {
        resolve(value);
        done();
      }, function _reject(err) {
        reject(err);
        done();
      }, response);

      // Clean up request
      request = null;
}
if ('onloadend' in request) {
      // Use onloadend if available
      request.onloadend = onloadend;
    } else {
      // Listen for ready state to emulate onloadend
      request.onreadystatechange = function handleLoad() {
        if (!request || request.readyState !== 4) {
          return;
        }

        // The request errored out and we didn't get a response, this will be
        // handled by onerror instead
        // With one exception: request that using file: protocol, most browsers
        // will return status as 0 even though it's a successful request
        if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) {
          return;
        }
        // readystate handler is calling before onerror or ontimeout handlers,
        // so we should call onloadend on the next 'tick'
        setTimeout(onloadend);
      };
}

request.onerror = function handleError() {

  reject(/**/);

  request = null;

};

request.ontimeout = function handleTimeout() {

  reject(/**/);

  request = null;

};

验证服务端的返回结果是否通过验证:

// /lib/core/settle.js

export default function settle(resolve, reject, response) {
  const validateStatus = response.config.validateStatus;
  if (!response.status || !validateStatus || validateStatus(response.status)) {
    resolve(response);
  } else {
    reject(new AxiosError(
      'Request failed with status code ' + response.status,
      [AxiosError.ERR_BAD_REQUEST, AxiosError.ERR_BAD_RESPONSE][Math.floor(response.status / 100) - 4],
      response.config,
      response.request,
      response
    ));
  }
}

回到dispatchRequest方法内,首先得到xhrAdapter方法返回的Promise对象,
然后通过.then方法,对xhrAdapter返回的Promise对象的成功或失败结果再次加工,
成功的话,则将处理后的response返回,
失败的话,则返回一个状态为rejected的Promise对象

return adapter(config).then(function onAdapterResolution(response) {

    // ...

    return response;

  }, function onAdapterRejection(reason) {

    // ...

    return Promise.reject(reason);

  });

那么至此,用户调用axios()方法时,就可以直接调用Promise的.then或.catch进行业务处理了。

回过头来,我们在介绍dispatchRequest一节时说到的数据转换,而axios官方也将数据转换专门作为一个亮点来介绍的,那么数据转换到底能在使用axios发挥什么功效呢?

数据转换器-转换请求与响应数据

如何使用呢?

1. 修改全局的转换器

// 往现有的请求转换器里增加转换方法
axios.defaults.transformRequest.push((data, headers) => {
  // ...处理data
  return data;
});

// 重写请求转换器
axios.defaults.transformRequest = [(data, headers) => {
  // ...处理data
  return data;
}];

// 往现有的响应转换器里增加转换方法
axios.defaults.transformResponse.push((data, headers) => {
  // ...处理data
  return data;
});

// 重写响应转换器
axios.defaults.transformResponse = [(data, headers) => {
  // ...处理data
  return data;
}];

2. 修改某次axios请求的转换器

// 往已经存在的转换器里增加转换方法
axios.get(url, {
  // ...
  transformRequest: [
    ...axios.defaults.transformRequest, // 去掉这行代码就等于重写请求转换器了
    (data, headers) => {
      // ...处理data
      return data;
    }
  ],
  transformResponse: [
    ...axios.defaults.transformResponse, // 去掉这行代码就等于重写响应转换器了
    (data, headers) => {
      // ...处理data
      return data;
    }
  ],
})

了解了使用,我们接下来进行源码的分析:

// 默认的defaults配置项里已经自定义了一个请求转换器和一个响应转换器

    const defaults = {
        transformRequest: [
            function transformRequest(data, headers) {
                normalizeHeaderName(headers, 'Content-Type');
                // ...
                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) {
            if (typeof data === 'string') {
              try {
                data = JSON.parse(data);
              } catch (e) { /* Ignore */ }
            }
            return data;
          }
        ],
    }

那么在axios项目里,是在什么地方使用了转换器呢?

请求转换器的使用地方是http请求前,使用请求转换器对请求数据做处理,
然后传给http请求适配器使用。

// /lib/core/dispatchRequest.js
// 代码分为两段

config.data = transformData.call(
    config,
    config.transformRequest
);

// 响应转换器的使用地方是在http请求完成后,根据http请求适配器的返回值做数据转换处理
response.data = transformData.call(
    config,
    config.transformResponse,
    response
 );

看下transformData方法: 主要遍历转换器数组,分别执行每一个转换器,根据data和headers参数,返回新的data

// /lib/core/transformData.js
export default function transformData(fns, response) {
  const config = this || defaults;
  const context = response || config;
  const headers = AxiosHeaders.from(context.headers);
  let data = context.data;
  utils.forEach(fns, function transform(fn) {
    data = fn.call(config, data, headers.normalize(), response ? response.status : undefined);
  });
  headers.normalize();
  return data;
}

看完了之后,知道在何处使用了转换器,并且是如何使用的,那转换器和拦截器的关系?从上面看其实功能都差不多,甚至都不需要进行区分,完全是可以混用的。 但是依据我自己阅读源码,我感觉在请求时,拦截器主要负责修改config配置项,数据转换器主要负责转换请求体,比如转换对象为字符串,在请求响应后,拦截器可以拿到response,数据转换器主要负责处理响应体,比如转换字符串为对象。

header设置

首先看看如何使用header的:

import axios from 'axios'

// 设置通用header
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'// xhr标识

// 设置某种请求的header
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8';

// 设置某次请求的header
axios.get(url, {
  headers: {
    'Authorization''whr1',
  },
})

接下来看看这一块源码是如何实现的:

// lib/core/Axios.js文件中的request函数中实现
// Flatten headers
const defaultHeaders = config.headers && utils.merge(
     config.headers.common,
     config.headers[config.method]
);

如何取消已经发送的请求

如何使用取消

import axios from 'axios'

// 第一种取消方法
axios.get(url, {
  cancelToken: new axios.CancelToken(cancel => {
    if (/* 取消条件 */) {
      cancel('取消日志');
    }
  })
});

// 第二种取消方法
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.get(url, {
  cancelToken: source.token
});
source.cancel('取消日志');

看过了使用方法之后,可以看一下源码是怎么来进行的:

// lib/cancel/CancelToken
class CancelToken {
    constructor(executor) {
        // 判断函数
        if (typeof executor !== 'function') {
          throw new TypeError('executor must be a function.');
        }

        let resolvePromise;

        this.promise = new Promise(function promiseExecutor(resolve) {
          resolvePromise = resolve;
        });

        const token = this;

        // eslint-disable-next-line func-names
        this.promise.then(cancel => {
          if (!token._listeners) return;

          let i = token._listeners.length;

          while (i-- > 0) {
            token._listeners[i](cancel);
          }
          token._listeners = null;
        });

        // eslint-disable-next-line func-names
        this.promise.then = onfulfilled => {
          let _resolve;
          // eslint-disable-next-line func-names
          const promise = new Promise(resolve => {
            token.subscribe(resolve);
            _resolve = resolve;
          }).then(onfulfilled);

          promise.cancel = function reject() {
            token.unsubscribe(_resolve);
          };

          return promise;
        };

        executor(function cancel(message, config, request) {
          if (token.reason) {
            // Cancellation has already been requested
            return;
          }

          token.reason = new CanceledError(message, config, request);
          resolvePromise(token.reason);
        });
    }
    // 其余代码省略
    static source() {
        let cancel;
        const token = new CancelToken(function executor(c) {
          cancel = c;
        });
        return {
          token,
          cancel
        };
      }
}
// 真正调用取消的是在/lib/adapters/xhr.js中使用的
if (config.cancelToken) {
        config.cancelToken.unsubscribe(onCanceled);
}

if (config.cancelToken || config.signal) {
      // Handle cancellation
      // eslint-disable-next-line func-names
      onCanceled = cancel => {
        if (!request) {
          return;
        }
        reject(!cancel || cancel.type ? new CanceledError(null, config, request) : cancel);
        request.abort();
        request = null;
      };

      config.cancelToken && config.cancelToken.subscribe(onCanceled);
      if (config.signal) {
        config.signal.aborted ? onCanceled() : config.signal.addEventListener('abort', onCanceled);
      }
}

取消功能的核心是通过CancelToken内的this.promise = new Promise(resolve => resolvePromise = resolve), 得到实例属性promise,此时该promise的状态为pending通过这个属性,在/lib/adapters/xhr.js文件中执行,从而改变此promise的状态;

在CancelToken外界,通过executor参数拿到对cancel方法的控制权,这样当执行cancel方法时就可以改变实例的promise属性的状态为rejected,从而执行request.abort()方法达到取消请求的目的。

上面第二种写法可以看作是对第一种写法的完善,因为很多是时候我们取消请求的方法是用在本次请求方法外 如下:

// 例如,发送A、B两个请求,当B请求成功后,取消A请求。
// 第1种写法:
let source;
axios.get(Aurl, {
  cancelToken: new axios.CancelToken(cancel => {
    source = cancel;
  })
});
axios.get(Burl).then(() => source('B请求成功了'));

// 第2种写法:
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.get(Aurl, {
  cancelToken: source.token
});
axios.get(Burl).then(() => source.cancel('B请求成功了'));

// 相对来说,我更推崇第1种写法,因为第2种写法太隐蔽了,不如第一种直观好理解。

总结

其实还是有很多方面没有写出来,包含跨域携带cookie,超时配置及处理等,但是原理都基本包含在上面的源码中,只要把上面的东西都理解了基本后续的也就迎刃而解了,axios这个项目里,有很多对JS使用很巧妙的地方,比如对promise的串联操作(当然你也可以说这块是借鉴很多异步中间件的处理方式),让我们可以很方便对请求前后的各种处理方法的流程进行控制;很多实用的小优化,比如请求前后的数据处理,省了程序员一遍一遍去写JSON.xxx了;同时支持了浏览器和node两种环境,对使用node的项目来说无疑是极好的,最主要的还是要理解promise和XMLHttpRequest,那么这个工程基本也就理解了。关于理解工具函数,可能是平时对应lodash工具库看的比较多,对于axios里面的工具函数比较的无感。

// promsie链式理解
var a = Promise.resolve(1);
var b = a.then(function (value) {
    console.log(value);
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            resolve(value);
        }, 3000)
    })
})
var c = b.then(function (value) {
    console.log('3后获取到value:1');
})