axios源码记录 学习笔记

本文详细记录了axios的源码学习过程,从初始化、axios构造函数、工具函数到请求的创建、配置处理、拦截器管理和实际的请求发送。重点分析了request方法、dispatchRequest函数以及xhr请求的实现。通过源码解析,揭示了axios如何处理配置、合并默认设置、注册拦截器以及发起网络请求的内部机制。
摘要由CSDN通过智能技术生成

axios源码记录

前言

最近都再看算法的内容,突然发现自己axios方面的知识还没有学习好,也攒下了不少偏笔记,在这里浅浅记录一下自己学习的历程。

axios 初始化

当我们npm install axios后,打开axios的 package.json可以找得到axios的入口文件 "main": "index.js"

我们顺着这个文件去一步步理清axios的内容

index.js

图片.png

随之我们进入index.js的文件中

axios.js

引入部分

图片.png

文件最上面的部分是各种文件的引入,包括配默认置文件,工具函数,Axios主文件

下面的部分中可以看得出来,axios是一个函数

是生成实例对象 axiosaxios.Axiosaxios.create等。

图片.png

函数的内容如图

  • 定义了一个 Axios实例
  • 使用bind函数创建instance,返回一个新的 wrap 函数,
  • 使用extend函数将Axios原型和实例上的内容绑定到instance上,
    • 也就是为什么 有 axios.get 等别名方法
    • 也就是为什么默认配置 axios.defaults 和拦截器 axios.interceptors 可以使用的原因
  • 定义调用axios.create()时执行的内容
  • 最后返回实例对象

剩下的文件中的部分就是为 axios函数添加方法

取消相关API实现,还有allspread、导出等实现。
图片.png
这些还没有去研究,之后再写吧

工具函数

源码中有一些自定义的工具函数,和平时我们认知的有一点不一样,把他们单独拿出来讲一下

bind函数

图片.png

这里重写的bind函数并不像我们平时的bind函数一样,参数的位置有一丢丢不一样,但也容易理解

utils.extend

我们再utils文件中找到extend

图片.png

forEach

图片.png

加了大量的注释,应该一下子就能看懂了,

调用forEach时

forEach(遍历的内容,处理的函数(值,索引,遍历的内容){处理细节}, 是否全是自身的key)

看完这三个 方法工具 上面的 createInstance函数 应该就能看懂了

merge

把多个对象的值都放到一起来。

图片.png

核心–构造函数 Axios

在上面的axios.js的文件中

图片.png

这一步是非常关键的,其中出现了很多次的 Axios。同时这也是 axios的关键所在

Axios构造函数

图片.png

Axios文件中主要内容在这个Axios构造函数的构建。

里面包括了

  • 调用 axios('url',config),axios({config})的配置方式
  • 默认配置和传入配置的合并
  • 请求方法的设置
  • 请求头配置
  • 拦截器注册
  • 拦截器操作的promise
  • get,post,delete等方法绑定到实例的原型上
  • 暴露处Axios

这些内容,如果把这些内容掌握了axios就差不多了

配置对象config的处理

无论是直接通过axios('url',{config})的方法调用 axios 还是 axios({config}) 或者是 axios.get()的方式使用axios , 都绕不过config的配置

直接调用axios({})或者axios(“url”,{})

图片.png

使用这两种方式时,原理上就是直接调用Axios的request方法

这时会对调用时,传入的参数进行判断,

如果第一个参数是字符串形式 即用 axios("url",{})的方法,就会将第一个参数中的 url 复制到 第二个参数的config中

如果第一个字符串不存在 即用 axios({})的方式,就将第一个参数的值,直接给了config,

之后还要将传入配置和axios默认配置进行合并。

通过 axios.get()调用

图片.png

Axios的原型中绑定了一些列的请求方法,调用这些方法会返回一个 this.request同样也是执行请求,会将调用时传入的一系列参数都合并起来,组成一个 config对象

mergeConfig.js

图片.png

图片.png

图片.png
mergeConfig中有争对不同的配置型,确定合并的方式,有一个Map的映射表,还有处理的forEach

将所有的配置文件(用户传入的,默认配置的等等)进行一个合并,最终返回合并后的结果

request()

