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» и учиться и расти вместе~~