【axios】源码分析


主要目的是对 博客中axios的基本使用进行分析,提供依据。

0. 源码目录结构

├── /dist/ # 项目输出目录
├── /lib/ # 项目源码目录
│ ├── /adapters/ # 定义请求的适配器 xhr、http
│ │ ├── http.js # 实现http适配器(包装http包)
│ │ └── xhr.js # 实现xhr适配器(包装xhr对象)
│ ├── /cancel/ # 定义取消功能
│ ├── /core/ # 一些核心功能
│ │ ├── Axios.js # axios的核心主类
│ │ ├── dispatchRequest.js # 用来调用http请求适配器方法发送请求的函数
│ │ ├── InterceptorManager.js # 拦截器的管理器
│ │ └── settle.js # 根据http响应状态,改变Promise的状态
│ ├── /helpers/ # 一些辅助方法
│ ├── axios.js # 对外暴露接口
│ ├── defaults.js # axios的默认配置
│ └── utils.js # 公用工具
├── package.json # 项目信息
├── index.d.ts # 配置TypeScript的声明文件
└── index.js # 入口文件

1. axios为什么能有多种发请求的方法?

axios函数对应的是Axios.prototype.request方法通过bind(Axiox的实例)产生的函数
axios有Axios原型上的所有发特定类型请求的方法: get()/post()/put()/delete()
axios有Axios的实例上的所有属性: defaults/interceptors
后面又添加了create()/CancelToken()/all()
/**axios.js
 * Create an instance of Axios
 *
 * @param {Object} defaultConfig The default config for the instance
 * @return {Axios} A new instance of Axios
 */
function createInstance(defaultConfig) {
    
    
  /* 
  创建Axios的实例
      原型对象上有一些用来发请求的方法: get()/post()/put()/delete()/request()
      自身上有2个重要属性: defaults/interceptors
  */  
  var context = new Axios(defaultConfig);
  // axios和axios.create()对应的就是request函数
  // Axios.prototype.request.bind(context)
  var instance = bind(Axios.prototype.request, context); // axios
  //bind返回一个原函数(Axios.prototype.request)的拷贝,并拥有指定的 this 值(Axios对象context)和初始参数(可无)。


  // 将Axios原型对象上的方法拷贝到instance上: request()/get()/post()/put()/delete()
  utils.extend(instance, Axios.prototype, context); 
  // 将Axios实例对象上的属性拷贝到instance上: defaults和interceptors属性
  utils.extend(instance, context);

  return instance;
}
// Create the default instance to be exported
var axios = createInstance(defaults);
// Expose Cancel & CancelToken
axios.Cancel = require('./cancel/Cancel');
axios.CancelToken = require('./cancel/CancelToken');
axios.isCancel = require('./cancel/isCancel');
// Expose all/spread
axios.all = function all(promises) {
    
    
  return Promise.all(promises);
};
/**utils.js 
 * Extends object a by mutably adding to it the properties of object b.
 *
 * @param {Object} a The object to be extended
 * @param {Object} b The object to copy properties from
 * @param {Object} thisArg The object to bind function to
 * @return {Object} The resulting value of object a
 */
function extend(a, b, thisArg) {
    
    
  forEach(b, function assignValue(val, key) {
    
    
    if (thisArg && typeof val === 'function') {
    
    
      a[key] = bind(val, thisArg);
    } else {
    
    
      a[key] = val;
    }
  });
  return a;
}
/* bind.js
绑定函数的this
*/
module.exports = function bind(fn, thisArg) {
    
    
  return function wrap() {
    
    
    var args = new Array(arguments.length);  //arguments为函数内部可用的arguments对象
    //arguments对象是所有(非箭头)函数中都可用的局部变量。你可以使用arguments对象在函数中引用函数的参数。此对象包含传递给函数的每个参数,第一个参数在索引0处。
    for (var i = 0; i < args.length; i++) {
    
    
      args[i] = arguments[i];
    }
    return fn.apply(thisArg, args); 
    //返回调用有指定this值(thisArg)和参数(args)的函数(fn)的结果
  };
};

2. axios.create()返回的对象与axios的区别?

相同: 
    都是一个能发任意请求的函数: request(config)
    都有发特定请求的各种方法: get()/post()/put()/delete()
    都有默认配置和拦截器的属性: defaults/interceptors
不同:
    默认匹配的值很可能不一样
    instance没有axios后面添加的一引起方法: create()/CancelToken()/all()
// Factory for creating new instances
axios.create = function create(instanceConfig) {
    
    
  return createInstance(mergeConfig(axios.defaults, instanceConfig));
};

3. axios运行的整体流程

在这里插入图片描述

4. Axios.prototype.request()都做了什么?

/**
 * 用于发请求的函数
 * 我们使用的axios就是此函数bind()返回的函数
 * Dispatch a request
 *
 * @param {Object} config The config specific for this request (merged with this.defaults)
 */
Axios.prototype.request = function request(config) {
    
    
  /*eslint no-param-reassign:0*/
  // 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);
  // 添加method配置, 默认为get
  config.method = config.method ? config.method.toLowerCase() : 'get';

  /*
  创建用于保存请求/响应拦截函数的数组
  数组的中间放发送请求的函数
  数组的左边放请求拦截器函数(成功/失败)
  数组的右边放响应拦截器函数
  */
  var chain = [dispatchRequest, undefined];
  var promise = Promise.resolve(config);

  // 后添加的请求拦截器保存在数组的前面
  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    
    
    chain.unshift(interceptor.fulfilled, interceptor.rejected);  //fufilled即resolved
  });
  // 后添加的响应拦截器保存在数组的后面
  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    
    
    chain.push(interceptor.fulfilled, interceptor.rejected);
  });
  
  // 通过promise的then()串连起所有的请求拦截器/请求方法/响应拦截器
  while (chain.length) {
    
    
    promise = promise.then(chain.shift(), chain.shift());
  }

  // 返回用来指定我们的onResolved和onRejected的promise
  return promise;
};

PS:请求拦截器在头部插入,响应拦截器在尾部插入。初始链为var chain = [dispatchRequest, undefined];是因为添加取出都是成对的

5. dispatchrequest()都做了什么?

请求响应数据体格式转换

/* dispatchRequest.js
对config中的data进行必要的转换处理
设置相应的Content-Type请求头
*/
config.data = transformData(
  config.data,
  config.headers,
  config.transformRequest //执行请求体数据转换函数
);
...
var adapter = config.adapter || defaults.adapter;

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

    /* 
    对response中还没有解析的data数据进行解析
    json字符串解析为js对象/数组
    */
    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);
  });
//default.js
function getDefaultAdapter() {
    
    
  var adapter;
  // Only Node.JS has a process variable that is of [[Class]] process
  if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
    
    
    // For node use HTTP adapter
    adapter = require('./adapters/http');
  } else if (typeof XMLHttpRequest !== 'undefined') {
    
    
    // For browsers use XHR adapter
    adapter = require('./adapters/xhr');
  }
  return adapter;
}
var defaults = {
    
    
// 得到当前环境对应的请求适配器
  adapter: getDefaultAdapter(),

  // 请求转换器
  transformRequest: [function transformRequest(data, headers) {
    
    
  ...
  // 如果data是对象, 指定请求体参数格式为json, 并将参数数据对象转换为json
    if (utils.isObject(data)) {
    
    
      setContentTypeIfUnset(headers, 'application/json;charset=utf-8');
      return JSON.stringify(data);
    }
    return data;
  }],

  // 响应数据转换器: 解析字符串类型的data数据
  transformResponse: [function transformResponse(data) {
    
    
    /*eslint no-param-reassign:0*/
    if (typeof data === 'string') {
    
    
      try {
    
    
        data = JSON.parse(data);
      } catch (e) {
    
     /* Ignore */ }
    }
    return data;
  }],
  ...

6. xhrAdapter()做了什么?

整体流程: request(config)  ===> dispatchRequest(config) ===> xhrAdapter(config)
request(config): 将请求拦截器 / dispatchRequest() / 响应拦截器 通过promise链串连起来, 返回promise
dispatchRequest(config): 转换请求数据 ==  => 调用xhrAdapter()发请求 ===> 请求返回promise=>.then中对响应数据transformdata转换. 返回promise
xhrAdapter(config): 创建XHR对象, 根据config进行相应设置, 发送特定请求, 并接收响应数据, 返回promise

7. axios的请求/响应拦截器是什么?

请求拦截器: 在真正发请求前, 可以对请求进行检查或配置进行特定处理的函数, 包括成功/失败的函数, 传递的必须是config
响应拦截器: 在请求返回后, 可以对响应数据进行特定处理的函数, 包括成功/失败的函数, 传递的默认是response

8. axios的请求/响应数据转换器是什么?

请求转换器: 对请求头和请求体数据进行特定处理的函数
    setContentTypeIfUnset(headers, 'application/json;charset=utf-8');
    return JSON.stringify(data)
响应转换器: 将响应体json字符串解析为js对象或数组的函数
    response.data = JSON.parse(response.data)

9. response的整体结构

{
    data,
    status,
    statusText,
    headers,
    config,
    request
}

10. error的整体结构

{
    message,
    request,
    response
}

11. 如何取消已经发送的请求?

1.当配置了cancelToken对象时, 保存cancel函数
    创建一个用于将来中断请求的cancelPromise
    并定义了一个用于取消请求的cancel函数
    将cancel函数传递出来
2.调用cancel()取消请求
    执行cancel函数, 传入错误信息message
    内部会让cancelPromise变为成功, 且成功的值为一个Cancel对象
    在cancelPromise的成功回调中中断请求, 并让发请求的proimse失败, 失败的reason为Cancel对象
//html界面
    // 添加请求拦截器
    axios.interceptors.request.use((config) => {
    
    
      // 在准备发请求前, 取消未完成的请求
      if (typeof cancel==='function') {
    
    
          cancel('取消请求')
      }
      // 添加一个cancelToken的配置
      config.cancelToken = new axios.CancelToken((c) => {
    
     // c是用于取消当前请求的函数
        // 保存取消函数, 用于之后可能需要取消当前请求
        cancel = c
      })

      return config
    })
//xhr.js
    // 如果配置了cancelToken
    if (config.cancelToken) {
    
    
      // 指定用于中断请求的回调函数
      config.cancelToken.promise.then(function onCanceled(cancel) {
    
    
        if (!request) {
    
    
          return;
        }
        // 中断请求
        request.abort();
        // 让请求的promise失败
        reject(cancel);
        // Clean up request
        request = null;
      });
    }
/**
 * 用于取消请求的对象构造函数
 * 
 * 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.');
  }

  // 为取消请求准备一个promise对象, 并保存resolve函数
  var resolvePromise;
  this.promise = new Promise(function promiseExecutor(resolve) {
    
    
    resolvePromise = resolve;
  });

  // 保存当前token对象
  var token = this;

  // 立即执行接收的执行器函数, 并传入用于取消请求的cancel函数
  executor(function cancel(message) {
    
    
    // 如果token中有reason了, 说明请求已取消
    if (token.reason) {
    
    
      // Cancellation has already been requested
      return;
    } 
    // 将token的reason指定为一个Cancel对象
    token.reason = new Cancel(message);
    // 将取消请求的promise指定为成功, 值为reason
    resolvePromise(token.reason);
  });
}

猜你喜欢

转载自blog.csdn.net/qq_40265247/article/details/109092901