Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

axios源码分析 #65

Open
1h1h opened this issue Jan 19, 2021 · 0 comments
Open

axios源码分析 #65

1h1h opened this issue Jan 19, 2021 · 0 comments

Comments

@1h1h
Copy link

1h1h commented Jan 19, 2021

axios源码分析

源码目录分析

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

axios的工作流程

image-20210119143531511

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,bind`将`Axios`上复制所有的原型扩展方法。
  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);
  });
}

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;
      });
    }

cancelToken只会对未进行相应的请求进行取消,已响应的请求执行了也不会有什么作用。

总结

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

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

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant