优化Promise.race递归调用实现绘图工具事件流逻辑
解决递归Promise.race导致大量Promise未resolve的问题
你的核心问题在于:每次调用counterFlow时,都会同时创建doCounter和quit两个Promise,当其中一个事件触发(比如点击)并resolve对应的Promise后,另一个事件的监听器(比如keydown)并没有被移除,导致它对应的Promise一直处于pending状态,递归次数越多,积累的pending Promise就越多。
优化方案核心思路
不管哪个事件先触发,都要主动移除另一个未触发的事件监听器,同时结束对应的Promise(避免pending),这样既能保持全局状态整洁,也不会积累未resolve的Promise。
修改后的代码实现
function startCount(node) { const counter = Object.create(counterMethods); counter.node = node; counter.number = 0; // 存储当前活跃的事件处理器,方便后续移除 counter.activeHandlers = {}; return counter; } const counterMethods = { // 封装通用的事件监听Promise创建方法 createEventPromise(eventType, filterFn) { return new Promise((resolve) => { const handler = (e) => { // 满足过滤条件才触发 if (filterFn?.(e) !== false) { this.node.removeEventListener(eventType, handler); delete this.activeHandlers[eventType]; resolve(e); } }; this.node.addEventListener(eventType, handler); this.activeHandlers[eventType] = handler; }); }, // 主动移除所有活跃的事件监听器 clearActiveListeners() { Object.entries(this.activeHandlers).forEach(([eventType, handler]) => { this.node.removeEventListener(eventType, handler); }); this.activeHandlers = {}; }, async counterFlow() { try { // 创建点击和退出的事件Promise const clickPromise = this.createEventPromise('click'); const quitPromise = this.createEventPromise('keydown', (e) => e.key === 'Escape'); const result = await Promise.race([ clickPromise.then(() => 'click'), quitPromise.then(() => 'exit') ]); if (result === 'exit') { console.log(`已退出,累计点击次数:${this.number}`); // 退出时清除所有剩余监听器 this.clearActiveListeners(); } else if (result === 'click') { this.number += 1; console.log(`当前点击次数:${this.number}`); // 点击后清除退出监听器(避免残留) this.clearActiveListeners(); // 递归进入下一轮监听 await this.counterFlow(); } } catch (err) { console.error('监听流程出错:', err); this.clearActiveListeners(); } }, };
关键改动说明
- 通用事件Promise封装:
createEventPromise统一处理事件监听、过滤和移除逻辑,同时把活跃的处理器存在实例的activeHandlers中,方便后续批量清理。 - 强制清理监听器:不管是点击还是退出触发,都会调用
clearActiveListeners移除所有剩余的事件监听器,确保没有残留的监听器和pending Promise。 - 递归时使用await:递归调用
counterFlow时加上await,保证异步流程的顺序性,避免出现多个并行的监听流程。
这样修改后,每次监听周期结束都会清理所有未触发的监听器,不会积累pending Promise,同时也不需要维护额外的全局状态变量,符合你保持状态整洁的需求。
内容的提问来源于stack exchange,提问作者chart3r
相关产品推荐
相关产品推荐

