欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

关于axios请求原理分析

程序员文章站 2022-03-07 21:45:13
Axios的特点:请求返回Promise,可以很方便的进行链式调用可以附加拦截器:请求拦截器、响应拦截器可以随时取消未完成的请求客户端支持防御 XSRF(跨站请求伪造)会帮你转换请求数据和响应数据在node.js中发送请求使用http,在浏览器使用XMLHttpRequest...

什么是axios

axios是基于Promise封装的一个前端请求库,可以用在node.js和浏览器中。

axios的特点:

  • 请求返回Promise,可以很方便的进行链式调用
  • 可以附加拦截器:请求拦截器、响应拦截器
  • 可以随时取消未完成的请求
  • 客户端支持防御 XSRF(跨站请求伪造)
  • 会帮你转换请求数据和响应数据
  • 在node.js中发送请求使用http,在浏览器使用XMLHttpRequest

安装

$ npm install axios

简单使用:

get请求

axios.get('/user', {
    params: {
      ID: 12345
    }
  })
  .then(response => {
 	console.log(response);
  })
  .catch(error => {
 	console.log(error);
  });

post请求

axios.post('/user', {
    firstName: 'Fred',
    lastName: 'Flintstone'
  })
  .then(response => {
 	console.log(response);
  })
  .catch(error => {
 	console.log(error);
  });

因为是基于 promise 封装的,所以 axios 也支持 promise.all 的写法,同时 axios.spread 会 依次帮你展开请求相应结果

function getUserAccount() {
  return axios.get('/user/12345');
}

function getUserPermissions() {
  return axios.get('/user/12345/permissions');
}

axios.all([getUserAccount(), getUserPermissions()])
  .then(axios.spread(function (acct, perms) {
    // 两个请求现在都执行完成
  }));

深入学习axios

在学完axios的用法,以及上手使用了axios之后,我心里就有些疑问:

  1. axios是怎么附加的拦截器呢?
  2. axios 的取消请求具体又是怎么实现的?
  3. axios和我们自己调用create方法产生的实例有什么区别呢?

首先要搞清axios发送请求的整个流程。我们稍微分析一下就能知道,请求拦截器肯定是在发送请求之前被调用的,而响应拦截器肯定是在请求结束,拿到返回结果的时候,再联想到官方文档介绍 axios 是基于promise 的, 那这整个流程肯定就是基于promise的链式调用了嘛。

去 github 拿到 axios 的源码 ,然后我们来捋一捋:

拿到源码的之后,我发现了两个axios文件,一个首字母大写,一个小写!我们日常写代码,不都是用的小写axios,或者创建的instance吗?

关于axios请求原理分析
然后我去看了下入口文件

关于axios请求原理分析

关于axios请求原理分析
入口文件不是小写的 axios.js 嘛?所以这个大写的Axios是个啥?

带着这个疑问,我们先去看下axios.js,默认导出了axios,axios又是createInstance方法的返回值,而且后面一句axios.create,这不就是axios创建实例的方法嘛?同样是调用了createInstance方法,区别是传入的config配置不同。

再往后面看,发现在var 了 axios后,又往axios上加了取消请求的相关方法,和 all 等等一些方法。而我们自己再代码中调用create方法返回的实例并没有这些!这就解决了我的第三个疑问!

关于axios请求原理分析

axios和我们自己调用create方法产生的实例有什么区别

看完以上代码我们可以总结下,先说相同点

  • 都能发任意请求
  • 都有默认配置和拦截器属性

不同点

  • 默认匹配的值可能不同,由于使用create方法创建的instance也支持传入配置项,这里的配置项在合并时会覆盖掉默认的axios.default
  • instance原型对象上没有取消请求相关,以及axios.all,axios.spead等方法

然后我们去看下createInstance 方法里,再给个特写

