背景
最近在做一个B端的项目, 其中订单页需要做一个轮询的处理, 能让用户在不刷新网页的情况下实时看到最新的订单状态. 项目使用的是vue2.7 + ts(装饰器)
想法
-
初始版本: 这里最开始想的比较简单, 直接用setInterval进行处理就好了. 由于使用的是keep-alive组件, 所以在activated中开启定时, 在deactivated中clearInterval就ok. 大概代码如下:
clearInterval(this.timer); await this.getList(); this.timer = setInterval(async () => { console.log('init'); await this.getList(); }, 5000);不过实际操作起来, 这种方式的局限性太大, 尤其是包含了异步操作. 在实际的场景中, 如果这个操作触发的过于频繁(比如分页会重新触发, 搜索会重新触发), 就很有可能上一次的setInterval没有成功清除, 导致页面数据错乱.
-
优化: 我注意到setTimeout可以很好的解决异步带来的问题, 于是封装了一个方法, 用于专门解决这类问题. 大概思路如下:
- 要实现setInterval的效果
- 需要可以自由控制暂停,开启
- 传入的(异步)函数可能有参数 下面是具体的代码:
export class RepeatedAsyncExecutor<T> {
private readonly asyncFn: (...args: T[]) => Promise<void>;
private readonly interval: number;
private timer: number | null = null;
private running = false;
private args: T[];
constructor(asyncFn: (...args: T[]) => Promise<void>, interval: number) {
this.asyncFn = asyncFn;
this.interval = interval;
this.args = [];
}
private async _execute() {
if (!this.running) return;
try {
await this.asyncFn(...this.args);
} catch (error) {
console.error('Error in async function:', error);
} finally {
if (this.running) {
this.timer = setTimeout(() => this._execute(), this.interval);
}
}
}
public start(...args: T[]) {
if (this.running) return;
this.running = true;
this.args = args;
this.timer = setTimeout(() => this._execute(), this.interval);
}
public stop() {
this.running = false;
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
}
}
public restart(...args: T[]) {
this.stop();
this.start(...args);
}
}
// 使用示例
const asyncFn = async (message: string) => {
await new Promise<void>(resolve => setTimeout(resolve, 2000));
console.log('Async function executed:', message, new Date().toISOString());
};
const executor = new RepeatedAsyncExecutor<string>(asyncFn, 5000);
executor.start('Hello World');
// 如果需要停止执行,可以调用
executor.stop();
// 如果需要重新开始并更改参数(例如分页操作后),可以调用
executor.restart('New Message');
注意
这里的中断并不能中断已经开始执行的代码, 运行中代码依然会继续执行. 如果确实需要中断异步代码, 可以通过锁的机制来处理.