axios源码 请求实现

86 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第22天,点击查看活动详情aaaaaa

前言

终于要把坑填完了,axios的主要源码大体就结束了,之后会在已经写好的文章上面更新内容。axios源码的体量虽然不是很大,单包含了大部分的框架所用到的东西,花了好几天时间,断断续续的看玩后,理清了即便思路,感觉真的收获很大。在面试之前再拿出来复习复习

dispatchRequest

dispatchRequest.js

image-20221021165425173

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

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

adapter

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

image-20221021165802576

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

同样在 defaults/index.js

image-20221021192451340

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

adapters/index.js

image-20221021192943394

最后让我们进入到 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) {

      // 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;
        }
​
        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;
    };
​//这里把 xsrf 的一部分内容删掉了
    // Send the request
    // 在最后,发起请求,
    request.send(requestData || null);
  });

我对里面重要代码的部分进行了一系列的解释,大概照着思路就能看明白。

  • new XMLHttpRequest,
  • request.open()
  • request.timeout
  • request.ontimeout()
  • onloadend函数中对应的 onreadystatechange 的改变时
  • 还有最后的send()的发起
  • 这些都是ajax的关键,也是axios在浏览器端的底层请求。要好好弄明白了

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

后续还有很多内容需要补充,等待细节更新!