文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

手写Axios核心原理

2024-12-03 16:03

关注

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

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

1.基本使用

axios基本使用方式主要有:

  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. "getMsg()">点击 
  2. "./axios.js"
  3.  

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

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前端严选内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