import { Emitter, EventType } from "./Emitter"; import { API_FAILED_SHOW_MESSAGE } from "./Config"; import { Logger, LogLabel, LevelLogLabel, colorRadio, StatusLabel } from "./Logger"; interface IAppAPIParam { api: { /** * API 编号 */ nextId: number; /** * 请求池 * 保存正在等待的 API 请求 */ pool: Set>; } } interface IAnyData { [x:string]: any } type IWxRequestOption = WechatMiniprogram.RequestOption; type DeepReadonly = { readonly [P in keyof T]: DeepReadonly; }; /** * 请求参数设置 */ type IParamSetting = { [P in keyof T]: { /** * 键值映射 */ mapKey?: string, /** * 默认值 */ defaultValue?: T[P], /** * ### 数据测试 * 1、支持正则表达式测试 \ * 2、支持使用 string === string 测试 \ * 3、支持使用 number === number 测试 \ * 4、支持使用自定义函数测试 */ tester?: RegExp | ((data:T[P]) => boolean) | string | number, /** * ### 预解析函数 * 1、此函数用来处理该键值 \ * 2、当返回 undefined 时此键值被遗弃 \ * 3、返回值时,此键值被覆盖 * * @param data 当前给定数据 * @param key 当前给定数据键值 * @param all 全部输入数据 * @returns 解析结果 */ parse?: ((data:T[P], key:string, all:DeepReadonly>) => T[P] | undefined), /** * 是否为请求头数据 */ isHeader?: boolean, /** * 是否为模板 */ isTemplate?: boolean /** * 是否可选 */ Optional?: boolean } } type SuccessCallbackResult = WechatMiniprogram.RequestSuccessCallbackResult; type GeneralCallbackResult = WechatMiniprogram.GeneralCallbackResult; /** * API 事件 */ type IAPIEvent = { /** * 当数据初始化事件 */ initData: Partial; /** * 请求数据解析完成后 */ parseRequestData: Partial; /** * 请求发送前 */ request: IWxRequestOption; /** * 成功回调 */ success: SuccessCallbackResult; /** * 失败回调 */ fail: GeneralCallbackResult; /** * 完成回调 */ complete: GeneralCallbackResult; } /** * 输出事件类型 */ type IAPIResultEvent = { /** * 成功获取数据 */ ok: O, /** * 无论因为什么 * 总之数据获取到 */ no: GeneralCallbackResult & U, /** * 完成了 * 无论失败与否 */ done: { data?: O } & GeneralCallbackResult & U } /** * API 接口调用 * @template I API 输入数据 * @template O API 输出数据 * @template E API 中的事件 * @template U 用户自定义的输出数据 */ class API< I extends IAnyData = IAnyData, O extends IAnyData = IAnyData, E extends Record = Record, U extends IAnyData = IAnyData > extends Emitter < { // 这个复杂的泛型是为了 MixIn 用户自定义事件类型 // 懂得如何使用就可以了 // 不要试图去理解下面这三行代码,真正的恶魔在等着你 [P in (keyof (IAPIEvent & IAPIResultEvent) | keyof E)] : P extends keyof IAPIEvent ? IAPIEvent[P] : P extends keyof IAPIResultEvent ? IAPIResultEvent[P] : E[P] } > { /** * 默认调试标签 */ public static defaultLogLabel:LogLabel = new LogLabel( `API:API`, colorRadio(200, 120, 222) ); /** * 基础 URL * TODO: 这里可能涉及负载均衡 */ public baseUrl: string = "https://jwc.nogg.cn"; /** * Logger 使用的标签 */ private LogLabel:LogLabel = API.defaultLogLabel; /** * Api 唯一 ID */ public key:string = "API"; /** * API url */ public url:string = "/"; /** * API 需要的参数列表 */ public params:IParamSetting = {} as any; /** * API 需要的数据 */ public data?:Partial; /** * 请求数据 */ public requestData?:IWxRequestOption; //#region wx.request public timeout?:number; public method:HTTPMethod = HTTPMethod.GET; public enableHttp2:boolean = false; public enableQuic:boolean = false; public enableCache:boolean = false; /** * 是否自动解析返回的 json * 对应 wx.request 的 dataType */ public jsonParse:boolean = true; //#endregion wx.request /** * 初始化标签 */ public initDebugLabel(key: string) { this.key = key; this.LogLabel = new LogLabel( `API:${ this.key }`, colorRadio(200, 120, 222) ); // 添加 API 生命周期调试事件 this.on("request", (data) => { Logger.logMultiple( [LevelLogLabel.InfoLabel, this.LogLabel, StatusLabel.Pending], `请求发送中: `, data ); }) this.on("success", (data) => { Logger.logMultiple( [LevelLogLabel.InfoLabel, this.LogLabel, StatusLabel.Success], `请求成功: `, data ); }) this.on("fail", (data) => { Logger.logMultiple( [LevelLogLabel.ErrorLabel, this.LogLabel, StatusLabel.Failed], `请求失败: `, data.errMsg ); }) } /** * 初始化数据 * 注意:data 是不安全的,请传入数据副本 * @param data API需要的全部数据 */ public param(data?: Partial):this { this.data = data; if (this.data === void 0) { Logger.log(`数据初始化异常: 没有输入 [data] 数据!`, LevelLogLabel.ErrorLabel, this.LogLabel); return this; } for (let key in this.params) { let data = this.data[key]; let { defaultValue, Optional, tester } = this.params[key]; // 默认值赋予 if (data === void 0 && defaultValue !== void 0) { this.data[key] = defaultValue; } // 数据存在测试 if (data === void 0 && !Optional) { Logger.log(`数据校验异常: 数据 [${key}] 是必须的,但是并没有接收到!`, LevelLogLabel.ErrorLabel, this.LogLabel); } // 用户自定义测试 if (data !== void 0 && tester !== void 0) { let testRes:boolean = false; if (tester instanceof RegExp) { testRes = tester.test(data!); } else if (typeof tester === "string" || typeof tester === "number") { testRes = tester === data; } else if (tester instanceof Function) { testRes = tester(data!); } else { Logger.logMultiple( [LevelLogLabel.ErrorLabel, this.LogLabel], `数据校验异常: [${ key }] 参数存在未知类型的 tester:`, tester ); } if (!testRes) { Logger.logMultiple( [LevelLogLabel.ErrorLabel, this.LogLabel], `数据校验异常: [${ key }] 参数数据未通过自定义的 tester:`, data ); } } } // 触发数据初始化事件 this.emit("initData", this.data); // 重置请求数据 const requestData:IWxRequestOption = this.requestData = { url: this.baseUrl + this.url, data: {}, header: {}, timeout: this.timeout, method: this.method, dataType: this.jsonParse ? "json" : undefined, enableHttp2: this.enableHttp2, enableQuic: this.enableQuic, enableCache: this.enableCache }; // 数据解析 for (let key in this.params) { let data = this.data[key]; let { parse } = this.params[key]; // 数据预解析 if (parse !== void 0) { this.data[key] = parse(data!, key, this.data as DeepReadonly>); } } // 触发数据解析 this.emit("parseRequestData", this.data); // 数据收集 for (let key in this.params) { let data = this.data[key]; let { isHeader, isTemplate, mapKey } = this.params[key]; let useKey = mapKey ?? key; // 加载数据 if (!isTemplate) { if (isHeader) { requestData.header![useKey] = data; } else { (requestData.data as IAnyData)[useKey] = data; } } } return this; } /** * 请求的唯一标识符 */ protected id:number = 0; /** * 将此请求加入到请求池 */ protected addPool():this { let app = getApp(); if (app.api.pool.has(this as any)) { return this; } // 获取标识符 if (!this.id) { this.id = app.api.nextId; app.api.nextId ++; } app.api.pool.add(this as any); return this; } /** * 从 pool 中移除 */ protected removePool():this { let app = getApp(); app.api.pool.delete(this as any); return this; } /** * 寻找相似的请求 */ protected findSameAPI():API | undefined { if (this.requestData === void 0) { Logger.log(`搜索相似请求异常: 没有收集 [requestData] 数据!`, LevelLogLabel.ErrorLabel, this.LogLabel); return; } let app = getApp(); let sameAPI:API | undefined; // 判断 API 是否相似 app.api.pool.forEach((api) => { if ((api as API | this) === this) return; if (!api.requestData) return; if (api.requestData!.url !== this.requestData!.url) return; if (api.requestData!.method !== this.requestData!.method) return; sameAPI = api; }); return sameAPI; } /** * 标记此实例是否已被使用 */ private isUsed = false; /** * 请求策略 */ public policy:RequestPolicy = RequestPolicy.RequestAnyway; /** * 运行 API */ public request():this { if (this.requestData === void 0) { Logger.log(`请求发送异常: 没有收集 [requestData] 数据!`, LevelLogLabel.ErrorLabel, this.LogLabel); return this; } if (this.isUsed) { Logger.log(`请求发送异常: 此实例已经使用过了,请使用新实例操作`, LevelLogLabel.ErrorLabel, this.LogLabel); return this; } else { this.isUsed = true; } // 加入请求池 this.addPool(); // 发起请求 let request = () => { // 触发请求发送事件 this.emit("request", this.requestData!) wx.request({ ...this.requestData!, success: (e) => { this.emit("success", e); }, fail: (e) => { this.emit("fail", e); }, complete: (e) => { this.emit("complete", e); } }); } if (this.policy !== RequestPolicy.RequestAnyway) { let lastAPI = this.findSameAPI(); if (lastAPI) { // 被上次请求阻止 if (this.policy === RequestPolicy.BlockByLastRequest) { return this; } // 使用上次请求结果 if (this.policy === RequestPolicy.useLastRequest) { lastAPI.on("success", (e) => { this.emit("success", e as SuccessCallbackResult); this.emit("complete", {errMsg: e.errMsg}); }); lastAPI.on("fail", (e) => { this.emit("fail", e); this.emit("complete", {errMsg: e.errMsg}); }); } // 等待上次请求 if (this.policy === RequestPolicy.waitLastRequest) { lastAPI.on("success", () => request()); lastAPI.on("fail", () => request()); } } } else { request(); } // 监听请求完成后,从 pool 中移除,释放内存 this.on("complete", () => { this.removePool(); }) return this; } /** * 等待结果 */ public waitRequest(): Promise>; public waitRequest(callBack: ICallBack): this; public waitRequest(callBack?: ICallBack): Promise> | this { // 存在 callback 使用传统回调 if (callBack) { callBack.success && this.on("success", callBack.success); callBack.fail && this.on("fail", callBack.fail); callBack.complete && this.on("complete", callBack.complete); return this; } // 不存在 callback 使用 Promise 对象 else { return new Promise((r) => { this.on("success", r); this.on("fail", r); }); } } public wait(): Promise["done"]>; public wait(callBack: IResCallBack): this; public wait(callBack?: IResCallBack): Promise["done"]> | this { // 存在 callback 使用传统回调 if (callBack) { callBack.ok && this.on("ok", callBack.ok); callBack.no && this.on("no", callBack.no); callBack.done && this.on("done", callBack.done); return this; } // 不存在 callback 使用 Promise 对象 else { return new Promise((r) => { this.on("done", r); }); } } /** * 请求加载时显示加载动画 * @param message 消息 可以为函数 * @param mask 使用蒙版阻止点击穿透 */ public showLoading(message: string | ((data?: Partial) => string), mask: boolean = false): this { // 获取标题 let title: string = message instanceof Function ? message(this.data) : message; this.on("request", () => { wx.showLoading({ title, mask }) }); this.on("complete", () => { wx.hideLoading(); }); return this; }; /** * 显示导航栏加载动画 */ public showNavigationBarLoading(): this { this.on("request", () => { wx.showNavigationBarLoading() }); this.on("complete", () => { wx.hideNavigationBarLoading(); }); return this; } /** * 自动挂载失败回调 */ public addFailedCallBack(): this { this.on("fail", (e) => { this.emit("no", e as any); this.emit("done", e as any); }); return this; } /** * 请求失败后的提示语句 */ public showFailed(): this { // 生成随机索引值 let randomIndex = Math.floor( Math.random() * API_FAILED_SHOW_MESSAGE.length ); this.on("fail", () => { wx.showToast({ title: API_FAILED_SHOW_MESSAGE[randomIndex], icon: "none" }); }); return this; }; } /** * 自动响应数据 */ type IRespondData = Partial["success"]> & IAPIEvent["fail"]; /** * 回调对象 */ interface ICallBack { success?: (data: IAPIEvent<{}, O>["success"]) => any; fail?: (data: IAPIEvent<{}, O>["fail"]) => any; complete?: (data: IAPIEvent<{}, O>["complete"]) => any; } interface IResCallBack { ok?: (data: IAPIResultEvent["ok"]) => any; no?: (data: IAPIResultEvent["no"]) => any; done?: (data: IAPIResultEvent["done"]) => any; } /** * Request 请求策略 * 此策略用于节流 */ enum RequestPolicy { /** * 什么都不管,发就完了 */ RequestAnyway = 1, /** * 如果存在等待中的相似请求 * 等待相似请求结束后在发送 */ waitLastRequest = 2, /** * 如果存在等待中的相似请求 * 阻止本次请求发送 * 将相似请求的结果作为本次请求结果 */ useLastRequest = 3, /** * 如果存在等待中的相似请求 * 阻止本次请求发送 */ BlockByLastRequest = 4 } /** * HTTP 请求类型 */ enum HTTPMethod { OPTIONS = "OPTIONS", GET = "GET", HEAD = "HEAD", POST = "POST", PUT = "PUT", DELETE = "DELETE", TRACE = "TRACE", CONNECT = "CONNECT" } export default API; export { API, IParamSetting, IAppAPIParam, IAnyData, ICallBack, HTTPMethod, RequestPolicy, GeneralCallbackResult }