UNPKG

api-simplex-handler

Version:

一个api封装解析器

274 lines (273 loc) 9.64 kB
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 };