Axios 源码解读 —— 网络请求篇

489 阅读6分钟

上一章我们介绍了 Axios 源码解读 —— request 篇,这一章我们来介绍 Axios 实际发起网络请求的部分吧,也就是 dispatchRequest 方法。

dispatchRequest

这个方法定义也比较简单(如下图)

image

第 29 行 —— 取消请求

我们来逐行解析每一行代码所做的事情吧,首先是第 29 行的取消请求。(如下)

throwIfCancellationRequested(config);

这个动作不仅仅在发起正式请求前做了一次,而且在请求成功和请求失败时都做了一次。

只要是被 cancel 的请求,都是不会进入到成功或失败回调处理中的。(如下图)

image

throwIfCancellationRequested 函数所做的事情,就是检测该请求是否被取消,如果被取消则抛出一个错误,并阻止代码继续向下执行。(如下)

function throwIfCancellationRequested(config) {
  if (config.cancelToken) {
    // 检测请求是否被取消,然后决定是否抛出错误
    config.cancelToken.throwIfRequested();
  }

  if (config.signal && config.signal.aborted) {
    // 抛出错误
    throw new Cancel('canceled');
  }
}

当然,整套 CancelToken 的实现还是有一些复杂的(复杂在回调处理),如果有人感兴趣的话,可以单独讲讲这一部分的处理,这里就先不做展开了。

第 35 ~ 40 行 —— 处理请求 data

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

这里用到了一个 transformData 方法,主要的作用是合并 data,并且可以使用配置的 transformRequest 方法进行合并(如下图)。

image

transformData 方法就是遍历传入的 transformRequest 方法,将 dataheaders 作为 transformRequest 的入参,然后将返回结果复制给 config.data,作为最后请求要发送的 data 内容。

第 43 ~ 54 行 —— 合并请求 headers

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

上面的代码主要做了两件事情:

  • 第一件事情就是将通用的 headerscommon headers)和对应方法的 headers 以及原 headers 做了一个合并;
  • 第二件事情就是在合并完成后,将多余的 headers 配置删除。

第 56 ~ 58 行 —— 发起真实请求

var adapter = config.adapter || defaults.adapter;

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

这里使用了配置的 adapter,这其实就是发起请求的方法。

image

比如两个默认的请求方法,在浏览器端运行时使用的是 XMLHttpRequest,在 Node 端运行时使用的是 http 模块。

知道这一项配置可以做什么呢?

你可以在这里传入一个自定义的请求方法,例如在客户端使用 fetch 封装,而不是 XMLHttpRequest

你也可以在这里传入一个你封装好的 mock 方法,返回的都是本地 json 数据,用于 mock 数据,这样你就不用额外搭建一个 mock 服务啦。

......

言归正传,我们还是来看看 axios 默认的两个 adapter 吧,本文会重点讲解客户端 adapter —— xhrAdapter

客户端 adapter —— xhrAdapter

我们按照惯例,对客户端 adapter —— xhrAdapter 逐行进行解析。

发起请求前的准备工作

image

行数描述
16 ~ 18收集请求数据信息(requestData)、请求头信息(requestHeaders)、返回体类型(responseType
19 ~ 28cancelToken 做处理,在请求完成的时候取消这个订阅,释放内存;还有对 signal 的处理,这个 signal 在文档中已经看不到了,应该也是用于 abort 请求的。
30 ~ 32对于 FormData 的请求移除 Content-Type 请求头,让浏览器自动设置请求头。

设置鉴权信息

image

首先在第 34 行,创建了一个 XMLHttpRequest 实例,用于后续的网络请求。

然后在第 37 ~ 41 行,设置了用于 HTTP basic authentication 鉴权的信息,从这一段我们可以学到简单的 HTTP basic authentication 鉴权知识。

首先对 password 进行 encodeURLComponent 转义编码,然后再将用户名与密码按照规则拼接后,使用了 btoa用户名与密码 转成了 base64 编码。

如果以后你要自己做这类事情的话,可以再翻到这一章节找到这部分的代码内容,抄一遍。

拼接请求 url

image

首先是用 buildFullPath 方法拼接了完整的请求 url。(如下图)

image

可以看到,该方法对绝对路径的 urlisAbsoluteURL()) 是不会拼接 baseURL 的。

然后,axios 还用 buildURL 方法将 params 参数拼接到了 url 中 —— 也就是我们常说的 query 参数。(如下)

function buildURL(url, params, paramsSerializer) {
  /*eslint no-param-reassign:0*/
  if (!params) {
    return url;
  }

  var serializedParams;
  if (paramsSerializer) {
    serializedParams = paramsSerializer(params);
  } else if (utils.isURLSearchParams(params)) {
    serializedParams = params.toString();
  } else {
    // ...

    serializedParams = parts.join('&');
  }

  if (serializedParams) {
    // ...
    // 在这里,将处理后的参数作为 query 查询参数拼接到 url 中
    url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams;
  }

  return url;
};

这也是为什么使用 axios 时,GET 方法的参数要设置在 params 字段中。

然后,使用 request.open 方法初始化了一个请求。

响应回调函数

接下来就是比较核心的响应回调函数了(如下图)

image

行数描述
54获取所有响应头
55获取响应内容
57 ~ 64设置 promise resolve 内容,就是你经常 console.log 出来的那些东西,你应该很眼熟

设置其他回调函数

后面基本上都是设置 XMLHttpRequest 对象的一些回调函数了。(如下图)

image

比较晚入行接触前端的,可能对 XMLHttpRequest 实例这些事件所做的事情不太清楚,可以参考一下 XMLHttpRequest 文档

最后,发送这个请求。(如下)

request.send(requestData);

回到 dispatchRequest

从上面可以看出,最后 dispatchRequest 调用 adapter 后,拿到了下面这些数据。

{
  data: responseData,
  status: request.status,
  statusText: request.statusText,
  headers: responseHeaders,
  config: config,
  request: request
};

然后我们来看看 dispatchRequest 内部是如何处理这一段数据的吧。(如下图)

image

行数描述
59/72处理被 CancelToken 取消的请求,如果请求已取消则不继续向下执行
62~67将响应结果通过 transformResponse 进行转换,得到处理后的响应数据
69将响应结果返回

这样一来,整个 axios 的请求流程就梳理清晰了,我们画一张流程图来梳理一下。(如下图)

image

这里对 Node 端的 adapter 就不进行展开讲解了,和客户端的差异主要在于下面几点:

  1. 使用了 Nodehttp 模块发起请求。
  2. 可以通过 proxy 设置代理服务器,请求将会发送到代理服务器。

小结

好了,我们对 axios 源码的解读就到这里为止了。

可以看出,axios 虽然是目前最流行的、比较强大的网络请求框架,但是源码看起来还是比较清爽易读的,建议大家可以自己按照文章的思路去看看。

下一章,我们会实现一个简易的 axios 框架,包含 axios 的一些核心功能,将 axios 源码解读系列收官。

最后一件事

如果您已经看到这里了,希望您还是点个赞再走吧~

您的点赞是对作者的最大鼓励,也可以让更多人看到本篇文章!

如果觉得本文对您有帮助,请帮忙在 github 上点亮 star 鼓励一下吧!