初探axios源码

1,121 阅读5分钟

axios源码分析

源码目录分析

  • axios.js: axios导出的文件入口
  • Axios.js: 添加原型方法
  • default.js: 默认的axios配置
  • adapters:适配浏览器和非浏览器请求
  • cancel:取消请求类

axios的工作流程

axios的工作原理

1. axios

// axios 和 instance 都会调用createInstance
function createInstance(defaultConfig) {

  var context = new Axios(defaultConfig);

  // 绑定Axios原型上的方法request
  // Axios.prototype.request.bind(context), 返回的是方法。
  var instance = bind(Axios.prototype.request, context);

  // 将Axios上原型的方法复制到instance上,使得axios能调用get,post,put,delete等方法 axios.get()
  utils.extend(instance, Axios.prototype, context);

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

  return instance;
}

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

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

// 同样支持axios.create(),创建instance,跟axios没有太大区别,都是通过调用createInstance来的,但是instance没有axios后面添加的cancel,cancelToken等方法
axios.create = function create(instanceConfig) {
  return createInstance(mergeConfig(axios.defaults, instanceConfig));
};

// 为axios添加新的属性方法,取消请求。
axios.Cancel = require('./cancel/Cancel');
axios.CancelToken = require('./cancel/CancelToken');
axios.isCancel = require('./cancel/isCancel');
  1. 无论是``axios还是instance都会调用createInstance函数,构造Axios的实例,但axios从本质上来说还是一个函数,通过extend,bindAxios`上复制所有的原型扩展方法。
  2. axiosinstance是有区别的,axios包含了后面扩展的CancelCancelToken等方法。
  3. 通过这种方式,在createInstance返回的axios函数的属性上,就已经挂在了Axios原型上的方法和属性了。

2. Axios 核心类

function Axios(instanceConfig) {
  this.defaults = instanceConfig;
  // 创建两个拦截器。
  this.interceptors = {
    request: new InterceptorManager(), // 数组存放收集
    response: new InterceptorManager() // 数组存放收集
  };
}

InterceptorManager.prototype.use = function use(fulfilled, rejected) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected
  });
  return this.handlers.length - 1;
};
  1. 收集请求拦截,返回拦截,支持多个拦截操作,触发时会一并进行调用。调用的顺序有所不同,请求拦截是unshift插入,倒序执行,返回拦截是push插入,顺序执行。


// 核心,实现请求
Axios.prototype.request = function request(config) {
  /*eslint no-param-reassign:0*/
 
  if (typeof config === 'string') { // axios('/xxx') 默认是get方法
    config = arguments[1] || {};
    config.url = arguments[0];
  } else {
    // config是个对象
    config = config || {};
  }
  // 合并配置
  config = mergeConfig(this.defaults, config);

  // 设置请求方式,统一转成小写
  if (config.method) {
    config.method = config.method.toLowerCase();
  } else if (this.defaults.method) {
    config.method = this.defaults.method.toLowerCase();
  } else {
    config.method = 'get';
  }

  // Hook up interceptors middleware
  var chain = [dispatchRequest, undefined];
  var promise = Promise.resolve(config);

  // 拦截器会在then返回之前执行
  // 请求拦截器是倒序执行的,unshift
  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    chain.unshift(interceptor.fulfilled, interceptor.rejected);
  });

  // 返回拦截器是正序执行的,push
  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    chain.push(interceptor.fulfilled, interceptor.rejected);
  });
  
  // 支持链式调用
  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift());
  }

  return promise;
};
  1. request做了哪些事,支持config是个字符串,表现形式为axios('/xxx'), 也可以是object, 会去合并mergeConfig(this.defaults, config)默认值。
  2. 统一配置请求方式为toLowerCase, 默认为get方式
  3. 调用interceptors存放的方法
  4. chain.length不为0, 支持链式调用,最终返回promise

3 dispatchRequest

module.exports = function dispatchRequest(config) {
  throwIfCancellationRequested(config);

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

  // Transform request data

  // data, headers 作为参数放到 transformRequest 中执行
  config.data = transformData(
    config.data,
    config.headers,
    config.transformRequest
  );

  // 扁平化操作。
  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];
    }
  );
	
  // 判断出是node环境还是浏览器环境
  var adapter = config.adapter || defaults.adapter;

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

    // Transform response data
    response.data = transformData(
      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(
          reason.response.data,
          reason.response.headers,
          config.transformResponse
        );
      }
    }

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

  1. dispatchRequest主要做了哪些,将data、headers放入到transformData中执行,再将headers扁平化处理
  2. transformRequest函数数组,对data数据进行转换
  3. adapter判断出是node坏境还是浏览器环境,adapter.then(), 将返回response数据。
// ajax请求,http请求
function getDefaultAdapter() {
  var adapter;
  if (typeof XMLHttpRequest !== 'undefined') {
    // For browsers use XHR adapter
    adapter = require('./adapters/xhr');
  } else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
    // For node use HTTP adapter
    adapter = require('./adapters/http');
  }
  return adapter;
}

4 xhr

module.exports = function xhrAdapter(config) {
  return new Promise(function dispatchXhrRequest(resolve, reject) {
    var requestData = config.data;
    var requestHeaders = config.headers;


    if (utils.isFormData(requestData)) {
      delete requestHeaders['Content-Type']; // Let the browser set it
    }

    var request = new XMLHttpRequest();

    // HTTP basic authentication
    if (config.auth) {
      var username = config.auth.username || '';
      var password = config.auth.password ? unescape(encodeURIComponent(config.auth.password)) : '';
      requestHeaders.Authorization = 'Basic ' + btoa(username + ':' + password);
    }

    // 合并请求连接,可以通过{baseURL: 'xxx'}或者axios.defaults.baseURL = 'https://xxx';设置
    var fullPath = buildFullPath(config.baseURL, config.url);
    request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true);

    // Set the request timeout in MS
    request.timeout = config.timeout;

    // Listen for ready state
    // // onreadystatechange的状态码
    // 0: 初始化, XMLHttpRequest对象还没有完成初始化
    // 1: 载入, XMLHttpRequest对象开始发送请求
    // 2: 载入完成, XMLHttpRequest对象的请求发送完成
    // 3: 解析, XMLHttpRequest对象开始读取服务器的响应
    // 4: 完成, XMLHttpRequest对象读取服务器响应结束
    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;
      }

      // Prepare the response
      var responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders(request.getAllResponseHeaders()) : null;
      var responseData = !config.responseType || config.responseType === 'text' ? request.responseText : request.response;
      var response = {
        data: responseData,
        status: request.status,
        statusText: request.statusText,
        headers: responseHeaders,
        config: config,
        request: request
      };
      // 当返回的status状态码在[200, 300)之间,返回resolve(response)
      //default.js中 validateStatus: function validateStatus(status) {
      // return status >= 200 && status < 300;
      // }
      settle(resolve, reject, response);

      // Clean up request
      request = null;
    };

    // Handle browser request cancellation (as opposed to a manual cancellation)
    // 取消请求
    request.onabort = function handleAbort() {
      if (!request) {
        return;
      }

      reject(createError('Request aborted', config, 'ECONNABORTED', request));

      // Clean up request
      request = null;
    };

    // 处理网络错误问题
    request.onerror = function handleError() {
      // Real errors are hidden from us by the browser
      // onerror should only fire if it's a network error
      reject(createError('Network Error', config, null, request));

      // Clean up request
      request = null;
    };

    // 处理请求超时问题。
    request.ontimeout = function handleTimeout() {
      var timeoutErrorMessage = 'timeout of ' + config.timeout + 'ms exceeded';
      if (config.timeoutErrorMessage) {
        timeoutErrorMessage = config.timeoutErrorMessage;
      }
      reject(createError(timeoutErrorMessage, config, 'ECONNABORTED',
        request));

      // Clean up request
      request = null;
    };

    // Add xsrf header
    // This is only done if running in a standard browser environment.
    // Specifically not if we're in a web worker, or react-native.
    // 浏览器
    if (utils.isStandardBrowserEnv()) {
      // Add xsrf header
      var xsrfValue = (config.withCredentials || isURLSameOrigin(fullPath)) && config.xsrfCookieName ?
        cookies.read(config.xsrfCookieName) :
        undefined;

      if (xsrfValue) {
        requestHeaders[config.xsrfHeaderName] = xsrfValue;
      }
    }

    // Add headers to the request
    if ('setRequestHeader' in request) {
      utils.forEach(requestHeaders, function setRequestHeader(val, key) {
        if (typeof requestData === 'undefined' && key.toLowerCase() === 'content-type') {
          // Remove Content-Type if data is undefined
          delete requestHeaders[key];
        } else {
          // Otherwise add header to the request
          request.setRequestHeader(key, val);
        }
      });
    }

    // Add withCredentials to request if needed
    if (!utils.isUndefined(config.withCredentials)) {
      request.withCredentials = !!config.withCredentials;
    }

    // Add responseType to request if needed
    if (config.responseType) {
      try {
        request.responseType = config.responseType;
      } catch (e) {
        // Expected DOMException thrown by browsers not compatible XMLHttpRequest Level 2.
        // But, this can be suppressed for 'json' type as it can be parsed by default 'transformResponse' function.
        if (config.responseType !== 'json') {
          throw e;
        }
      }
    }

    // Handle progress if needed
    if (typeof config.onDownloadProgress === 'function') {
      request.addEventListener('progress', config.onDownloadProgress);
    }

    // Not all browsers support upload events
    if (typeof config.onUploadProgress === 'function' && request.upload) {
      request.upload.addEventListener('progress', config.onUploadProgress);
    }
    // 取消请求。
    if (config.cancelToken) {
      // Handle cancellation
      config.cancelToken.promise.then(function onCanceled(cancel) {
        if (!request) {
          return;
        }

        request.abort();
        reject(cancel);
        // Clean up request
        request = null;
      });
    }

    if (!requestData) {
      requestData = null;
    }

    // Send the request
    request.send(requestData);
  });
};
  1. 创建new XMLHttpRequest()实例

  2. 合并请求连接,可以通过{baseURL: 'xxx'}或者axios.defaults.baseURL = 'https://xxx';设置

  3. 监听onreadystatechange变化,

    • 0: 初始化, XMLHttpRequest对象还没有完成初始化

    • 1: 载入, XMLHttpRequest对象开始发送请求

    • 2: 载入完成, XMLHttpRequest对象的请求发送完成

    • 3: 解析, XMLHttpRequest对象开始读取服务器的响应

    • 4: 完成, XMLHttpRequest对象读取服务器响应结束

  4. 收集response返回数据,执行settle函数,调用validateStatus(status)判断是否在[200, 300),成功状态后resolve(response)返回数据。

结合代码

<!DOCTYPE html>
<html>

<head>
  <title>axios</title>
</head>

<body>
  <div>
    <button onclick="handleGet()">get</button>
    <button onclick="handlePost()">post</button>
  </div>
  <script src="../源码/axios/dist/axios.js"></script>
  <script>
    axios.defaults.baseURL = 'http://localhost:8081';
    axios.interceptors.request.use((config) => {
      console.log('request1')
      return config
    })
    axios.interceptors.request.use((config) => {
      console.log('request2')
      return config
    })
    axios.interceptors.response.use((response) => {
      console.log('response1')
      return response
    })
    axios.interceptors.response.use((response) => {
      console.log('response2')
      return response
    })
    function handleGet(params) {
      axios.get('/list').then(json => {
        console.log(json);
      })
    }
    function handlePost(params) {
      console.log('handlePost')
    }

    // 返回的顺序
    // request2
    // request1
    // response1
    // response2
    // {data: {…}, status: 200, statusText: "OK", headers: {…}, config: {…}, …}
  </script>
</body>

</html>

5 取消请求

function CancelToken(executor) {
  if (typeof executor !== 'function') {
    throw new TypeError('executor must be a function.');
  }

  var resolvePromise;
  // 创建了内置的promise对象,将resovle控制放在executor中去控制
  //eg: let resolveHandle;
  // new Promise((resolve) => {
  //   resolveHandle = resolve;
  // }).then((val) => {
  //   console.log('resolve', val);
  // });
  // resolveHandle('ok');
  this.promise = new Promise(function promiseExecutor(resolve) {
    resolvePromise = resolve;
  });

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

    token.reason = new Cancel(message);
    resolvePromise(token.reason);
  });
}
  1. cancelToken主要做了什么事情,内置创建了promise对象,通过resolvePromise=resolve,将控制放在executor中去控制,config.cancelToken.promise.then在异步中去取消请求
if (config.cancelToken) {
      // Handle cancellation
      // abort方法只会对未进行相应的请求进行取消,已响应的请求执行了也不会有什么作用
      config.cancelToken.promise.then(function onCanceled(cancel) {
        if (!request) {
          return;
        }

        request.abort();
        reject(cancel);
        // Clean up request
        request = null;
      });
    }
  1. cancelToken只会对未进行相应的请求进行取消,已响应的请求执行了也不会有什么作用。
    let CancelToken = axios.CancelToken;
    let map = new Map()
    axios.defaults.baseURL = 'http://localhost:8081';
    axios.interceptors.request.use((config) => {
      const f = map.get(config.url)
      if (f) {
        f()
        map.delete(config.url)
      }
      config.cancelToken = new CancelToken(function executor(c) {
        map.set(config.url, c)
      })
      return config
    })
    axios.interceptors.response.use((response) => {
      const f = map.get(config.url)
      if (f) {
        map.delete(config.url)
      }
      return response
    })

    function handleGet1(params) {
      axios.get('/cancel-list1').then(json => {
        console.log(json)
      });
    }

    function handleGet2(params) {
      axios.get('/cancel-list2').then(json => {
        console.log(json)
      });
    }

    function handlePost(params) {
      console.log('handlePost')
    }

    function handleCancel() {
      console.log(123);
      cancel()
    }

总结

  1. axios是一个基于promiseHttp库,在浏览器环境使用XHR,在node环境中使用http模块发送请求,在axios,可以对请求,返回进行拦截,支持链式拦截,请求拦截为倒序拦截,使用unshift往前插入数据,返回拦截使用push方法,正常拦截。

  2. 取消请求的是一个异步分离的设计方案,利用promise的异步效果,通过切换promise的状态,从而达到异步取消请求的实现。

  3. axiosaxios.create的差别是增加了cancelToken等方法。