Angular RxJS并发倒计时重启异常:API重复调用及循环调用问题排查
问题排查与解决方案
核心问题分析
- 重复API调用:每个事件的倒计时订阅独立判断自身timer是否归0,一旦满足条件就调用
getEvents。如果有多个事件同时结束,就会触发多次API请求;且旧订阅未在刷新前取消,会继续触发后续调用。 - 订阅泄漏与无限循环:单个
Subject管理所有订阅的方式存在漏洞,调用getEvents后会创建新的订阅和Subject,旧订阅若未被正确终止,会持续操作旧的事件数据,导致timer归0后再次触发刷新,形成无限循环。
修正后的代码
// helper.service.ts import { Subscription, timer } from 'rxjs'; // 存储所有倒计时订阅 private countdownSubs: Subscription[] = []; // 防止重复调用API的锁 private isRefreshing = false; getCounter() { return timer(0, 1000); } getEvents() { // 避免并发刷新 if (this.isRefreshing) return; const url = '/app/events'; this.isRefreshing = true; this.auth.get(url).then(x => { this.eventArr = x?.data.events || []; // 先清除旧订阅,再创建新的倒计时 this.clearCountdownSubs(); this.showTimer(); }).catch(err => { console.log(err); }).finally(() => { // 释放刷新锁 this.isRefreshing = false; }); } // 统一清除所有倒计时订阅 private clearCountdownSubs() { this.countdownSubs.forEach(sub => sub.unsubscribe()); this.countdownSubs = []; } showTimer() { if (this.eventArr.length === 0) return; // 初始化每个事件的剩余秒数,避免负数 this.eventArr.forEach(x => { const dateFuture = x?.inaugurateDt * 1000; const now = new Date().getTime(); const t = dateFuture - now; x.timer = Math.max(Math.floor(t / 1000), 0); }); // 单个timer统一更新所有事件的倒计时,减少订阅开销 const sub = this.getCounter().subscribe(() => { let needRefresh = false; this.eventArr.forEach(x => { if (x.timer > 0) { x.timer--; } // 标记是否需要刷新事件 if (x.timer === 0) { needRefresh = true; } }); if (needRefresh) { this.clearCountdownSubs(); this.getEvents(); } }); this.countdownSubs.push(sub); }
关键修优点说明
- 统一订阅管理:用
countdownSubs数组存储所有倒计时订阅,每次刷新事件前强制清除旧订阅,彻底避免旧逻辑干扰新数据。 - 刷新锁机制:
isRefreshing变量确保同一时间只有一次API请求,从根源解决重复调用问题。 - 全局timer优化:单个timer更新所有事件的倒计时,减少RxJS订阅数量,提升性能的同时简化逻辑。
- 负数防护:用
Math.max确保timer不会出现负数,避免无意义的刷新触发。
内容的提问来源于stack exchange,提问作者Somnath Pal
相关产品推荐
相关产品推荐

