axios 源码阅读

272 阅读6分钟

思维导图

axios.png

核心代码

request

所有的核心代码就是 request 函数,deletegetheadoptionspostputpatch 都是通过 request 函数来创建的。

  1. 构建 config
  2. 构建拦截器
  3. 执行请求拦截器
  4. 执行请求
  5. 执行响应拦截器

构建 config

允许 axios 可以像 axios('url'[, config]) 这样请求,所以第一个参数可能为 string

如果像这样请求的话,没有给出 config, 那么请求方式默认为 get

 // Allow for axios('example/url'[, config]) a la fetch API
 if (typeof config === 'string') {
   config = arguments[1] || {};
   config.url = arguments[0];
 } else {
   config = config || {};
 }
 ​
 config = mergeConfig(this.defaults, config);
 ​
 // Set config.method, default: get
 if (config.method) {
   config.method = config.method.toLowerCase();
 } else if (this.defaults.method) {
   config.method = this.defaults.method.toLowerCase();
 } else {
   config.method = 'get';
 }

构建拦截器

InterceptorManager.js 这个文件中,提供了拦截器的管理方式。

拦截器的数据结构

 interface inteceptor {
   fulfilled: function;
   rejected: function;
   synchronous: boolean;
   runWhen // 不知道这个是什么
 }

use 方法

我们通过 use 方法来创建一个拦截器。

方法接收三个参数:

  1. fulfilled: 成功的方法
  2. rejected: 失败的方法
  3. options: 配置参数
    1. synchronous: 是否为异步拦截器
    2. runWhen: 现在并不知道是啥

通过 handlers 这个数组来管理整个拦截器。

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

构建请求拦截器

现在再来看如何构建请求拦截器。

  1. 判断是否为异步请求拦截器
  2. fulfilled 以及 rejected 推入到 requestInterceptorChain

以上就完成了对请求拦截器的构建。

 // 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 newConfig = config;
 // 执行请求拦截器
 while (requestInterceptorChain.length) {
   var onFulfilled = requestInterceptorChain.shift();
   var onRejected = requestInterceptorChain.shift();
   try {
     newConfig = onFulfilled(newConfig);
   } catch (error) {
     onRejected(error);
     break;
   }
 }

异步请求拦截器

以下是执行异步请求拦截器的方法

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

执行请求

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

执行响应拦截器

需要注意的是,在 promise 执行完后才能执行响应拦截器,所以应该使用 then 方法。

 // 执行响应拦截器
 while (responseInterceptorChain.length) {
   promise = promise.then(
     responseInterceptorChain.shift(),
     responseInterceptorChain.shift()
   );
 }

dispatchRequest

所以可以看出,执行请求的方法就是 dispatchRequest 这个方法

同样此方法也是有顺序的:

  1. 构建 headers
  2. 转换请求数据
  3. 发送请求
  4. 转换响应数据

构建 headers

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

转换请求数据

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

发送请求

需要注意的是此方法内会选择 adapter,默认:浏览器为 XMLHttpRequest,node.js 为 http

 return adapter(config).then(function onAdapterResolution(response) {
   // ...
   return response;
 }, function onAdapterRejection(reason) {
   // ...
   return Promise.reject(reason);
 });

转换响应数据

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

adapter

发送请求的函数在 adapter 中,这里以 xhr 举例。

判断

通过 getDefaultAdapter() 函数来判断什么环境用什么 adapter

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

认证

认证比较简单,Basic Authenticatin 认证。将 username 以及 password 两个结合进行 Base64 编码。

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

设置 timeout

表示该请求的最大请求时间,若超出该时间,请求会自动终止。

 request.timeout = config.timeout;

新建完成请求时的回调函数

构建 response 的响应数据结构。

 function onloadend() {
   if (!request) {
     return;
   }
   // Prepare the response
   var responseHeaders =
       'getAllResponseHeaders' in request
   ? parseHeaders(request.getAllResponseHeaders())
   : null;
   
   var responseData =
       !responseType || responseType === 'text' || responseType === 'json'
         ? request.responseText
         : request.response;
   
   var response = {
     data: responseData,
     status: request.status,
     statusText: request.statusText,
     headers: responseHeaders,
     config: config,
     request: request,
   };
 ​
   settle(resolve, reject, 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);
   };
 }

