axios的替代方案onion-middleware

onion-middleware的由来

嗯。。。闲来无事瞎搞的!!!!主要用来实现请求/相应拦截,当然队列性的数据操作都是可以的

直接上使用教程

  1. 安装

    npm install onion-middleware

  2. 使用

    import { OnionMiddleware } from 'onion-middleware'

    // vite等模块化加载的工具需调用clearMiddleware
    app.clearMiddleware()

    // 创建一个中间件应用
    const app = new OnionMiddleware()

    // 添加中间件
    app.use(async (ctx, next) => {
    // Middleware Before to do something
    next()
    // Middleware After to do something
    })

  3. 实战使用

    import { BaseResponse, CtxType, FetchOptionsType } from './type';
    import storage from '../storage';
    import { sortObject } from '../common';
    import { aesDecrypt, aesEncrypt } from './cryproUtil';
    import config from '@/configs/config';
    import { message } from 'antd';
    import { OnionMiddleware } from 'onion-middleware'
    const pendingPromises: { [key: string]: any } = {};
    export const fetchApi = (options: FetchOptionsType) => {
    const { url, timeout, headers, method, data = {}, ...args } = options || {};
    const queryData =
    data instanceof FormData ? data : JSON.parse(JSON.stringify(data)); // 去掉undefined的属性值
    let conpleteUrl = url;
    const reqOptions = { ...args, headers, method };

    if (['GET', 'DELETE'].includes(method?.toUpperCase())) {
    conpleteUrl = url + '?';
    for (let key in queryData) {
    const val = queryData[key]
    conpleteUrl += ${key}=${typeof val === 'object' ? JSON.stringify(val) : encodeURIComponent(queryData[key])}&;
    }
    } else {
    reqOptions.body =
    typeof queryData === 'string' || queryData instanceof FormData
    ? queryData
    : JSON.stringify(queryData);
    if (queryData instanceof FormData) {
    delete reqOptions.headers['Content-Type'];
    }
    }

    return Promise.race([
    fetch(conpleteUrl, reqOptions)
    .then((response) => {
    return response.json();
    })
    .catch((e) => {
    return JSON.stringify({
    code: 504,
    msg: '连接不到服务器',
    });
    }),
    new Promise((_, reject) =>
    setTimeout(() => reject(new Error('Request timeout')), timeout)
    ),
    ]);
    };

    const app = new OnionMiddleware()
    app.clearMiddleware();

    // 特殊业务处理中间件(401),以及相应数据的ts类型定义
    app.use(async (ctx: CtxType, next: (arg?: any) => void) => {
    // Middleware Before
    await next();
    // Middleware After
    const { response } = ctx;
    // 401,1401 重新静默登录
    if ([401, 1401].includes(response?.code)) {
    // 未登录
    storage.clearAll();
    message.error('登录过期,请重新登陆!');
    // to do something
    }
    });

    // 重复请求复用中间件
    app.use(async (ctx: CtxType, next: (arg?: any) => void) => {
    // Middleware Before
    const { request } = ctx;
    // 使用请求信息作为唯一的请求key,缓存正在请求的promise对象
    // 相同key的请求将复用promise
    const requestKey = JSON.stringify([
    request.method.toUpperCase(),
    sortObject(request.data),
    ]);
    if (pendingPromises[requestKey]) {
    console.log('重复请求,已合并请求并返回共享的第一次请求,参数:', request);
    await next({
    shouldAbort: true,
    callback: () => {
    return pendingPromises[requestKey];
    },
    });
    } else {
    await next();
    }
    // Middleware After
    delete pendingPromises[requestKey];
    });

    // 请求/相应信息输出中间件
    app.use(async (ctx: CtxType, next: (arg?: any) => void) => {
    // Middleware Before
    await next();
    // Middleware After
    const { request, response } = ctx;
    if (request?.errorTip && ![200, 401, 1401].includes(response.code)) {
    message.error(response.msg);
    }
    console.log('请求参数', {
    url: request?.url,
    data: request?.originData,
    method: request?.method,
    });
    console.log('响应参数', response);
    });

    // header处理中间件(此中间件位于)
    app.use(async (ctx: CtxType, next: (arg?: any) => void) => {
    // Middleware Before
    const { request, response } = ctx;
    const token = storage.getItem('token');
    if (token) {
    // 请求头token信息,请根据实际情况进行修改
    request.headers['Authorization'] = 'Bearer ' + token;
    }
    ctx.request = request;

    await next();

    // Middleware After
    });

    // 加解密
    app.use(async (ctx: CtxType, next: (arg?: any) => void) => {
    // Middleware Before
    const { request } = ctx;
    request.originData = request.data;
    if (config.OPEN_ENCRYPTION) {
    const aesData = aesEncrypt(request.data);
    // 加密
    request.data = { encryptedReqData: aesData[0] };
    // sign
    request.headers['sign'] = aesData[1];
    }
    await next();
    // Middleware After
    if (config.OPEN_ENCRYPTION) {
    if (ctx.response?.encryptedResData) {
    ctx.response = aesDecrypt(ctx.response?.encryptedResData || '') || '{}';
    }
    }
    });

    // 公共请求发送方法(简约)
    const send = <T>(options: any): Promise<T> => {
    const { errorTip = true } = options;
    // HTTP请求上下文
    const ctx: CtxType = {
    request: {
    ...options,
    errorTip,
    config: {
    timeout: 60 * 1000,
    baseUrl: config.VITE_API_URL + config.VITE_BASE_URL,
    },
    headers: {
    'Content-Type': 'application/json',
    },
    },
    promise: { resolve: () => { }, reject: () => { } },
    };

    const baseRequest = new Promise((resolve: (arg: T) => void, reject) => {
    // 打破promise回调作用域,在其他地方实现调用
    ctx.promise.resolve = resolve;
    ctx.promise.reject = reject;

     // 执行中间件
     app
       .execute(ctx, () => {
         let { config } = ctx?.request || {};
         const { data, method, url, headers } = ctx?.request || {};
         const fetchPromise = new Promise(async (_resolve) => {
           config = {
             ...config,
             data,
             headers,
             url: config.baseUrl + (url || ''),
             method: method.toUpperCase(), // 配置method方法
           };
           const res = await fetchApi({
             ...options,
             ...config,
           });
           _resolve(res);
         });
    
         return fetchPromise;
       })
       .then(() => { })
       .catch((err) => {
         console.log(err);
       });
    

    });
    // 使用请求信息作为唯一的请求key,缓存正在请求的promise对象
    // 相同key的请求将复用promise
    const requestKey = JSON.stringify([
    options.method.toUpperCase(),
    options.url,
    sortObject(options.data),
    ]);
    // 存储第一次请求引用 (重复请求判断需要)
    if (!pendingPromises[requestKey]) {
    pendingPromises[requestKey] = baseRequest;
    }
    return baseRequest;
    };

    // 公共请求发送方法
    const sendApi = <T = BaseResponse>(options: FetchOptionsType): Promise<T> => {
    return send(options);
    };

    export default sendApi;