Axios.js中,Axios构造函数中的 request()时最重要的模块,下面是我对源码详细注释后的 request实力函数

  request(configOrUrl, config) {
    /*eslint no-param-reassign:0*/
    // Allow for axios('example/url'[, config]) a la fetch API
    // 对url的设置,争对调用axios时是否将 url 分出去,做出区分配置
    if (typeof configOrUrl === 'string') {
      config = config || {};
      config.url = configOrUrl;
    } else {
      config = configOrUrl || {};
    }

    //将传入的配置和默认配置合并
    // mergeConfig 会将参数中的配置合并在一起,
    config = mergeConfig(this.defaults, config);

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

    // Set config.method
    // 确定 config 中的方法名
    config.method = (config.method || this.defaults.method || 'get').toLowerCase();

    // 下面的这堆都是对header进行处理,最后生成请求用的headers
    // Flatten headers
    const defaultHeaders = config.headers && utils.merge(
      config.headers.common,
      config.headers[config.method]
    );

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

    config.headers = new AxiosHeaders(config.headers, defaultHeaders);

    // filter out skipped interceptors
    const requestInterceptorChain = [];
    // 同步请求拦截器
    let synchronousRequestInterceptors = true;

    // 这里的 forEach 不是数组的forEach方法,而是 request 创建的实例 InterceptorManager 中的 实例方法forEach,
    // 遍历的是其中的实例属性 handlers数组。下面的响应拦截器也是一样的
    this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
      // 如果用户没有特殊的进行配置的话这里一般不会执行
      if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) {
        return;
      }

      // interceptor.synchronous 是用户 通过 axios.interceptors.request.use 注册时填入的第三个参数 option中的 synchronous 值,一般默认为false
      // 表示不是同步全球拦截器
      synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous;

      // 在这里将 请求拦截器的内容添加到 chrequestInterceptorChain 中,一组一组的添加
      // 请求拦截器的添加是通过 unshift 添加到 chain 的前面的
      // 由于这种unshift的方法,后注册的请求拦截器会 比 先注册的拦截器的执行快
      requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
    });

    const responseInterceptorChain = [];
    // 在这里将 响应拦截器的内容添加到 responseInterceptorChain 中,一组一组的添加

    this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
      // 这里也是将 response 中的内容全部添加到 chain 的后面
      responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
    });

    let promise;
    let i = 0;
    let len;

    // 这里的 synchronousRequestInterceptors 同步请求拦截器 一般指都为 false,然后执行里面的内容
    if (!synchronousRequestInterceptors) {
      // 先把 chain 初始化,第一个是请求,第二个是undefined
      const chain = [dispatchRequest.bind(this), undefined];
      // 然后在前面添加 请求拦截器的 reject和resolve
      chain.unshift.apply(chain, requestInterceptorChain);
      // 然后在后面添加 请求拦截器的 reject和resolve
      chain.push.apply(chain, responseInterceptorChain);
      len = chain.length;

      // 创建一个成功的 promise 并把 合并好的config 作为参数传给.then的成功回调
      // 这个config 参数就是 给到所有拦截器的config参数
      promise = Promise.resolve(config);

      while (i < len) {
        // 最后再把这些内容 依次调用.then执行,
        // 把 chain中的内容, 一组一组的(拦截器的成功回调和失败回调为一组),添加到.then的 resolve函数和reject函数中
        promise = promise.then(chain[i++], chain[i++]);
      }
      // 最终返回一个这个promise对象
      return promise;
    }

    // 从这里往下就是设置的是  synchronousRequestInterceptors 为true时执行的内容
    // 会以同步的方式执行拦截器,而不是 .then 后放入异步队列
    len = requestInterceptorChain.length;

    let newConfig = config;

    i = 0;

    while (i < len) {
      const onFulfilled = requestInterceptorChain[i++];
      const onRejected = requestInterceptorChain[i++];
      try {
        newConfig = onFulfilled(newConfig);
      } catch (error) {
        onRejected.call(this, error);
        break;
      }
    }

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

    i = 0;
    len = responseInterceptorChain.length;

    while (i < len) {
      promise = promise.then(responseInterceptorChain[i++], responseInterceptorChain[i++]);
    }

    return promise;
  }

我们将如上的内容解读完后,了解到这一步中就是形成了一个 promise链,其中的 dispatchRequest 才是请求的更深层。

dispatchRequest

dispatchRequest.js
图片.png

dispatchRequest中主要是对 配置data 和 返回的data的处理,发送请求时在 adapter 里面进一步划分的,

值得注意的内容是 config.transformRequest 中一系列函数 对data content-type的处理

adapter

defaults/index.js中我们可以找到 adapter的内容

图片.png

我们顺着这个线索一步步往下

同样在 defaults/index.js

图片.png
这里就是通过两个if语句来判断当前的环境是 浏览器环境还是node环境 。通常都是使用 xhr 在浏览器端发送请求

adapters/index.js

图片.png
最后让我们进入到 xhr 的请求中看一看具体的发送请求过程

xhr.js

这里是我们真正发起请求的地方

