手写Axios核心原理

开发 架构
Axios天然支持Promise的性能让其方便对异步进行处理,同时又利用了Promise对请求进行了拦截,使得用户可以在请求过程中添加更多的功能,对请求的中断能自如操作。

[[355442]]

Axios是一个基于promise的HTTP库,它能够自动判断当前环境,自由切换在浏览器和 node.js环境中。如果是浏览器,就会基于XMLHttpRequests实现;如果是node环境,就会基于node内置核心http模块实现。同时,它还用promise处理了响应结果,避免陷入回调地狱中去。

不仅如此,Axios还可以拦截请求和响应、转化请求数据和响应数据、中断请求、自动转换JSON数据、客户端支持防御XSRF等。如此众多好用的功能,快来一起看看它是如何实现的吧!

1.基本使用

axios基本使用方式主要有:

  • axios(config)
  • axios.method(url,data,config)
  1. // 发送 POST 请求 
  2. axios({ 
  3.   method: 'post'
  4.   url: '/user/12345'
  5.   data: { 
  6.     username: 'Web前端严选'
  7.     age: 2 
  8.   } 
  9. }); 
  10. // GET请求ID参数 
  11. axios.get('/user?ID=12345'
  12.   .then(function (response) { 
  13.     console.log(response); 
  14.   }) 
  15.   .catch(function (error) { 
  16.     console.log(error); 
  17.   }); 

2.实现axios

从axios(config)的使用上可以看出导出的axios是一个方法,从axios.get()的使用上可以看出导出的axios原型上会有get,post,put,delete等方法。

由分析可知,axios实际上是Axios类中的一个方法。我们可以先写一个request方法来实现主要的请求功能,这样就能使用axios(config)形式来调用了。

  1. class Axios{ 
  2.     constructor(){ 
  3.  
  4.     } 
  5.     request(config){ 
  6.         return new Promise((resolve) => { 
  7.             const {url='',data={},method='get'} = config; //解构传参 
  8.             const xhr = new XMLHttpRequest;     //创建请求对象 
  9.             xhr.open(method,url,true);  
  10.             xhr.onreadystatechange = () => { 
  11.                 if(xhr.readyState == 4 && xhr.status == 200){ 
  12.                     resolve(xhr.responseText); 
  13.                     //异步请求返回后将Promise转为成功态并将结果导出 
  14.                 } 
  15.             } 
  16.             xhr.send(JSON.stringfy(data)); 
  17.         }) 
  18.     } 
  19.  
  20. function CreateAxiosFn(){ 
  21.     let axios = new Axios; 
  22.     let req = axios.request.bind(axios); 
  23.     return req; 
  24.  
  25. let axios = CreateAxiosFn(); 

然后搭建一个简易服务端代码,以测试请求的效果:

  1. const express = require('express'
  2.  
  3. let app = express(); 
  4.  
  5. app.all('*'function (req, res, next) { 
  6.     res.header('Access-Control-Allow-Origin''*'); 
  7.     res.header('Access-Control-Allow-Headers''Content-Type'); 
  8.     res.header('Access-Control-Allow-Methods''*'); 
  9.     res.header('Content-Type''application/json;charset=utf-8'); 
  10.     next(); 
  11. }); 
  12.  
  13. app.get('/getInfo'function(request, response){ 
  14.     let data = { 
  15.         'username':'前端严选'
  16.         'age':'2' 
  17.     }; 
  18.     response.json(data); 
  19. }); 
  20. app.listen(3000, function(){ 
  21.     console.log("服务器启动"); 
  22. }); 

启动服务后,在页面中测试请求是否成功:

  1. <button onclick="getMsg()">点击</button> 
  2. <script src="./axios.js"></script> 
  3. <script> 
  4.     function getMsg(){ 
  5.         axios({ 
  6.             method: 'get'
  7.             url: 'http://localhost:3000/getInfo' 
  8.         }).then(res => { 
  9.             console.log(res); 
  10.         }) 
  11.     } 
  12. </script> 

点击按钮后,可以看到请求成功并获取到数据。

3.原型上的方法

接下来实现以axios.method()形式的方法。

通过axios.get(),axios.post(),axios.put()等方法可以看出它们都是Axios.prototype上的方法,这些方法调用内部的request方法即可:

  1. const methodsArr = ['get','post','put','delete','head','options','patch','head']; 
  2. methodsArr.forEach(method => { 
  3.     Axios.prototype[method] = function(){ 
  4.         return this.request({ 
  5.             method: method, 
  6.             ...arguments[0] 
  7.         }) 
  8.     } 
  9. }) 

arguments的第一个参数包含url,data等信息,直接解构它的第一个元素即可

还需要实现一个工具方法,用来将b方法属性混入到a中去:

  1. const utils = { 
  2.     extend(a,b,context){ 
  3.         for(let key in b){ 
  4.             if(b.hasOwnProperty(key)){ 
  5.                 if(typeof b[key] == 'function'){ 
  6.                     a[key] = b[key].bind(context); 
  7.                 }else
  8.                     a[key] = b[key
  9.                 } 
  10.             } 
  11.         } 
  12.     } 

最终导出axios的request方法,使之拥有get,post等方法

  1. function CreateAxiosFn(){ 
  2.     let axios = new Axios; 
  3.     let req = axios.request.bind(axios); 
  4.     //新增如下代码 
  5.     utils.extend(req, Axios.prototype, axios) 
  6.     return req; 

再来测试一下post的请求:

  1. axios.post({ 
  2.     url: 'http://localhost:3000/postTest'
  3.     data: { 
  4.         a: 1, 
  5.         b: 2 
  6.     } 
  7. }).then(res => { 
  8.     console.log(res); 
  9. }) 

可以看到正确返回结果了。

4.拦截器

先来看看拦截器的使用:

  1. // 请求拦截 
  2. axios.interceptors.request.use(function (config) { 
  3.     // 在发送请求之前 
  4.     return config; 
  5.   }, function (error) { 
  6.     // 请求错误处理 
  7.     return Promise.reject(error); 
  8.   }); 
  9.  
  10. // 响应拦截 
  11. axios.interceptors.response.use(function (response) { 
  12.     // 响应数据处理 
  13.     return response; 
  14.   }, function (error) { 
  15.     // 响应错误处理 
  16.     return Promise.reject(error); 
  17.   }); 

拦截器,顾名思义就是在请求之前和响应之前,对真正要执行的操作数据拦截住进行一些处理。

那么如何实现呢,首先拦截器也是一个类,用于管理响应和请求。

  1. class InterceptorsManage{ 
  2.     constructor(){ 
  3.         this.handlers = []; 
  4.     } 
  5.     use(onFulField,onRejected){ 
  6.         //将成功的回调和失败的回调都存放到队列中 
  7.         this.handlers.push({ 
  8.             onFulField, 
  9.             onRejected 
  10.         }) 
  11.     } 

axios.interceptors.response.use和axios.interceptors.request.use来定义请求和响应的拦截方法。

这说明axios上有响应拦截器和请求拦截器,那么如何在axios上实现呢:

  1. class Axios{ 
  2.     constructor(){ 
  3.         this.interceptors = { 
  4.             request: new InterceptorsManage, 
  5.             response: new InterceptorsManage 
  6.         } 
  7.     } 
  8.     //.... 

在Axios的构造函数中新增interceptors属性,然后定义request和response属性用于处理请求和响应。

执行use方法时,会把传入的回调函数放到handlers数组中。

这时再回看使用方式,axios.interceptors.request.use方法是绑在axios实例上的,因此同样需要把Axios上的属性和方法转移到request上,将interceptors对象挂载到request方法上。

  1. function CreateAxiosFn() { 
  2.   let axios = new Axios(); 
  3.   let req = axios.request.bind(axios); 
  4.   utils.extend(req, Axios.prototype, axios) 
  5.   //新增如下代码 
  6.   utils.extend(req, axios) 
  7.   return req; 

但是现在request不仅要执行请求的发送,还要执行拦截器中handler的回调函数,因此还需要把request方法进行一下改造:

  1. request(config){ 
  2.     //拦截器和请求的队列 
  3.     let chain = [this.sendAjax.bind(this),undefined]; 
  4.  //请求的拦截 
  5.     this.interceptors.request.handlers.forEach(interceptor => { 
  6.         chain.unshift(interceptor.onFulField,interceptor.onRejected); 
  7.     }) 
  8.  //响应的拦截 
  9.     this.interceptors.response.handlers.forEach(interceptor => { 
  10.         chain.push(interceptor.onFulField,interceptor.onRejected) 
  11.     }) 
  12.     let promise = Promise.resolve(config); 
  13.     while(chain.length > 0){ 
  14.         //从头部开始依次执行请求的拦截、真正的请求、响应的拦截 
  15.         promise = promise.then(chain.shift(),chain.shift()); 
  16.     } 
  17.     return promise; 
  18. sendAjax(config){ 
  19.     return new Promise((resolve) => { 
  20.         const {url='',method='get',data={}} = config; 
  21.         const xhr = new XMLHttpRequest(); 
  22.         xhr.open(method,url,true); 
  23.         xhr.onreadystatechange = () => { 
  24.             if(xhr.readyState == 4 && xhr.status == 200){ 
  25.                 resolve(xhr.responseText) 
  26.             } 
  27.         } 
  28.         xhr.send(JSON.stringify(data)); 
  29.     }) 

最后执行chain的时候是这个样子的:

  1. chain = [ 
  2.     //请求之前成功的回调和失败的回调 
  3.     function (config) { 
  4.         return config; 
  5.     },  
  6.     function (error) { 
  7.         return Promise.reject(error); 
  8.     } 
  9.  //真正的请求执行 
  10.     this.sendAjax.bind(this),  
  11.     undefined, 
  12.  //请求之后响应的成功回调和失败回调 
  13.     function (response) { 
  14.         return response; 
  15.     },  
  16.     function (error) { 
  17.         return Promise.reject(error); 
  18.     } 

请求之前,promise执行为:

  1. promise.then
  2.  function (config) { 
  3.         return config; 
  4.     },  
  5.     function (error) { 
  6.         return Promise.reject(error); 
  7.     } 

请求时,执行为:

  1. promise.then
  2.  this.sendAjax.bind(this),  
  3.     undefined, 

响应后,执行为:

  1. promise.then
  2.  function (response) { 
  3.         return response; 
  4.     },  
  5.     function (error) { 
  6.         return Promise.reject(error); 
  7.     } 

这时我们测试一下拦截器的使用:

  1. function getMsg(){ 
  2.     axios.interceptors.request.use((config) => { 
  3.         console.log('请求拦截:',config); 
  4.         return config; 
  5.     },err => { 
  6.         return Promise.reject(err) 
  7.     }) 
  8.     axios.interceptors.response.use(response => { 
  9.         response = { 
  10.             message: '响应数据替换'
  11.             data: response 
  12.         } 
  13.         return response 
  14.     },err => { 
  15.         console.log(err,'响应错误'
  16.         return Promise.reject(err) 
  17.     }) 
  18.     axios.get({ 
  19.         url: 'http://localhost:3000/getTest'
  20.  
  21.     }).then(res => { 
  22.         console.log(res); 
  23.     }) 

可以在控制台中看到拦截处理的打印输出,证明拦截成功!

5.总结

Axios天然支持Promise的性能让其方便对异步进行处理,同时又利用了Promise对请求进行了拦截,使得用户可以在请求过程中添加更多的功能,对请求的中断能自如操作。它的思想既清新朴实又不落入俗套,具有很好的借鉴意义。

看完这篇文章,你了解了Axios的核心原理了吗?

本文转载自微信公众号「Web前端严选」,可以通过以下二维码关注。转载本文请联系Web前端严选公众号。

 

责任编辑:武晓燕 来源: Web前端严选
相关推荐

2020-10-20 09:12:57

axios核心原理

2020-11-02 09:35:04

ReactHook

2021-12-14 07:43:52

Spring核心原理环境准备

2021-08-10 18:36:02

Express原理面试

2020-11-24 07:48:32

React

2021-12-27 08:27:17

SpringMVC面试

2021-12-15 09:17:12

Spring依赖注入面试题

2022-08-27 13:49:36

ES7promiseresolve

2020-10-23 09:26:57

React-Redux

2021-12-12 21:01:12

CSS 技巧PurgeCss

2021-07-27 14:50:15

axiosHTTP前端

2021-12-01 06:40:32

Bind原理实现

2021-11-24 10:10:32

axios前端拦截器

2021-07-12 09:45:36

NameServer 核心Conusmer

2020-11-09 07:29:12

ReentrantLo源码公平锁

2020-07-03 17:20:07

Redux前端代码

2021-04-21 07:52:39

核心SignalR应用

2021-04-28 10:13:58

zookeeperZNode核心原理

2021-08-02 07:57:03

注册Nacos源码

2020-05-21 13:25:43

Spring组件架构
点赞
收藏

51CTO技术栈公众号