递归检测runningTimers数组长度触发调用栈溢出问题该如何解决?
问题产生原因
JavaScript是单线程事件驱动模型,同步代码的执行优先级远高于setTimeout回调这类宏任务,你的错误由以下逻辑问题共同导致:
- 你写的
checkTimers是同步递归函数,调用时只要runningTimers长度不为0,就会立刻同步调用自身,整个过程始终占用JS主线程,事件循环根本没有空闲时间去执行已经到点的定时器回调,runningTimers永远不会被清空,递归会无限执行。 - 递归调用没有终止前,调用栈会持续累积,最终超过浏览器预设的最大调用栈限制,抛出
RangeError: Maximum call stack size exceeded错误。 - 额外逻辑漏洞:
checkTimers的else分支没有返回值,就算递归能正常终止,你拿到的checkVariable也会是undefined,永远走不到提交逻辑。
可行修复方案
方案1:定时器轮询(改动最小)
把同步递归改成异步轮询,每次间隔一小段时间再检查数组长度,给定时器回调留出执行空间:
// 改造checkTimers,支持传入回调 function checkTimers(callback) { if (runningTimers.length < 1) { callback(true); return; } // 每隔100ms检查一次,间隔可以自行调整 setTimeout(() => checkTimers(callback), 100); } // 改造提交按钮绑定逻辑 $(document).on("click","#checkoutbtn",function(e) { e.preventDefault(); const $btn = $(this); // 先把按钮置为加载状态,避免重复点击 $btn.html("<i class='fa fa-spinner fa-spin'></i>").prop('disabled',true); checkTimers(() => { $("#cartform").submit(); }); });
方案2:Promise + async/await(代码更简洁易维护)
把等待逻辑封装成Promise,用异步语法处理:
function waitForTimers() { return new Promise(resolve => { const check = () => { if (runningTimers.length < 1) return resolve(); setTimeout(check, 100); } check(); }) } // 提交逻辑用async函数 $(document).on("click","#checkoutbtn", async function(e) { e.preventDefault(); const $btn = $(this); $btn.html("<i class='fa fa-spinner fa-spin'></i>").prop('disabled',true); await waitForTimers(); $("#cartform").submit(); });
方案3:事件触发机制(无轮询性能损耗)
不需要轮询,每次定时器执行完毕时检查是否需要提交,适合对性能要求更高的场景:
// 新增全局标志位,标记用户是否点击了提交 let needSubmit = false; // 修改定时器回调逻辑 timername[oid] = setTimeout(function() { const index = runningTimers.indexOf(oid); if (index > -1) { runningTimers.splice(index,1); console.log("removed"); } // 定时器执行完后检查是否需要提交,且所有定时器都已清空 if (needSubmit && runningTimers.length === 0) { $("#cartform").submit(); } //Ajax code here },500); // 提交按钮逻辑 $(document).on("click","#checkoutbtn",function(e) { e.preventDefault(); const $btn = $(this); $btn.html("<i class='fa fa-spinner fa-spin'></i>").prop('disabled',true); // 如果当前没有运行的定时器直接提交,否则设置标志位等定时器跑完自动提交 if (runningTimers.length === 0) { $("#cartform").submit(); } else { needSubmit = true; } });
内容的提问来源于stack exchange,提问作者Saad Bashir
相关产品推荐
相关产品推荐

