api-simplex-handler
Version:
一个api封装解析器
274 lines (273 loc) • 9.64 kB
JavaScript
class ApiHandler {
api;
expect;
success;
exception;
fail;
cache = new Map();
argsQueue = new Array();
limitSet = 0;
beforeList = new Array();
afterList = new Array();
guardList = new Array();
completeList = new Array();
getURL() {
if (!this.api.path)
throw new Error("not setting url");
return this.api.path;
}
composeURL(args) {
if (!this.api.path)
throw new Error("not setting url");
let path = this.api.path;
if (Array.isArray(this.api.params)) {
const params = ApiHandler.assemblyParameters(this.api.params, args, this.api);
if (typeof params === 'string')
path += params;
}
else if (typeof this.api.params !== 'undefined') {
const params = ApiHandler.assemblyParametersToUnion(this.api.params, args, this.api);
path += params.path + params.query;
}
return path;
}
constructor(api, responseHandlerDefineFunction) {
this.api = api;
if (Array.isArray(this.api.hanger)) {
this.api.hanger.forEach((v) => this.analysisHanger(v, api));
}
else if (typeof this.api.hanger !== 'undefined')
this.analysisHanger(this.api.hanger, api);
this.expect = responseHandlerDefineFunction.expectFunction;
this.success = responseHandlerDefineFunction.successFunction;
this.exception = responseHandlerDefineFunction.exceptionFunction;
this.fail = responseHandlerDefineFunction.failFunction;
}
analysisHanger(hangerIndex, api) {
const { before, after, guard, complete } = hangerIndex(api);
if (typeof before !== "undefined")
this.beforeList.push(before);
if (typeof after !== "undefined")
this.afterList.push(after);
if (typeof guard !== "undefined")
this.guardList.push(guard);
if (typeof complete !== "undefined")
this.completeList.push(complete);
}
clearCache() {
for (const [key, cacheData] of this.cache.entries()) {
localStorage.removeItem(`api-handler-${this.api.cacheKey ?? ''}-${key}`);
}
this.cache.clear();
for (const key in localStorage) {
if (key.startsWith(`api-handler-${this.api.cacheKey ?? ''}-${this.api.path}`))
localStorage.removeItem(key);
}
}
saveCacheHandler(args, data) {
if (typeof this.api.cachePolicy === "undefined" || this.api.cachePolicy === 'noCache')
return;
const key = this.api.path + JSON.stringify(args);
if (this.cache.has(key)) {
this.cache.get(key).lastTime = new Date();
}
else {
const cacheData = {
data,
lastTime: new Date()
};
this.cache.set(key, cacheData);
if (this.api.cachePolicy === 'save')
localStorage.setItem(`api-handler-${this.api.cacheKey ?? ''}-${key}`, JSON.stringify(data));
}
}
async cacheHandler(args) {
const key = this.api.path + JSON.stringify(args);
if (!this.cache.has(key)) {
if (this.api.cachePolicy === 'save') {
const localStorageData = localStorage.getItem(`api-handler-${this.api.cacheKey ?? ''}-${key}`);
if (localStorageData === null)
return this.send(args);
else
return Promise.resolve().then(() => {
const cacheData = JSON.parse(localStorageData);
this.cache.set(key, {
data: cacheData,
lastTime: new Date()
});
return cacheData;
});
}
return this.send(args);
}
const cacheData = this.cache.get(key);
if (this.api.cachePolicy === 'dynamic') {
if (new Date().getTime() - cacheData.lastTime.getTime() > (this.api.expirationTime ?? 1000 * 60 * 30))
return this.send(args);
}
return Promise.resolve().then(() => {
return cacheData.data;
});
}
async sendNotSuccessTips(args) {
return this.sendHandler(args, { successFunction: null });
}
startNext() {
if (this.argsQueue.length == 0)
return;
if (typeof this.api.limit === 'undefined' || this.api.limit <= 0)
return;
while (this.limitSet < this.api.limit) {
this.limitSet += 1;
const resolve = this.argsQueue.shift();
resolve(1);
}
}
async sendLimit(args) {
await new Promise((resolve) => {
if (typeof this.api.limit === 'undefined' || this.api.limit <= 0)
return resolve(1);
if (this.limitSet < this.api.limit) {
this.limitSet += 1;
return resolve(1);
}
this.argsQueue.push(resolve);
});
try {
return await this.cacheHandler(args);
}
finally {
this.limitSet -= 1;
this.startNext();
}
}
hangerBefore(args) {
for (const beforeListElement of this.beforeList) {
if (beforeListElement) {
args = beforeListElement(args);
}
}
return args;
}
hangerComplete(res) {
return this.completeList.reduce((c, p) => {
if (p)
return p(c);
return c;
}, res);
}
hangerAfter(res) {
return this.afterList.reduce((c, p) => {
if (p)
return p(c);
return c;
}, res);
}
hangerGuard(args, info) {
const start = this.guardList.reduceRight((next, c) => {
if (c)
return (a) => c(a, info, next);
return next;
}, ((args) => this.sendLimit(args)));
if (start)
return start(args);
return this.sendLimit(args);
}
async sendHandler(args, responseHandlerFunction) {
const requestTime = new Date();
const info = {
requestTime,
completeTime: new Date()
};
args = this.hangerBefore(args);
const resp = this.hangerComplete(this.hangerGuard(args, info));
return this.hangerAfter(resp
.catch(e => {
if (e instanceof Error)
this.processResponse(this.fail, responseHandlerFunction?.failFunction, e);
throw e;
})
.then(sendResponse => {
info['completeTime'] = new Date();
const isRight = this.expect(sendResponse);
if (isRight) {
this.processResponse(this.success, responseHandlerFunction?.successFunction, sendResponse);
this.saveCacheHandler(args, sendResponse);
return { data: sendResponse.data, info };
}
else {
this.processResponse(this.exception, responseHandlerFunction?.exceptionFunction, sendResponse);
throw new Error(sendResponse.message);
}
}));
}
processResponse(define, custom, ...args) {
if (typeof custom !== "undefined") {
if (custom != null)
custom.apply(this, args);
}
else
define.apply(this, args);
}
static filteredData(data) {
return Object.fromEntries(Object.entries(data).filter(([_, value]) => value !== undefined && value !== null));
}
static toQueryString(data) {
const queryString = new URLSearchParams(ApiHandler.filteredData(data)).toString();
return queryString.length > 0 ? `?${queryString}` : '';
}
static toPathString(data) {
const path = Object.entries(ApiHandler.filteredData(data)).map(([_, value]) => {
return `${value}`;
}).join("/");
return ("/" + path).substring(1);
}
static paramsToObject(params, args) {
return params.reduce((p, c) => {
p[c] = args[c];
return p;
}, {});
}
static assemblyParameters(params, args, api) {
if (typeof args === 'undefined')
return;
const paramsType = api.paramsDefault ?? "query";
const paramsObject = this.paramsToObject(params, args);
if (paramsType === "body")
return paramsObject;
if (paramsType === 'query')
return this.toQueryString(paramsObject);
return this.toPathString(paramsObject);
}
static assemblyParametersToUnion(params, args, api) {
if (typeof args === 'undefined')
return {
body: {},
query: "",
path: "",
};
const types = Object.entries(params).reduce((p, [k, v]) => {
let path = k;
let paramsType = api.paramsDefault ?? "query";
if (typeof v === 'string')
path = v;
else {
path = v.name ?? k;
paramsType = v.type ?? paramsType;
}
// @ts-ignore
p[paramsType][path] = args[path];
return p;
}, {
body: {},
query: {},
path: {},
});
return {
body: types.body,
query: this.toQueryString(types.query),
path: this.toPathString(types.path)
};
}
}
export { ApiHandler };