mini-dlpu-v3/miniprogram/core/Api.ts
2022-01-20 21:42:44 +08:00

701 lines
18 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<API<IAnyData, IAnyData>>;
}
}
interface IAnyData {
[x:string]: any
}
type IWxRequestOption<O> = WechatMiniprogram.RequestOption<O>;
type DeepReadonly<T> = {
readonly [P in keyof T]: DeepReadonly<T[P]>;
};
/**
* 请求参数设置
*/
type IParamSetting<T extends IAnyData> = {
[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<Partial<T>>) => T[P] | undefined),
/**
* 是否为请求头数据
*/
isHeader?: boolean,
/**
* 是否为模板
*/
isTemplate?: boolean
/**
* 是否可选
*/
Optional?: boolean
}
}
type SuccessCallbackResult<O extends IAnyData> = WechatMiniprogram.RequestSuccessCallbackResult<O>;
type GeneralCallbackResult = WechatMiniprogram.GeneralCallbackResult;
/**
* API 事件
*/
type IAPIEvent<I extends IAnyData, O extends IAnyData> = {
/**
* 当数据初始化事件
*/
initData: Partial<I>;
/**
* 请求数据解析完成后
*/
parseRequestData: Partial<I>;
/**
* 请求发送前
*/
request: IWxRequestOption<O>;
/**
* 成功回调
*/
success: SuccessCallbackResult<O>;
/**
* 失败回调
*/
fail: GeneralCallbackResult;
/**
* 完成回调
*/
complete: GeneralCallbackResult;
}
/**
* 输出事件类型
*/
type IAPIResultEvent<O extends IAnyData, U extends IAnyData> = {
/**
* 成功获取数据
*/
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<EventType, any> = Record<EventType, any>,
U extends IAnyData = IAnyData
> extends Emitter <
{
// 这个复杂的泛型是为了 MixIn 用户自定义事件类型
// 懂得如何使用就可以了
// 不要试图去理解下面这三行代码,真正的恶魔在等着你
[P in (keyof (IAPIEvent<I, O> & IAPIResultEvent<O, U>) | keyof E)] :
P extends keyof IAPIEvent<I, O> ? IAPIEvent<I, O>[P] :
P extends keyof IAPIResultEvent<O, U> ? IAPIResultEvent<O, U>[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<I> = {} as any;
/**
* API 需要的数据
*/
public data?:Partial<I>;
/**
* 请求数据
*/
public requestData?:IWxRequestOption<O>;
//#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<I>):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<O> = 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<Partial<I>>);
}
}
// 触发数据解析
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<IAppAPIParam>();
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<IAppAPIParam>();
app.api.pool.delete(this as any);
return this;
}
/**
* 寻找相似的请求
*/
protected findSameAPI():API<IAnyData, IAnyData> | undefined {
if (this.requestData === void 0) {
Logger.log(`搜索相似请求异常: 没有收集 [requestData] 数据!`,
LevelLogLabel.ErrorLabel, this.LogLabel);
return;
}
let app = getApp<IAppAPIParam>();
let sameAPI:API<IAnyData, IAnyData> | 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<O>({
...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<O>);
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<IRespondData<O>>;
public waitRequest(callBack: ICallBack<O>): this;
public waitRequest(callBack?: ICallBack<O>): Promise<IRespondData<O>> | 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<IAPIResultEvent<O, U>["done"]>;
public wait(callBack: IResCallBack<O, U>): this;
public wait(callBack?: IResCallBack<O, U>): Promise<IAPIResultEvent<O, U>["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<I>) => 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<O extends IAnyData> = Partial<IAPIEvent<IAnyData, O>["success"]> & IAPIEvent<IAnyData, O>["fail"];
/**
* 回调对象
*/
interface ICallBack<O extends IAnyData> {
success?: (data: IAPIEvent<{}, O>["success"]) => any;
fail?: (data: IAPIEvent<{}, O>["fail"]) => any;
complete?: (data: IAPIEvent<{}, O>["complete"]) => any;
}
interface IResCallBack<O extends IAnyData, U extends IAnyData> {
ok?: (data: IAPIResultEvent<O, U>["ok"]) => any;
no?: (data: IAPIResultEvent<O, U>["no"]) => any;
done?: (data: IAPIResultEvent<O, U>["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 }