如何实现队列式倒计时?解决setInterval/setTimeout并发执行问题
问题:实现倒计时队列,当前任务结束后自动执行下一个
我需要做一个倒计时队列功能:用户在当前倒计时运行时输入新数字,这个新任务要加入队列,等当前倒计时跑完再自动启动。但现在用setInterval和setTimeout写的代码里,两个定时器会同时跑,根本没排队。
下面是我的代码,q()是我尝试写队列但失败的函数,countDown()是单独的倒计时逻辑:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <script> var isRunning = false; var qNums = []; var wrapFunction = function (fn, context, params) { return function () { fn.apply(context, params); }; }; function q() { var sec = document.getElementById("data").value; if (!Number.isInteger(parseInt(sec))) { document.getElementById("timer").innerHTML = "Not a number!"; return; } else if (parseInt(sec) < 0) { document.getElementById("timer").innerHTML = "Invalid timer setting!"; return; } qNums.push(wrapFunction(countDown, this, [sec])); while (qNums) { qNums.shift()(); } } function countDown(sec) { var sec = document.getElementById("data").value; var ms = 100; isRunning = true; document.getElementById("timer").innerHTML = ""; document.getElementById("btn").innerHTML = "Ticking!"; var interval = setInterval(function () { if (ms == 100) { document.getElementById("timer").innerHTML = sec + ".00"; } else { document.getElementById("timer").innerHTML = sec + "." + ms; } ms -= 10; if (ms < 0) { sec--; ms = 100; } if (sec < 0) { document.getElementById("data").value = ""; document.getElementById("btn").innerHTML = "Start"; document.getElementById("timer").innerHTML = "Countdown complete"; isRunning = false; clearInterval(interval); } }, 100); } </script> <body> <h1>Timer</h1> <label>Timer Duration: </label><input id="data" /> <button id="btn" onclick="countDown()">Start</button> <p id="timer"></p> </body> </html>
另外还有个问题:这段代码在浏览器里能跑,但代码片段没法运行,不知道咋回事。
解决方案
核心问题分析
- 你的
q()函数里用while(qNums)循环直接执行所有队列任务,完全没做等待逻辑,导致任务一次性全部启动,出现并发问题。 countDown()里重复获取输入框的值,传入的参数完全没用,而且倒计时结束后没有触发队列的下一个任务。- 原代码把
<script>放在<head>里,此时DOM还没加载完成,代码片段环境下可能获取不到元素导致运行失败。
修复后的完整代码
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>队列倒计时器</title> </head> <body> <h1>Timer</h1> <label>倒计时时长:</label><input id="data" type="number" /> <button id="btn" onclick="addToQueue()">Start</button> <p id="timer"></p> <script> let isRunning = false; const taskQueue = []; // 将任务加入队列 function addToQueue() { const inputVal = document.getElementById("data").value.trim(); const sec = parseInt(inputVal); // 输入验证 if (isNaN(sec) || !Number.isInteger(sec)) { document.getElementById("timer").textContent = "请输入有效整数!"; return; } if (sec < 0) { document.getElementById("timer").textContent = "倒计时不能为负数!"; return; } // 加入队列并清空输入框 taskQueue.push(sec); document.getElementById("data").value = ""; // 当前无任务运行时,启动队列处理 if (!isRunning) { processQueue(); } } // 处理队列中的任务 function processQueue() { // 队列空了就重置状态 if (taskQueue.length === 0) { isRunning = false; document.getElementById("btn").textContent = "Start"; return; } isRunning = true; document.getElementById("btn").textContent = "Ticking!"; // 取出队列第一个任务 const targetSec = taskQueue.shift(); startCountdown(targetSec); } // 执行具体倒计时逻辑 function startCountdown(totalSec) { let sec = totalSec; let ms = 100; // 初始化显示 document.getElementById("timer").textContent = `${sec}.00`; const interval = setInterval(() => { ms -= 10; // 更新显示内容 if (ms >= 0) { document.getElementById("timer").textContent = `${sec}.${ms.toString().padStart(2, '0')}`; } else { sec--; ms = 100; if (sec >= 0) { document.getElementById("timer").textContent = `${sec}.00`; } } // 倒计时结束,清理定时器并触发下一个任务 if (sec < 0) { clearInterval(interval); document.getElementById("timer").textContent = "倒计时完成"; // 用setTimeout确保当前任务完全结束后再执行下一个 setTimeout(processQueue, 0); } }, 100); } </script> </body> </html>
关键改进点
- 拆分逻辑:
addToQueue()负责输入验证和入队,processQueue()负责任务调度,startCountdown()专注倒计时实现,职责更清晰。 - 去掉冗余的
wrapFunction,直接把倒计时时长存入队列,简洁高效。 - 倒计时结束后用
setTimeout(processQueue, 0)触发下一个任务,保证当前任务的清理工作完成后再启动新任务。 - 修正
countDown()中重复获取输入值的问题,直接使用传入的参数,避免数据混乱。 - 将
<script>移到<body>末尾,确保DOM加载完成后再执行JS,解决代码片段无法运行的问题。 - 用
textContent替代innerHTML,更安全且性能更好。
内容的提问来源于stack exchange,提问作者zzzz
相关产品推荐
相关产品推荐

