axios 是如何封装 HTTP 请求的

资讯 作者:CSDN 2021-06-09 10:33:38 阅读:536

作者 | 追公交车的小仙女       责编 | 欧阳姝黎

Axios 毋庸多说大家在前端开发中常用的一个发送 HTTP 请求的库,用过的都知道。本文用来整理项目中常用的 Axios 的封装使用。同时学习源码,手写实现 Axios 的核心代码。


Axios 常用封装


是什么

Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。它的特性:

  • 从浏览器中创建 XMLHttpRequests
  • 从 node.js 创建 http 请求
  • 支持 Promise API
  • 拦截请求和响应
  • 转换请求数据和响应数据
  • 取消请求
  • 自动转换 JSON 数据
  • 客户端支持防御 XSRF 官网地址:http://www.axios-js.com/zh-cn/docs/#axios-config

Axios 使用方式有两种:一种是直接使用全局的 Axios 对象;另外一种是通过 axios.create(config) 方法创建一个实例对象,使用该对象。两种方式的区别是通过第二种方式创建的实例对象更清爽一些;全局的 Axios 对象其实也是创建的实例对象导出的,它本身上加载了很多默认属性。后面源码学习的时候会再详细说明。

请求

Axios 这个 HTTP 的库比较灵活,给用户多种发送请求的方式,以至于有些混乱。细心整理会发现,全局的 Axios(或者 axios.create(config)创建的对象) 既可以当作对象使用,也可以当作函数使用:

// axios 当作对象使用
axios.request(config)
axios.get(url[, config])
axios.post(url[, data[, config]])
// axios() 当作函数使用。 发送 POST 请求
axios({
  method'post',
  url'/user/12345',
  data: {
    firstName'Fred',
    lastName'Flintstone'
  }
});

后面源码学习的时候会再详细说明为什么 Axios 可以实现两种方式的使用。

取消请求

可以使用 CancelToken.source 工厂方法创建 cancel token:

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.');

source 有两个属性:一个是 source.token 标识请求;另一个是 source.cancel() 方法,该方法调用后,可以让 CancelToken 实例的 promise 状态变为 resolved,从而触发 xhr 对象的 abort() 方法,取消请求。

拦截

Axios 还有一个奇妙的功能点,可以在发送请求前对请求进行拦截,对相应结果进行拦截。结合业务场景的话,在中台系统中完成登录后,获取到后端返回的 token,可以将 token 添加到 header 中,以后所有的请求自然都会加上这个自定义 header。

//拦截1 请求拦截
instance.interceptors.request.use(function(config){
    //在发送请求之前做些什么
    const token = sessionStorage.getItem('token');
    if(token){
        const newConfig = {
            ...config,
            headers: {
                token: token
            }
        }
        return newConfig;
    }else{
        return config;
    }
}, function(error){
    //对请求错误做些什么
    return Promise.reject(error);
});

我们还可以利用请求拦截功能实现 取消重复请求,也就是在前一个请求还没有返回之前,用户重新发送了请求,需要先取消前一次请求,再发送新的请求。比如搜索框自动查询,当用户修改了内容重新发送请求的时候需要取消前一次请求,避免请求和响应混乱。再比如表单提交按钮,用户多次点击提交按钮,那么我们就需要取消掉之前的请求,保证只有一次请求的发送和响应。

实现原理是使用一个对象记录已经发出去的请求,在请求拦截函数中先判断这个对象中是否记录了本次请求信息,如果已经存在,则取消之前的请求,将本次请求添加进去对象中;如果没有记录过本次请求,则将本次请求信息添加进对象中。最后请求完成后,在响应拦截函数中执行删除本次请求信息的逻辑。

