没事读读码…
  axios在业务中请求用得比较多了,这个周末就花点时间阅读下源码,先进github开启sourcegraph插件,找到核心实现目录:

那从哪里开始慢慢看比较好呢?我个人其实比较倾向从工具方法里入手,可以学习其中的编码思路,发现平常自己实现相同功能容易忽略的细节。了解了其中的工具方法作用后,对我们后续盘核心代码的逻辑也会轻松不少。
helpers
该文件目录下主要是一些对我们发送请求时,拼接URL、处理参数的工具方法。
bind.js
| 1 | ; | 
  其实就是在ES5下,实现了一个bind,返回一个闭包,最后返回绑定this的调用返回结果。
adapters
根据文件目录,及README.md,该模块主要进行浏览器端及Node端的网络请求兼容,它会处理成一个request请求进行dispatch,并且当response返回后,处理返回一个Promise。
cancel
这个模块进行了基本的Cancel对象封装,主要用于判断请求是否被Cancel以及如何进行请求的Cancel。
Cancel.js
  构造了一个Cancel函数,初始化message信息,原型链上重载了toString方法以及添加了__CANCEL__标记变量:
| 1 | ; | 
CancelToken.js
  CancelToken是一个用于进行请求取消操作的对象:
| 1 | ; | 
  通过源码阅读我们可以看出,CancelToken也是一个构造函数,它接受一个执行函数executor,内部通过一个Promise实例控制,比较有趣的是它将Promise状态改变的回调函数执行句柄提出,并在executor中执行后再触发。并在执行函数触发时,首次执行会调用之前Cancel的构造函数,并将生成的实例赋值给当前上下文的reason属性。在下一次再触发时,若已有reason内容,则不再执行该函数。
  除此之外,在axios中真正使用CancelToken往往不是直接通过new构造,而是使用函数的静态方法source,它通过注入一个函数的形式,从CancelToken内部拿到了真正进行取消动作的cancel函数,并将其赋值给了外层source函数内部的cancel变量,最终返回了这个CancelToken实例以及与其匹配的取消方法,形成一个闭包。
isCancel.js
  判断任务是否已被取消,从构造上来说,它的入参是Cancel函数构造的实例,返回值的处理也比较巧妙,运用!!真值处理,因为如果value为undefined,返回的就是undefined,真值处理会进行布尔值转换。
| 1 | ; | 
axios是怎么做请求取消的?
  了解了以上构造函数实现后,我们知道了核心是CancelToken.source方法以及CancelToken实例原型链上对应的__CANCEL__属性。再看看真实应用的例子from README.md:
| 1 | const CancelToken = axios.CancelToken; | 
  从使用demo上,我们知道在axios进行请求时,我们的CancelToken实例作为参数传入,而取消句柄则在外部被我们开发者在对应业务场景消费,简单来说就是我们可以决定何时取消。
那么实际我们的axios实例是如何运用以上的token和对应的cancel呢?
  1. Axios构造函数实现核心请求方法request,本质上不同的请求方法(get、put、post等)最终都会调用这个request。
| 1 | // Provide aliases for supported request methods | 
  2. request中间的请求体会通过interceptors形成一个中间件进行处理,这里我们先不看具体中间件做了什么动作,聚焦到里面实际发起请求的函数dispatchRequest,代码中的adapter其实就是浏览器和Node端对应真实发起请求的方法封装,它们最终会返回一个Promise。
| 1 | // 省略部分 ... | 
  在这个Promise的回调中,我们会判断config配置是否有cancelToken属性,即是否配置了取消请求的方法,如果有,则检查其中是否已经存在了取消的reason属性,这个reason属性根据前文,它是一个Cancel对象,内部是我们外部调用取消方法传入的msg。即如果这个cancelToken实例此时存在reason了,它就会抛出这个内部的reason即Cancel对象。
| 1 | /** | 
  3. adapter我们就以浏览器端的实现xhr.js文件来看,可以看出浏览器端就是去构造一个XMLHttpRequest,在真实发送send前,判断config中是否有cancelToken,有则以cancelToken内部的promise来进行异步控制。通过前文的了解我们知道,在具体业务场景我们调用cancelToken匹配的cancel方法进行请求终止,其实就是将CancelToken内部的promise进行resolve并使得在adapter中进入异步等待回调的promise立马回调,将XMLHttpRequest的请求实例通过abort方法终止。然后这个axios请求的Promise将会reject,里面的属性就是cancelToken的reason。同时释放request内存。实际上在请求的各个阶段结束后,如错误、终止、完成都会清空request指向,这也是CancelToken的promise回调中发现request已经阶段完成就啥都不做的判断逻辑:
| 1 | module.exports = function xhrAdapter(config) { | 
utils.js
该文件下,主要是一些判断类型的工具方法:

一个基本的判断类型思路:
| 1 | // 获取toString 方便后续调用 避免每次都用.重新查找获取 | 
其中一些我个人觉得可以学习一下的:
isObject
| 1 | /** | 
isFormData
| 1 | /** | 
isStandardBrowserEnv
  判断当前运行环境,WEB端依赖window及document,Native端核心则是在navigator.product上。
| 1 | /** | 
forEach
| 1 | /** | 
normalizeHeaderName
| 1 | function normalizeHeaderName(headers, normalizedName) { | 
extend
相当于把第二个参数内的内容继承到第一个参数中。
| 1 | /** | 
axios.js
这个文件可以理解成一个入口,我们的逻辑都通过这个文件引入,再通过模块化导出供我们使用这个请求库。
先看生成实例的方法:
| 1 | /** | 
  首先通过Axios构造方法new一个实例,其中传入默认的配置参数defaults。该配置参数又通过defaults.js导出。
  defaults.js比较关键,它对Axios的默认请求配置进行了封装,并且其中做了浏览器端和Node端的兼容:
| 1 | function getDefaultAdapter() { | 
| 1 | var defaults = { | 
  再看看Axios这个“类”做了哪些事情:
| 1 | /** | 
  先声明一个Axios函数,实例上有两个属性,一个是实例的配置信息this.defaults,另一个则是发起请求和接收响应的拦截器this.interceptors。
InterceptorManager.js
  InterceptorManager这个拦截器又做了什么呢?
| 1 | function InterceptorManager() { | 
  实例中有一个handlers初始化空数组进行拦截内容添加,原型链上添加use、eject、forEach方法,分别进行Promise处理状态入栈,清除以及迭代函数调用。
  然后在Axios的原型链上配置方法request、getUri:
| 1 | /** | 
  实际上核心还是request函数,它在内部通过chain数组结构编织了一个请求管道,默认没有配置拦截器this.interceptors.request及this.interceptors.response时,初始化值为[dispatchRequest, undefined]。这等价于请求时,在promise的回调中直接触发resolve状态的dispatchRequest,入参即请求配置config。
  那么当我们分别在request和response中添加拦截,塞入中间件,就是下面这样的编排结构:

  结合请求前的处理,我们可以知道从队列首部到dispatchRequest,是给我们中间处理config的,因为dispatchRequest最终接收参数就是一个config。常见应用场景如获取app token,确认当前token是否有效等。而undefined之后到队尾的配置就是处理dispatchRequest的promise返回的response的内容,如果有相关场景依赖,我们便可以在返回的response上构造,处理起来也是生成新的Promise返回,数据返回新的response。
  综上,chain通过管道的概念,形成了一个promise链式调用,队列首到dispatchRequest进行config中间处理,undefined到队列尾部进行请求返回的response的中间处理。
