前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >前端中的接口聚合

前端中的接口聚合

作者头像
winty
发布2019-12-21 11:55:02
1.4K0
发布2019-12-21 11:55:02
举报
文章被收录于专栏:前端Q前端Q

request-combo

这是一个前端简易版接口聚合模块,主要用于以下场景:

  • 一个支持参数合并的接口,在组件化或其他场景下调用了不同参数的相同的接口,这时把这些调用合并成一个或多个接口再请求。
  • 避免发起相同的请求,某些情况下发起了相同的请求,经收集处理后,实际只发起一个请求。但是不同的发起端的callback 都能得到处理。
主要逻辑设计
  • 要知道接口的基本信息,包括但不限于 url、params、callback…
  • 既然要聚合,那么得有一个收集接口的队列
  • 每个接口的队列要有状态,当一个新接口到来时,该接口的队列可能还没创建,可能正在收集,可能刚发完请求。
  • 要有接口队列发起请求的条件,收集时间够了或者收集长度够了…
  • 有缓存机制,已获取的数据暂时缓存起来
API 设计

调用方法:requestCombo() 参数:

代码语言:javascript
复制
    apiData: ApiData, 
    params: object, 
    callback: Function, 
    request = axios, 
    collectTime = 100, 
    isCombo = true, 
    errorHandle?: Function

ApiData 类型中包含以下内容:

params

Description

Type

Example

url

接口地址

string

http:xxx/api

pack

参数合并逻辑函数

function

fn

unpack

数据拆解逻辑函数

function

fn

maxComboNum

接口最大收集次数

number

10

requestMethod

当前请求类型

string

'get'

具体实现
代码语言:javascript
复制
import axios from 'axios';

interface ApiData {
    url: string;
    pack: Function;
    unpack: Function;
    maxComboNum?: number;
    requestMethod?: string;
}

/**
 * status: number
 * 0:init
 * 1:pending
 * 2:done
 * 
 * request
 * The api must be the same as axios
 */
const dataCache: object = {};
const busData: object = {};

export const requestCombo = (apiData: ApiData, params: object, callback?: Function, request = axios, collectTime = 100, isCombo = true, errorHandle?: Function) => {
    const { url, requestMethod = 'get', maxComboNum = 10, pack, unpack } = apiData;

    const method: string = requestMethod.toLocaleLowerCase();
    const cacheKey = `${url}_${method}_${JSON.stringify(params)}`;
    const busKey = `${url}_${method}`;

    if (!url) return;

    const sendRequest = async () => {
        clearTimeout(busData[busKey].timer);
        const paramList = busData[busKey].paramList;
        const paramObject = pack(paramList);

        busData[busKey] = null;
        try {
            const result = await applyRequest(url, paramObject, method, request);
            // 拆解,拆完要对应回去,因此要用 param 做key
            const obj = unpack(result, paramList) || {};
            Object.keys(obj).forEach((key) => {
                const dataNode = dataCache[cacheKey];
                if (!dataNode) {
                    errorHandle ? errorHandle('Data Unpack Error') : console.log('Data Unpack Error');
                } else {
                    dataNode.data = obj[key];
                    dataNode.status = 2;
                    dataNode.cbs.forEach((cb: Function) => {
                        cb(obj[key]);
                    });
                }
            });
        } catch (ex) {
            if (errorHandle) {
                errorHandle(ex);
                return;
            }
            throw ex;
        }
    };

    return new Promise((resolve, reject) => {
        if (!callback) callback = () => { }; //预处理接口返回数据
        const _callback = callback;
        callback = (json: any) => {
            const raw = _callback(json);
            if (raw && typeof raw.then === 'function') {//认为是Promise
                raw.then((data: any) => {
                    resolve(data);
                }).catch((err: any) => { reject(err); }); //终结的promise链必须捕获错误,否则丢失错误链
            } else {
                resolve(raw);
            }
        };

        if (dataCache[cacheKey]) {
            if (dataCache[cacheKey].status === 1) {
                dataCache[cacheKey].cbs.push(callback);
            } else if (dataCache[cacheKey].status === 2) {
                callback(dataCache[cacheKey].data);
            }
        } else {
            dataCache[cacheKey] = {
                status: 1,
                cbs: [],
                data: {}
            };
            if (!isCombo) {
                applyRequest(url, params, requestMethod, request).then((data: object) => {
                    dataCache[cacheKey].status = 2;
                    dataCache[cacheKey].data = data;
                    dataCache[cacheKey].cbs.forEach((cb: Function) => {
                        cb(data);
                    });
                    resolve(data);
                });
            } else {
                if (!busData[busKey]) {
                    busData[busKey] = {
                        paramList: [params],
                        url,
                        timer: setTimeout(sendRequest, collectTime)
                    };
                } else {
                    busData[busKey].paramList.push(params); // 加入参数队列
                    if (busData[busKey].paramList.length >= maxComboNum) {
                        // 发起请求
                        sendRequest();
                    }
                }
            }
        }
    }).catch((ex) => {
        if (errorHandle) {
            errorHandle(ex);
            return;
        }
        throw ex;
    });
};