关于axios请求原理分析
这里定义的context 是 new 的 大写Axios,后面用于return 的 instance调用了bind,里面传入的是Axios原型对象上的request,和大写Axios的实例,再到后面两句代码都跟Axios有关。也就是说axios从功能上来说,完全就是Axios的实例啊,然后去 Axios.js文件里看下:

关于axios请求原理分析

拦截器是怎么附加的

第一段代码 定义了defaults 默认配置,以及拦截器。然后我好奇的点开 InterceptorManager 看下

关于axios请求原理分析

InterceptorManager 里定义了 一个 handlers 数组,再往下看 一个 use 函数被添加到它的原型对象上。从 use 函数 的 fulfilled 和 rejected 两个参数的命名也可以猜出来, 这两个参数就是我们添加拦截器时传入的promise,看下下面添加拦截器的代码就懂了。
use 函数的作用也很明显 把这两个函数添加到 handlers 数组中,所以 axios 的拦截器是可以添加多个的。

// 添加请求拦截器
axios.interceptors.request.use(function (config) {
    // 在发送请求之前做些什么
    return config;
  }, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
  });

// 添加响应拦截器
axios.interceptors.response.use(function (response) {
    // 对响应数据做点什么
    return response;
  }, function (error) {
    // 对响应错误做点什么
    return Promise.reject(error);
  });

最后 use 函数返回了 handlers 数组的长度减1,刚开始我是没看懂的,后来看到关于返回值的注释 An ID used to remove interceptor later 翻译过来就是 用于以后删除拦截器的ID ,也就是被加入到handlers 数组中对应的索引呐。
联想到 axios 是可以删除 拦截器的,下面 eject 函数的作用也就明显了。

const myInterceptor = axios.interceptors.request.use(function () {/*...*/});
axios.interceptors.request.eject(myInterceptor);

关于axios请求原理分析
至于 InterceptorManager 原型对象上的 foreach 函数,看起来像是循环 handles 数组,执行传入的 fn 函数 。这个暂且放下。

回到Axios .js文件中,下面代码往Axios原型对象上添加了一个 request 方法
关于axios请求原理分析

这个request方法暂且也放下,再往下看

关于axios请求原理分析
这里调用了工具类中的foreach方法, 为Axios的原型对象上添加对应的请求方法,如get,post等,我们调用的Axios.get,post等方法,就来源于这些代码。
继续往下看,Axios原型对象上的这些方法内,实际调用了this.request ,这里的this.request 就是我们上面跳过的那个 request方法,也就是说我们调用axios发起请求后,所有的操作都走了这个 request 方法,来看下下面这个request方法(英文注释都被我换成了中文)

关于axios请求原理分析
request方法接收一个 config 参数,功能如其名,明显这就是个配置项。下面的第一段代码,意为 根据config 类型做相应的处理,然后合并传入的 config 与 defaults.config,这段跳过。
第二段意为 设置请求方法,默认为 get请求 ,这段跳过。
第三段重点来了,先定义一个数组,数组中有两个默认值,一个是请求分发的函数,从 dispatchRequest.js 引入,这里先跳过,第二个是 undefined ,为什么是个undefined 呢 ?
先往后看
后面定义一个变量,存储promise成功的方法,这个promise里存的就是上面合并的config,随后循环数组,嗯????…这个foreach有点不对啊,这好像不是Array对象原型上的循环方法啊, f12进去发现,这丫不是 InterceptorManager 原型对象上的方法嘛。

关于axios请求原理分析
不过也确实是循环拦截器数组,调用传入的 fn函数,返回去看这个fn函数,把请求拦截器按照后来先进的顺序加入到 chain 的头部,响应拦截器按照先来先进的顺序加入到尾部。中间部分不就是之前定义数组时候,就放进去的 请求 分发嘛。

关于axios请求原理分析

再往下,就是循环chain数组,每次取两个元素,刚好一个是成功的回调,一个是失败的回调,用promise链把整个流程链接起来,形如 :

