支付层
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* 轮询器(指数退避+超时+可取消)
|
||||
*
|
||||
*/
|
||||
|
||||
import { PaymentApi } from "./PaymentApi";
|
||||
import type { QueryOrderData } from "./types";
|
||||
import { ApiCode } from "../network/client/types";
|
||||
|
||||
export interface PollOptions {
|
||||
maxDurationMs: number; // 轮询总时长(例如 2分钟)
|
||||
startIntervalMs: number; // 初始间隔(例如 2s)
|
||||
maxIntervalMs: number; // 最大间隔(例如 10s)
|
||||
jitter?: boolean; // 抖动
|
||||
}
|
||||
|
||||
export class PaymentPoller {
|
||||
private cancelled = false;
|
||||
|
||||
// 取消轮询
|
||||
cancel() { this.cancelled = true; }
|
||||
|
||||
// 轮询订单
|
||||
async poll(orderId: string, opt: PollOptions, onTick?: (d: QueryOrderData) => void): Promise<QueryOrderData | null> {
|
||||
const t0 = Date.now();
|
||||
let interval = opt.startIntervalMs;
|
||||
while (!this.cancelled) {
|
||||
// 查询
|
||||
const r = await PaymentApi.I.queryOrder(orderId);
|
||||
if (r.code === ApiCode.OK && r.data) {
|
||||
// 回调
|
||||
onTick?.(r.data);
|
||||
if (["SUCCESS", "FAILED", "CANCELED", "EXPIRED"].indexOf(r.data.status) !== -1) {
|
||||
return r.data;
|
||||
}
|
||||
}
|
||||
// 退出条件
|
||||
if (Date.now() - t0 > opt.maxDurationMs) return null;
|
||||
// 等待
|
||||
await this.sleep(this.jitter(interval, opt));
|
||||
// 每次轮询后,interval 会按照 1.5 倍增长,直到达到最大间隔 maxIntervalMs,避免在短时间内重复请求
|
||||
interval = Math.min(interval * 1.5, opt.maxIntervalMs);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 用来暂停一定的时间(ms 毫秒),并让轮询进入等待状态
|
||||
private sleep(ms: number) { return new Promise(res => setTimeout(res, ms)); }
|
||||
|
||||
// 用于实现抖动(随机化轮询间隔)
|
||||
private jitter(base: number, opt: PollOptions) {
|
||||
if (!opt.jitter) return base;
|
||||
const delta = Math.min(500, Math.max(100, base * 0.1));
|
||||
return base + (Math.random() * 2 - 1) * delta;
|
||||
}
|
||||
}
|
||||
export default PaymentPoller;
|
||||
Reference in New Issue
Block a user