手写Axios核心原理

戳蓝字"

Web前端严选

"

关注我们哦

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

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

1.基本使用

axios基本使用方式主要有:

  • axios(config)

  • axios.method(url,data,config)

// 发送 POST 请求
axios({
  method: 'post',
  url: '/user/12345',
  data: {
    username: 'Web前端严选',
    age: 2
  }
});
// GET请求ID参数
axios.get('/user?ID=12345')
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  });

2.实现axios

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

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

class Axios{
    constructor(){

    }
    request(config){
        return new Promise((resolve) => {
            const {url='',data={},method='get'} = config; //解构传参
            const xhr = new XMLHttpRequest;     //创建请求对象
            xhr.open(method,url,true); 
            xhr.onreadystatechange = () => {
                if(xhr.readyState == 4 && xhr.status == 200){
                    resolve(xhr.responseText);
                    //异步请求返回后将Promise转为成功态并将结果导出
                }
            }
            xhr.send(JSON.stringfy(data));
        })
    }
}

function CreateAxiosFn(){
    let axios = new Axios;
    let req = axios.request.bind(axios);
    return req;
}

let axios = CreateAxiosFn();

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

const express = require('express')

let app = express();

app.all('*', function (req, res, next) {
    res.header('Access-Control-Allow-Origin', '*');
    res.header('Access-Control-Allow-Headers', 'Content-Type');
    res.header('Access-Control-Allow-Methods', '*');
    res.header('Content-Type', 'application/json;charset=utf-8');
    next();
});

app.get('/getInfo', function(request, response){
    let data = {
        'username':'前端严选',
        'age':'2'
    };
    response.json(data);
});
app.listen(3000, function(){
    console.log("服务器启动");
});

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

<button onclick="getMsg()">点击</button>
<script src="./axios.js"></script>
<script>
    function getMsg(){
        axios({
            method: 'get',
            url: 'http://localhost:3000/getInfo'
        }).then(res => {
            console.log(res);
        })
    }
</script>

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

3.原型上的方法

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

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

const methodsArr = ['get','post','put','delete','head','options','patch','head'];
methodsArr.forEach(method => {
    Axios.prototype[method] = function(){
        return this.request({
            method: method,
            ...arguments[0]
        })
    }
})

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

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

const utils = {
    extend(a,b,context){
        for(let key in b){
            if(b.hasOwnProperty(key)){
                if(typeof b[key] == 'function'){
                    a[key] = b[key].bind(context);
                }else{
                    a[key] = b[key]
                }
            }
        }
    }
}

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

function CreateAxiosFn(){
    let axios = new Axios;
    let req = axios.request.bind(axios);
    //新增如下代码
    utils.extend(req, Axios.prototype, axios)
    return req;
}

再来测试一下post的请求:

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

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

4.拦截器

先来看看拦截器的使用:

// 请求拦截
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);
  });

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

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

class InterceptorsManage{
    constructor(){
        this.handlers = [];
    }
    use(onFulField,onRejected){
        //将成功的回调和失败的回调都存放到队列中
        this.handlers.push({
            onFulField,
            onRejected
        })
    }
}

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

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

class Axios{
    constructor(){
        this.interceptors = {
            request: new InterceptorsManage,
            response: new InterceptorsManage
        }
    }
    //....
}

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

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

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

function CreateAxiosFn() {
  let axios = new Axios();
  let req = axios.request.bind(axios);
  utils.extend(req, Axios.prototype, axios)
  //新增如下代码
  utils.extend(req, axios)
  return req;
}

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

request(config){
    //拦截器和请求的队列
    let chain = [this.sendAjax.bind(this),undefined];
 //请求的拦截
    this.interceptors.request.handlers.forEach(interceptor => {
        chain.unshift(interceptor.onFulField,interceptor.onRejected);
    })
 //响应的拦截
    this.interceptors.response.handlers.forEach(interceptor => {
        chain.push(interceptor.onFulField,interceptor.onRejected)
    })
    let promise = Promise.resolve(config);
    while(chain.length > 0){
        //从头部开始依次执行请求的拦截、真正的请求、响应的拦截
        promise = promise.then(chain.shift(),chain.shift());
    }
    return promise;
}
sendAjax(config){
    return new Promise((resolve) => {
        const {url='',method='get',data={}} = config;
        const xhr = new XMLHttpRequest();
        xhr.open(method,url,true);
        xhr.onreadystatechange = () => {
            if(xhr.readyState == 4 && xhr.status == 200){
                resolve(xhr.responseText)
            }
        }
        xhr.send(JSON.stringify(data));
    })
}

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

chain = [
    //请求之前成功的回调和失败的回调
    function (config) {
        return config;
    }, 
    function (error) {
        return Promise.reject(error);
    }
 //真正的请求执行
    this.sendAjax.bind(this), 
    undefined,
 //请求之后响应的成功回调和失败回调
    function (response) {
        return response;
    }, 
    function (error) {
        return Promise.reject(error);
    }
]

请求之前,promise执行为:

promise.then(
 function (config) {
        return config;
    }, 
    function (error) {
        return Promise.reject(error);
    }
)

请求时,执行为:

promise.then(
 this.sendAjax.bind(this), 
    undefined,
)

响应后,执行为:

promise.then(
 function (response) {
        return response;
    }, 
    function (error) {
        return Promise.reject(error);
    }
)

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

function getMsg(){
    axios.interceptors.request.use((config) => {
        console.log('请求拦截:',config);
        return config;
    },err => {
        return Promise.reject(err)
    })
    axios.interceptors.response.use(response => {
        response = {
            message: '响应数据替换',
            data: response
        }
        return response
    },err => {
        console.log(err,'响应错误')
        return Promise.reject(err)
    })
    axios.get({
        url: 'http://localhost:3000/getTest',

    }).then(res => {
        console.log(res);
    })
}

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

5.总结

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

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

❤️ 感谢大家

如果你觉得这篇内容对你挺有有帮助的话:

  1. 看到这里了就点个在看支持一下吧,你的[在看]是我创作的动力;

  2. 关注公众号【Web前端严选】,进交流群,定期为你推送好文。

添加个人微信,进群与小伙伴一起玩耍(已经推出)~

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值