(resolve0,reject0)=>(resolve1,reject1)=>(dispatchRequest, undefined)=>(resolve3,reject3)

而且贯穿整个promise请求的参数就是请求配置的config。这时候大概也就明白为什么会定义一个undefined了,因为每次都是从chain数组内取两个元素出来,而拦截器传入的都是两个回调,所以dispatchRequest,后面就需要定义一个undefined来占位置。以免位置出现偏差。而promise状态的成功失败与否都由dispatchRequest内处理。

当我们调用axios.get 或者 post 的时候,promise链步骤应该是这样

  1. 请求拦截器 (成功,或者失败的回调)
  2. 请求分发(成功的回调, undefined)
  3. 响应拦截器 (成功,或者失败的回调)
  4. 最终我们代码自己定义的成功,或者失败的回调

捋到这为止,就解开了心里的第一个疑惑:拦截器时怎么附加的

1、当调用 use 函数时,会往Axios的 interceptors 属性的 request 或者 response 数组里添加两个promise的回调函数,分别对应调用use函数时,传入的两个回调。

2、在Axios的 request 方法里,会定义一个chain数组,默认存放dispatchRequest,也就是请求分发,然后会循环request和response两个数组,把请求拦截器插入到chain数组的头部,响应拦截器插入到chain数组尾部

3、循环chain数组,每次取出两个元素,刚好是promise的成功回调和失败回调,并把这些promise 链在一起。

搞懂了拦截器是怎么附加的,但是axios还有个重点部分,发送请求,也就是之前看到的 定义chain数组时,就默认给到的dispatchRequest ,接下来去研究一下 dispatchRequest.js里究竟做了什么事情

关于axios请求原理分析
前面一些代码主要是一些请求数据转换,以及请求头的处理,重点在下面

关于axios请求原理分析
首先是定义adapter请求适配器,然后定义将要传入adapter内的resolve和reject函数,可以明显的看到,两者又对请求结果进行了处理。
那么 config.adapter || defaults.adapter 来自哪呢,我去们看下 default.js,开头所说的 axios的特点之一:
在node.js中发送请求使用http,在浏览器使用XMLHttpRequest便来自下面代码。

关于axios请求原理分析
我们先去看浏览器环境下,使用的xhr,暂且跳过node环境的http。找到xhr.js

关于axios请求原理分析
第一行,第二行代码是拿到请求头和请求体。

第三行是针对请求体内容FormData类型时,删除设置的content-type,交由浏览器自行设置。之所以要交给浏览器设置是因为请求体内容为FormData类型时,多为文件上传,而文件上传时,涉及到一个 boundary ,也就是分隔符,如果手动设置了content-type的话,就必须自己传入一个boundary ,否则上传文件就会失败。而交由浏览器自己设置的话,浏览器就会自己生成随机的boundary 。至于boundary 更详细的解释,可以自己去谷歌一下。

第四行new了一个XMLHttpRequest的实例。
第五行是关于authentication身份认证的设置,第六行 。。。。后面的操作暂且跳过

先看下监听请求状态变化的函数,
关于axios请求原理分析
前面代码是些判断,以及响应头,响应体处理,然后我们看下settle方法,传入的是从dispatchRequest里传入到xhr中的resolve和reject方法,以及响应体,并根据响应状态码,确定请求成功还是失败
关于axios请求原理分析
第一行代码中的validateStatus方法也在defaults.js中
关于axios请求原理分析

axios 的取消请求具体又是怎么实现的

上面代码都比较简单,但是我们至今都还没找到取消请求在哪啊,我们回到xhr.js接着往下看,终于找到了cancelToken相关。
关于axios请求原理分析
代码先判断是否配置了cancelToken,再回顾我们取消请求时,需要写的代码 ,

const CancelToken = axios.CancelToken;
const source = CancelToken.source();

