手写Axios核心原理

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

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

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

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

 
 
 
 
  1. 点击

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

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.     }
  13. }

最终导出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;
  7. }

再来测试一下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. axios.interceptors.response.use(function (response) {
  11.     // 响应数据处理
  12.     return response;
  13.   }, function (error) {
  14.     // 响应错误处理
  15.     return Promise.reject(error);
  16.   });

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

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

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

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.     //....
  9. }

在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;
  8. }

但是现在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. }
  19. sendAjax(config){
  20.     return new Promise((resolve) => {
  21.         const {url='',method='get',data={}} = config;
  22.         const xhr = new XMLHttpRequest();
  23.         xhr.open(method,url,true);
  24.         xhr.onreadystatechange = () => {
  25.             if(xhr.readyState == 4 && xhr.status == 200){
  26.                 resolve(xhr.responseText)
  27.             }
  28.         }
  29.         xhr.send(JSON.stringify(data));
  30.     })
  31. }

最后执行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.     }
  19. ]

请求之前,promise执行为:

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

请求时,执行为:

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

响应后,执行为:

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

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

 
 
 
 
  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.     }).then(res => {
  21.         console.log(res);
  22.     })
  23. }

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

5.总结

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

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

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

本文名称:手写Axios核心原理
浏览路径:http://www.shufengxianlan.com/qtweb/news19/366419.html

网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等

广告

声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联