export default function xhrAdapter(config) {
  // 请求返回一个promise对象
  return new Promise(function dispatchXhrRequest(resolve, reject) {
    // 在这里获取config中的data内容,配置requestHeaders,responseType等等
    let requestData = config.data;
    const requestHeaders = AxiosHeaders.from(config.headers).normalize();
    const responseType = config.responseType;
    let onCanceled;

    function done() {
      if (config.cancelToken) {
        config.cancelToken.unsubscribe(onCanceled);
      }

      if (config.signal) {
        config.signal.removeEventListener('abort', onCanceled);
      }
    }

    if (utils.isFormData(requestData) && platform.isStandardBrowserEnv) {
      requestHeaders.setContentType(false); // Let the browser set it
    }

    // 在这里我们就嫩见到我们所熟知的 ajax 请求的部分 new XMLHttpRequest
    let request = new XMLHttpRequest();

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

    // 构建一个完整的路径
    const fullPath = buildFullPath(config.baseURL, config.url);

    // 发起初始化一个ajax请求,method url、参数、序列化函数
    request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true);

    // Set the request timeout in MS
    // 设置请求超时
    request.timeout = config.timeout;

    // 注意 promise的 resolve() 和 reject() 函数是在这里面执行的
    function onloadend() {
      // 
      if (!request) {
        return;
      }
      // Prepare the response
      // 准备 请求返回的结果 response
      const responseHeaders = AxiosHeaders.from(
        'getAllResponseHeaders' in request && request.getAllResponseHeaders()
      );
      const responseData = !responseType || responseType === 'text' || responseType === 'json' ?
        request.responseText : request.response;  //在这里判断 responseType 是什么值,然后获取对应的 请求结果
      // 把结果进行封装一下
      const response = {
        data: responseData,
        status: request.status,
        statusText: request.statusText,
        headers: responseHeaders,
        config,
        request
      };
      // 当readystatus == 4时,才会执行
      // settle的参数为 成功回调,失败回调,response
      settle(function _resolve(value) {
        resolve(value);
        done();
      }, function _reject(err) {
        reject(err);
        done();
      }, response);

      // Clean up request
      request = null;
    }

    // 
    if ('onloadend' in request) {
      // Use onloadend if available
      // ajax 的 onloadend 会在请求结束时触发
      request.onloadend = onloadend;
    } else {
      // Listen for ready state to emulate onloadend
      // 如果没有定义 ajax的onloadend的话,就在下面 监听 onreadystatechange,status 如果结果成功(状态时4)的话,就异步执行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);
      };
    }

    // 下面都是定义在不同状态下的 ajax 的处理情况 例如 调用 ajax 的onabort,onerror,ontimeout等等
    // Handle browser request cancellation (as opposed to a manual cancellation)
    request.onabort = function handleAbort() {
      if (!request) {
        return;
      }

      reject(new AxiosError('Request aborted', AxiosError.ECONNABORTED, config, request));

      // Clean up request
      request = null;
    };

    // 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(new AxiosError('Network Error', AxiosError.ERR_NETWORK, config, request));

      // Clean up request
      request = null;
    };

    // Handle timeout
    // 设置超时的情况
    request.ontimeout = function handleTimeout() {
      let timeoutErrorMessage = config.timeout ? 'timeout of ' + config.timeout + 'ms exceeded' : 'timeout exceeded';
      const transitional = config.transitional || transitionalDefaults;
      if (config.timeoutErrorMessage) {
        timeoutErrorMessage = config.timeoutErrorMessage;
      }
      reject(new AxiosError(
        timeoutErrorMessage,
        transitional.clarifyTimeoutError ? AxiosError.ETIMEDOUT : AxiosError.ECONNABORTED,
        config,
        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 (platform.isStandardBrowserEnv) {
      // Add xsrf header
      const xsrfValue = (config.withCredentials || isURLSameOrigin(fullPath))
        && config.xsrfCookieName && cookies.read(config.xsrfCookieName);

      if (xsrfValue) {
        requestHeaders.set(config.xsrfHeaderName, xsrfValue);
      }
    }

    // Remove Content-Type if data is undefined
    requestData === undefined && requestHeaders.setContentType(null);

    // Add headers to the request
    if ('setRequestHeader' in request) {
      utils.forEach(requestHeaders.toJSON(), function setRequestHeader(val, key) {
        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 (responseType && responseType !== 'json') {
      request.responseType = config.responseType;
    }

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

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

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

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

    const protocol = parseProtocol(fullPath);

    if (protocol && platform.protocols.indexOf(protocol) === -1) {
      reject(new AxiosError('Unsupported protocol ' + protocol + ':', AxiosError.ERR_BAD_REQUEST, config));
      return;
    }


    // Send the request
    // 在最后,发起请求,
    request.send(requestData || null);
  });

其中设置了很多 ajax 的处理函数,最主要的内容是 onloadend函数 的定义。还有ajax请求发起的几个重要过程。在上面的代码中,已经把需要注意的地方用注释说明了,(建议放到编译器中方便看)

后续还有很多内容需要补充,待更新…

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值