axios.get('/user/12345', {
  cancelToken: source.token
}).catch(function(thrown) {
  if (axios.isCancel(thrown)) {
    console.log('Request canceled', thrown.message);
  } else {
     // 处理错误
  }
});
// 取消请求(message 参数是可选的)
source.cancel('Operation canceled by the user.');

当我们传入了cancelToken,就会执行后面的代码,也就是一个promise的成功回调,在这个回调函数里调用了request.abort()方法中断请求,并且调用从dispatchRequest里传进来的reject方法,以失败结束这段promise请求。至此终于逮到了取消请求在哪写的了。

接下来去cancelToken.js中,这里首先定义了一个resolvePromise用来保存promise的resolve操作

关于axios请求原理分析
关于axios请求原理分析
cancelToken接收一个执行器,其实这个执行器接收的cancel函数,也就是我们取消请求时所调用的source.cancel()。
当我们在外界代码中调用source.cancel(reason)时,这个函数被调用,然后resolvePromise被调用,使这个promise成功,进入then回调,也就是xhr.js中的那段代码来中断请求。

  config.cancelToken.promise.then(function onCanceled(cancel) {
        if (!request) {
          return;
        }

        request.abort();
        reject(cancel);
        // Clean up request
        request = null;
      });

然后再来看一点点细节,执行器的函数内部首先判断了token.reason,这是为什么呢?再往下看一步,token.reason被赋予了 Cancel的实例,而Cancel内部是这样的

关于axios请求原理分析
也就是说,如果token.reason存在,那么请求就必定被取消过了,这时就执行跳出。并且注意下 最后一句
Cancel.prototype.CANCEL = true;
往Cancel的原型对象上加这个属性有什么用呢,还记得dispatchRequest.js中,关于请求失败的回调,

关于axios请求原理分析
有一个判断, isCacel(),也就是Cancel.js文件内下面这句代码,看到这不得不佩服框架作者设计的巧妙

关于axios请求原理分析
至此,第二个疑问,关于取消请求的原理也搞清楚了。

1、在xhr.js中,xhrAdapter 内,定义了下面一段代码,判断发送请求时,是否在config内传入了cancelToken。如果有传,则定义好cancelToken的then回调。并在回调内进行中断请求,以及结束整个promise链,进入失败回调

if (config.cancelToken) {
      // Handle cancellation
      config.cancelToken.promise.then(function onCanceled(cancel) {
        if (!request) {
          return;
        }

        request.abort();
        reject(cancel);
        // Clean up request
        request = null;
      });
    }

2、 然后在cancelToken.js内,定义了一个promise,当我们在代码中执行,source.cancel(abort reason)时,调用resolve()方法,使promise成功,进入第一步所说的then回调,从而中断请求。

补充:
至于ssource.cancel(abort reason)方法怎么来的,以为什么调用它就会使promise进入then回调,可以参考下面源码:
这是我们取消请求时的写法:

const CancelToken = axios.CancelToken;
const source = CancelToken.source();

axios.get('/user/12345', {
  cancelToken: source.token
}).catch(function(thrown) {
  if (axios.isCancel(thrown)) {
    console.log('Request canceled', thrown.message);
  } else {
     // 处理错误
  }
});
// 取消请求(message 参数是可选的)
source.cancel('Operation canceled by the user.');

这是源码

function CancelToken(executor) {
  if (typeof executor !== 'function') {
    throw new TypeError('executor must be a function.');
  }

  var resolvePromise;
  this.promise = new Promise(function promiseExecutor(resolve) {
    resolvePromise = resolve;
  });

  var token = this;
  executor(function cancel(message) {
    if (token.reason) {
      // Cancellation has already been requested
      return;
    }

    token.reason = new Cancel(message);
    resolvePromise(token.reason);
  });
}

CancelToken.source = function source() {
  var cancel;
  var token = new CancelToken(function executor(c) {
    cancel = c;
  });
  return {
    token: token,
    cancel: cancel
  };
};

本文地址:https://blog.csdn.net/qq_41885871/article/details/109205583