const applyRequest = async (url: string, params: object, requestMethod = 'get', request: any, ) => {
    if (requestMethod === 'get') {
        return request[requestMethod](url, { params });
    } else {
        return request[requestMethod](url, { ...params });
    }
};

详见:https://github.com/LuckyWinty/ToolLibrary/tree/master/src/RequestCombo

Example

代码语言:javascript
复制
const ApiData = {
    getPrice: {
        url: '//test/prices',
        maxComboNum: 10,
        requestMethod: 'get',
        pack (paramList: object[]) {
            const skuids: string[] = [];
            paramList.forEach((p: any) => {
                if (p.skuids) {
                    skuids.push(p.skuids);
                }
            });
            const ret = {
                skuids: skuids.join(',')
            };

            console.log('合并后的价格参数', ret);
            return ret;
        },
        unpack: (data: any, paramList: object[]) => {
            if (data && data.data && length) {
                const resData = data.data || [];
                const ret = {};
                paramList.forEach((p: any) => {
                    const key = JSON.stringify(p);
                    resData.some((item: any, i: number) => {
                        const sku = item.sku;
                        if (sku === p.skuids) {
                            ret[key] = [data[i]];
                            return true;
                        }
                        return false;
                    });
                });
                console.log('价格拆解数据', ret);
                return ret;
            }
            return [];
        }
    }
};

const p1 = requestCombo(ApiData['getPrice'], { skuids: '11111' }, (data: any) => {
    console.log(data);
});
const p2 = requestCombo(ApiData['getPrice'], { skuids: '11112' }, (data: any) => {
    console.log(data);
});
const p3 = requestCombo(ApiData['getPrice'], { skuids: '11113' }, (data: any) => {
    console.log(data);
});
const data1 = await p1;
const data2 = await p2;
const data3 = await p3;

作为独立repo打包

这种情况适合使用 webpack 来作为打包器。我们主要配置几个点:

  • 支持各种模式的导入(umd、ES6的export、export default导出)
  • 打包压缩版用于生产环境,未压缩版用于开发环境
  • 将项目名与入口文件的返回值绑定(script引入时可以直接访问项目名称来访问包)

最后配置如下:

代码语言:javascript
复制
    //webpack.config.js
    const TerserPlugin = require('terser-webpack-plugin');

    module.exports = {
        entry: {
            'RequestCombo': './src/index.js',
            'RequestCombo.min': './src/index.js'
        },
        output: {
            filename: '[name].js',
            library: 'RequestCombo',
            libraryTarget: 'umd',
            libraryExport: 'default'
        },
        mode: 'none',
        optimization: {
            minimize: true,
            minimizer: [
                new TerserPlugin({
                    include: /\.min\.js$/,
                })
            ]
        }
    }

在工具库中,用 rollup 打包

这个跟 webpack 打包的目标是一致的。就是工具不同,配置稍有差异.

代码语言:javascript
复制
//为展示方便,删除了部分插件
const filesize = require('rollup-plugin-filesize')
const path = require('path')
const { terser } = require('rollup-plugin-terser')
const { name, version, author } = require('../package.json')

const componentName = process.env.COMPONENT
const componentType = process.env.COMPONENT_TYPE || 'js'

const banner = `${'/*!\n* '}${name}.js v${version}\n`
  + ` * (c) 2018-${new Date().getFullYear()} ${author}\n`
  + ' * Released under the MIT License.\n'
  + ' */'

module.exports = [
  {
    input: path.resolve(__dirname, `../src/${componentName}/src/index.${componentType}`),
    output: [
      {
        file: path.resolve(
          __dirname,
          `../src/${componentName}/dist/${componentName}.min.js`
        ),
        format: 'umd',
        name,
        banner,
        sourcemap: true,
      }
    ],
    plugins: [terser(), filesize()],
  },
  {
    input: path.resolve(__dirname, `../src/${componentName}/src/index.${componentType}`),
    output: {
      file: path.resolve(
        __dirname,
        `../src/${componentName}/dist/${componentName}.min.esm.js`
      ),
      format: 'esm',
      banner,
    },
    plugins: [terser(), filesize()],
  },
  {
    input: path.resolve(__dirname, `../src/${componentName}/src/index.${componentType}`),
    output: [
      {
        file: path.resolve(
          __dirname,
          `../src/${componentName}/dist/${componentName}.js`
        ),
        format: 'umd',
        name,
        banner,
      }
    ],
    plugins: [],
  }
]

详见:https://github.com/LuckyWinty/ToolLibrary

发布到 npm

相关命令 添加用户: npm adduser

登陆: npm login

发布版本: npm publish

升级版本:

  • 升级补丁版本号: npm version patch
  • 升级小版本号: npm version minor
  • 升级大版本号: npm version major
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-09-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端Q 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 主要逻辑设计
  • API 设计
  • 具体实现
  • Example
  • 作为独立repo打包
  • 在工具库中,用 rollup 打包
  • 发布到 npm
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档