// 拦截2   重复请求,取消前一个请求
const promiseArr = {};
instance.interceptors.request.use(function(config){
    console.log(Object.keys(promiseArr).length)
    //在发送请求之前做些什么
    let source=null;
    if(config.cancelToken){
        // config 配置中带了 source 信息
        source = config.source;
    }else{
        const CancelToken = axios.CancelToken;
        source = CancelToken.source();
        config.cancelToken = source.token;
    }
    const currentKey = getRequestSymbol(config);
    if(promiseArr[currentKey]){
        const tmp = promiseArr[currentKey];
        tmp.cancel("取消前一个请求");
        delete promiseArr[currentKey];
        promiseArr[currentKey] = source;
    }else{
        promiseArr[currentKey] = source;
    }
    return config;

}, function(error){
    //对请求错误做些什么
    return Promise.reject(error);
});
// 根据 url、method、params 生成唯一标识,大家可以自定义自己的生成规则
function getRequestSymbol(config){
    const arr = [];
    if(config.params){
        const data = config.params;
        for(let key of Object.keys(data)){
            arr.push(key+"&"+data[key]);
        }
        arr.sort();
    }
    return config.url+config.method+arr.join("");
}

instance.interceptors.response.use(function(response){
    const currentKey = getRequestSymbol(response.config);
    delete promiseArr[currentKey];
    return response;
}, function(error){
    //对请求错误做些什么
    return Promise.reject(error);
});

最后,我们可以在响应拦截函数中统一处理返回码的逻辑:

// 响应拦截
instance.interceptors.response.use(function(response){
    // 401 没有登录跳转到登录页面
    if(response.data.code===401){
        window.location.href = "http://127.0.0.1:8080/#/login";
    }else if(response.data.code===403){
        // 403 无权限跳转到无权限页面
        window.location.href = "http://127.0.0.1:8080/#/noAuth";
    }
    return response;
}, function(error){
    //对请求错误做些什么
    return Promise.reject(error);
})

文件下载

通常文件下载有两种方式:一种是通过文件在服务器上的对外地址直接下载;还有一种是通过接口将文件以二进制流的形式下载。

第一种:同域名 下使用 a 标签下载:

// httpServer.js
const express = require("express");
const path = require('path');
const app = express();

//静态文件地址
app.use(express.static(path.join(__dirname, 'public')))
app.use(express.static(path.join(__dirname, '../')));
app.listen(8081, () => {
  console.log("服务器启动成功!")
});
// index.html
<a href="test.txt" download="test.txt">下载</a>

第二种:二进制文件流的形式传递,我们直接访问该接口并不能下载文件,一定程度保证了数据的安全性。比较多的场景是:后端接收到查询参数,查询数据库然后通过插件动态生成 excel 文件,以文件流的方式让前端下载。

这时候,我们可以将请求文件下载的逻辑进行封装。将二进制文件流存在 Blob 对象中,再将其转为 url 对象,最后通过 a 标签下载。

//封装下载
export function downLoadFetch(url, params = {}, config={}{
    //取消
    const downSource = axios.CancelToken.source();
    document.getElementById('downAnimate').style.display = 'block';
    document.getElementById('cancelBtn').addEventListener('click'function(){
        downSource.cancel("用户取消下载");
        document.getElementById('downAnimate').style.display = 'none';
    }, false);
    //参数
    config.params = params;
    //超时时间
    config.timeout = config.timeout ? config.timeout : defaultDownConfig.timeout;
    //类型
    config.responseType = defaultDownConfig.responseType;
    //取消下载
    config.cancelToken = downSource.token;
    return instance.get(url, config).then(response=>{
        const content = response.data;
        const url = window.URL.createObjectURL(new Blob([content]));
        //创建 a 标签
        const link = document.createElement('a');
        link.style.display = 'none';
        link.href = url;
        //文件名  Content-Disposition: attachment; filename=download.txt
        const filename = response.headers['content-disposition'].split(";")[1].split("=")[1];
        link.download = filename;
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        return {
            status200,
            successtrue
        }
    })
}

https://juejin.cn/post/6878912072780873742


手写 Axios 核心代码


写了这么多用法终于到正题了,手写 Axios 核心代码。Axios 这个库源码不难阅读,没有特别复杂的逻辑,大家可以放心阅读

在线申请SSL证书行业最低 =>立即申请

[广告]赞助链接:

关注数据与安全,洞悉企业级服务市场:https://www.ijiandao.com/
让资讯触达的更精准有趣:https://www.0xu.cn/

#
公众号 关注KnowSafe微信公众号
随时掌握互联网精彩
赞助链接