设置终止回调函数

MDN XMLHttpRequestEventTarget.onabort

 request.onabort = function handleAbort() {
   if (!request) {
     return;
   }
 ​
   reject(createError('Request aborted', config, 'ECONNABORTED', request));
 ​
   // Clean up request
   request = null;
 };

设置错误处理回调函数

MDN XMLHttpRequestEventTarget.onerror

 // Handle low level network errors
 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;
 };

设置超时处理回调函数

MDN XMLHttpRequestEventTarget.ontimeout

 // Handle timeout
 request.ontimeout = function handleTimeout() {
   var timeoutErrorMessage = config.timeout
   ? 'timeout of ' + config.timeout + 'ms exceeded'
   : 'timeout exceeded';
   if (config.timeoutErrorMessage) {
     timeoutErrorMessage = config.timeoutErrorMessage;
   }
   reject(
     createError(
       timeoutErrorMessage,
       config,
       config.transitional && config.transitional.clarifyTimeoutError
       ? 'ETIMEDOUT'
       : 'ECONNABORTED',
       request
     )
   );
 ​
   // Clean up request
   request = null;
 };

设置 xsrf

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

设置下载的进度

MDN XMLHttpRequest: progress event

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

设置上传的进度

MDN XMLHttpRequestEventTarget.upload

 // Not all browsers support upload events
 if (typeof config.onUploadProgress === 'function' && request.upload) {
   request.upload.addEventListener('progress', config.onUploadProgress);
 }

取消了请求

直接使用 XMLHttpRequestEventTarget.abort() 方法取消请求。主要为 cancelToken 里面的内容,见下方。

 if (config.cancelToken) {
   // Handle cancellation
   config.cancelToken.promise.then(function onCanceled(cancel) {
     if (!request) {
       return;
     }
 ​
     request.abort();
     reject(cancel);
     // Clean up request
     request = null;
   });
 }

发送请求

 // Send the request
 request.send(requestData);

取消请求

官方的例子是这样使用的:

 const CancelToken = axios.CancelToken;
 const source = CancelToken.source();
 ​
 axios.get('/user/12345', {
   cancelToken: source.token
 }).catch(function (thrown) {
   if (axios.isCancel(thrown)) {
     console.log('Request canceled', thrown.message);
   } else {
     // handle error
   }
 });
 ​
 axios.post('/user/12345', {
   name: 'new name'
 }, {
   cancelToken: source.token
 })
 ​
 // cancel the request (the message parameter is optional)
 source.cancel('Operation canceled by the user.');

首先调用了 source 方法,然后在请求的时候加入了 cancelToken 参数,最后调用 cancel 方法。

source

 /**
  * Returns an object that contains a new `CancelToken` and a function that, when called,
  * cancels the `CancelToken`.
  */
 CancelToken.source = function source() {
   var cancel;
   var token = new CancelToken(function executor(c) {
     cancel = c;
   });
   return {
     token: token,
     cancel: cancel
   };
 };

此函数返回了一个对象包含 token 以及 cancel

token 是一个 CancelToken 实例,参数为一个函数。

CancelToken 构造函数

 /**
  * A `CancelToken` is an object that can be used to request cancellation of an operation.
  *
  * @class
  * @param {Function} executor The executor function.
  */
 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;
   executor(function cancel(message) {
     if (token.reason) {
       // Cancellation has already been requested
       return;
     }
 ​
     token.reason = new Cancel(message);
     resolvePromise(token.reason);
   });
 }

构造函数定义了一个 promise 属性,在 xhr.js 中使用:

 if (config.cancelToken) {
   // Handle cancellation
   config.cancelToken.promise.then(function onCanceled(cancel) {
     if (!request) {
       return;
     }
 ​
     request.abort();
     reject(cancel);
     // Clean up request
     request = null;
   });
 }

onCancel 就是取消请求的具体代码。如果没有 request 则直接返回;否则,终止请求后返回。

剩下的内容,之后看心情补充吧。可能会继续添加 xsrf 以及 transformData 的内容