效果简单截个图吧,拿请求/相应信息输出中间件为例,效果如下:

结尾

轻点喷😂😂😂

相关推荐
我爱学习_zwj2 小时前
深入浅出Node.js-1(node.js入门)
前端·webpack·node.js
疯狂的沙粒2 小时前
前端开发 vue 中如何实现 u-form 多个form表单同时校验
javascript·vue.js·ecmascript
IT 前端 张2 小时前
2025 最新前端高频率面试题--Vue篇
前端·javascript·vue.js
喵喵酱仔__2 小时前
vue3探索——使用ref与$parent实现父子组件间通信
前端·javascript·vue.js
_NIXIAKF2 小时前
vue中 输入框输入回车后触发搜索(搜索按钮触发页面刷新问题)
前端·javascript·vue.js
InnovatorX2 小时前
Vue 3 详解
前端·javascript·vue.js
布兰妮甜2 小时前
html + css 顶部滚动通知栏示例
前端·css·html
种麦南山下2 小时前
vue el table 不出滚动条样式显示 is_scrolling-none,如何修改?
前端·javascript·vue.js
天弈初心2 小时前
Vue 组件开发:构建高效可复用的 UI 构建块
javascript·vue.js
杨荧3 小时前
【开源免费】基于Vue和SpringBoot的贸易行业crm系统(附论文)
前端·javascript·jvm·vue.js·spring boot·spring cloud·开源