深入理解Axios源码,interceptor及cancelToken

Axios是一个很常用的http请求库,功能强大,应用也很灵活,可用于node和浏览器环境。也可以扩展自己的请求方法。我们可以通过调试源码来更深入的理解底层原理,学习其思想。

github地址:axiso

可以在线类似vscode访问,在github后面加上1s即可:

https://github1s.com/axios/axios

axios对象

axios对象是通过调用createInstance创建出来的。

经过如下处理axios既是个方法,又含有内部request方法。

function Axios() {}

Axios.prototype.request = function(msg) {
  console.log('Send msg:' + msg)
}

var context = new Axios();
var axios = Axios.prototype.request.bind(context)
axios.request = Axios.prototype.request

// below equals
axios("hello")
axios.request('hello1')

底下是具体的源码实现。

'use strict';

var utils = require('./utils');
var bind = require('./helpers/bind');
var Axios = require('./core/Axios');
var mergeConfig = require('./core/mergeConfig');
var defaults = require('./defaults');

/**
 * Create an instance of Axios
 *
 * @param {Object} defaultConfig The default config for the instance
 * @return {Axios} A new instance of Axios
 */
function createInstance(defaultConfig) {
  var context = new Axios(defaultConfig);
  var instance = bind(Axios.prototype.request, context);

  // Copy axios.prototype to instance
  utils.extend(instance, Axios.prototype, context);

  // Copy context to instance
  utils.extend(instance, context);

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

  return instance;
}

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

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

// Expose Cancel & CancelToken
axios.CanceledError = require('./cancel/CanceledError');
axios.CancelToken = require('./cancel/CancelToken');
axios.isCancel = require('./cancel/isCancel');
axios.VERSION = require('./env/data').version;
axios.toFormData = require('./helpers/toFormData');

// Expose AxiosError class
axios.AxiosError = require('../lib/core/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 = require('./helpers/spread');

// Expose isAxiosError
axios.isAxiosError = require('./helpers/isAxiosError');

module.exports = axios;

// Allow use of default import syntax in TypeScript
module.exports.default = axios;

拦截器的设计

在new Axios对象的时候就创建一个interceptors对象,里面包括一个request和response。

InterceptorManager定义了一个原形方法,use方法,那么此时我们就可以通过下面的:

axios.interceptors.request.use((config)=>{}, (error) => {})
axios.interceptors.response.use((res)=>{}, (error) => {})
配置拦截器了。
 
我们定义的拦截器就被放在axios.interceptors.request.handlers和axios.interceptors.request.handlers里面,因为它以数组存放,所有我们可以添加多个拦截器。
下面是部分源码,参考着看。
function Axios(instanceConfig) {
  this.defaults = instanceConfig;
  this.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager()
  };
}

function InterceptorManager() {
  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
 */
InterceptorManager.prototype.use = function use(fulfilled, rejected, options) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected,
    synchronous: options ? options.synchronous : false,
    runWhen: options ? options.runWhen : null
  });
  return this.handlers.length - 1;
};

那么拦截器在何时调用呢?当然是在我们发request的时候调用。

这里他使用了一个chain数组来存放我们的拦截器,具体有以下几步:

1. chain数组默认放了两个值[dispatchRequest, undefined],dispatchRequest是我们实际发请求的

2.  this.interceptors.request.handlers.forEach(i=>chain.unshift(i.fullfilled, i.rejected))

此时把request的拦截器放在了数组的头部(unshift操作)。

3. this.interceptors.response.handlers.forEach(i=>chain.push(i.fulfilled, i.rejected))

此时把response的拦截器放在了数组的尾部(push操作)。

假如request和response拦截器各有两个,最终形成的数组如下:

[

interceptors.request.handlers[1].fulfilled,

interceptors.request.handlers[1].rejected,

interceptors.request.handlers[0].fulfilled,

interceptors.request.handlers[0].rejected,

dispatchRequest,

undefined,

interceptors.response.handlers[0].fulfilled,

interceptors.response.handlers[0].rejected,

interceptors.response.handlers[1].fulfilled,

interceptors.response.handlers[1].rejected,

]

此时就可以使用promise的then来进行顺序调用了,先执行request拦截器再执行sendRequest再执行response拦截器,每次执行结果都是一个promise,形成链式关系。

promise = Promise.resolve(config);// config将作为request拦截器fullfilled的参数
while (chain.length) {
  promise = promise.then(chain.shift(), chain.shift());
}

