Агрегация интерфейсов во внешнем интерфейсе

JavaScript
Агрегация интерфейсов во внешнем интерфейсе

request-combo

Это интерфейсный упрощенный модуль агрегации интерфейса, который в основном используется в следующих сценариях:

  • Интерфейс, который поддерживает слияние параметров, вызывает один и тот же интерфейс с разными параметрами в компонентах или других сценариях, а затем объединяет эти вызовы в один или несколько интерфейсов, а затем запросы.
  • Не инициируйте один и тот же запрос. В некоторых случаях инициируется один и тот же запрос. После сбора и обработки фактически инициируется только один запрос. Но обратные вызовы разных инициаторов могут быть обработаны.

основная логическая конструкция

  • Чтобы знать основную информацию об интерфейсе, включая, помимо прочего, URL-адрес, параметры, обратный вызов...
  • Поскольку вы хотите агрегировать, должна быть очередь для интерфейса коллекции.
  • Очередь каждого интерфейса должна быть stateful, при приходе нового интерфейса очередь интерфейса может не создаваться, а может собираться, а запрос может быть просто отправлен.
  • Должно быть условие, чтобы интерфейсная очередь инициировала запрос, достаточно времени сбора или достаточно длины сбора...
  • Есть механизм кэширования, и полученные данные временно кэшируются

Дизайн API

Метод вызова: requestCombo() параметр:

    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'

Реализация

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 });
    }
};

Видеть:GitHub.com/счастливая победа TY/…

Example

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) {
                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;

Упакован как отдельный репозиторий

Эта ситуация подходит для использования webpack в качестве сборщика. В основном настраиваем несколько пунктов:

  • Поддерживает импорт различных режимов (umd, экспорт ES6, экспорт по умолчанию)
  • Упакованная сжатая версия используется в производственной среде, а несжатая версия используется в среде разработки.
  • Привяжите имя проекта к возвращаемому значению файла записи (когда скрипт импортируется, вы можете напрямую обращаться к имени проекта для доступа к пакету)

Окончательная конфигурация выглядит следующим образом:

    //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$/,
                })
            ]
        }
    }

В репозитории, запакованный роллапом

Это согласуется с целью упаковки webpack. Инструменты разные и конфигурация немного другая.

//为展示方便,删除了部分插件
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: [],
  }
]

Видеть:GitHub.com/счастливая победа TY/…

Опубликовать в нпм

Связанные командыДобавить пользователя: npm adduser

Логин: npm логин

Версия выпуска: публикация npm

обновленная версия:

  • Номер версии патча обновления: патч версии npm
  • Номер младшей версии обновления: второстепенная версия npm
  • Номер основной версии обновления: основная версия npm

наконец

  • Для получения дополнительной информации, пожалуйста, следуйте за мнойblog, дай мне звезду~
  • Если вы считаете, что контент полезен, вы можете обратить внимание на мой официальный аккаунт «Front-end Q» и учиться и расти вместе~~
    GitHub