You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现队列式倒计时?解决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>

另外还有个问题:这段代码在浏览器里能跑,但代码片段没法运行,不知道咋回事。


解决方案

核心问题分析

  1. 你的q()函数里用while(qNums)循环直接执行所有队列任务,完全没做等待逻辑,导致任务一次性全部启动,出现并发问题。
  2. countDown()里重复获取输入框的值,传入的参数完全没用,而且倒计时结束后没有触发队列的下一个任务。
  3. 原代码把<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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 13:55:28