Axios.prototype.request = function 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);

  // Set config.method
  if (config.method) {
    config.method = config.method.toLowerCase();
  } else if (this.defaults.method) {
    config.method = this.defaults.method.toLowerCase();
  } else {
    config.method = 'get';
  }

  var transitional = config.transitional;

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

  // filter out skipped interceptors
  var requestInterceptorChain = [];
  var 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);
  });

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

  var promise;

  if (!synchronousRequestInterceptors) {
    var chain = [dispatchRequest, undefined];

    Array.prototype.unshift.apply(chain, requestInterceptorChain);
    chain = chain.concat(responseInterceptorChain);

    promise = Promise.resolve(config);
    while (chain.length) {
      promise = promise.then(chain.shift(), chain.shift());
    }

    return promise;
  }


  var newConfig = config;
  while (requestInterceptorChain.length) {
    var onFulfilled = requestInterceptorChain.shift();
    var onRejected = requestInterceptorChain.shift();
    try {
      newConfig = onFulfilled(newConfig);
    } catch (error) {
      onRejected(error);
      break;
    }
  }

  try {
    promise = dispatchRequest(newConfig);
  } catch (error) {
    return Promise.reject(error);
  }

  while (responseInterceptorChain.length) {
    promise = promise.then(responseInterceptorChain.shift(), responseInterceptorChain.shift());
  }

  return promise;
};

cancelToken的设计

我们在request的config参数会加入如下信息

let cancel;

axios.get('/user/12345', {
  cancelToken: new CancelToken(function executor(c) {
    // An executor function receives a cancel function as a parameter
    cancel = c;
  })
});

cancel(); // 取消请求

来看看newCancelToken干了啥?

上面的cancel=c中的c就是下面加粗部分的代码,也就是说当我们调用cancel()也就是下面加粗部分会被调用,可以看到它竟然resolve了一个Promise。有resolve得有then啊,那then在哪呢?

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

    token.reason = new CanceledError(message);
    resolvePromise(token.reason);
  });

继续往下分析:
上面的resolvePromise来自一个新的promise的resolve,也就是相互关联了,cancel被调用的时候,this.promise的then就会被调用。
this.promise = new Promise(function promiseExecutor(resolve) {
  resolvePromise = resolve;
});
 
我们可以看到CancelToken上面维护了一个subscribe和unsubscribe方法。通过subscribe和unsubscribe维护了一个listener调用列表。当cancel()被调用的时候就会循环listener。
 
function CancelToken(executor) {
  if (typeof executor !== 'function') {
    throw new TypeError('executor must be a function.');
  }

  var resolvePromise;

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

  var token = this;

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

    var i;
    var l = token._listeners.length;

    for (i = 0; i < l; i++) {
      token._listeners[i](cancel);
    }
    token._listeners = null;
  });

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

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

    return promise;
  };

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

    token.reason = new CanceledError(message);
    resolvePromise(token.reason);
  });
}

/**
 * Throws a `CanceledError` if cancellation has been requested.
 */
CancelToken.prototype.throwIfRequested = function throwIfRequested() {
  if (this.reason) {
    throw this.reason;
  }
};

/**
 * Subscribe to the cancel signal
 */

CancelToken.prototype.subscribe = function subscribe(listener) {
  if (this.reason) {
    listener(this.reason);
    return;
  }

  if (this._listeners) {
    this._listeners.push(listener);
  } else {
    this._listeners = [listener];
  }
};

/**
 * Unsubscribe from the cancel signal
 */

CancelToken.prototype.unsubscribe = function unsubscribe(listener) {
  if (!this._listeners) {
    return;
  }
  var index = this._listeners.indexOf(listener);
  if (index !== -1) {
    this._listeners.splice(index, 1);
  }
};

我们去dispatchRequest里面看看,因为我们想取消发送只能在此处进行。可以看到发请求时会调用一个叫做throwIfCancellationRequested的方法,它会调用我们在cancelToken里面定义的一个原形方法throwIfRequested。

可以看到throwIfCancellationRequested在三个地方调用了,

  1. 在发请求之前判断
  2. 在服务器返回之后判断
  3. 在请求出问题的时候判断
function dispatchRequest(config) {
  throwIfCancellationRequested(config);

  // Ensure headers exist
  config.headers = config.headers || {};

  // Transform request data
  config.data = transformData.call(
    config,
    config.data,
    config.headers,
    config.transformRequest
  );

  // Flatten headers
  config.headers = utils.merge(
    config.headers.common || {},
    config.headers[config.method] || {},
    config.headers
  );

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

  var adapter = config.adapter || defaults.adapter;

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

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

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

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

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

在看一下xhr.js, 会通过subscribe去订阅onCanceled事件,如果有人cancel,就会调用abort()方法终止请求

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

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

 

相关标签:
  • axios
  • cancelToken
  • interceptor
0人点赞

发表评论

当前游客模式,请登陆发言

所有评论(0)