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

Node.js Express中实现支持定时+API触发的非并发异步任务

实现方案

利用Node.js单线程事件循环的特性,仅靠原生语法就能实现你要的效果,不需要引入任何第三方依赖、锁或者外部中间件:

import express from 'express';
const app = express();
const port = 3000;

// 辅助sleep函数
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));

// 你的业务task函数
async function task(reason) {
  console.log("do thing because %s...", reason);
  await sleep(1000);
  console.log("done");
}

// 全局状态(Node.js单线程下操作不存在竞态,不需要加锁)
let isTaskRunning = false;
const pendingTaskReasons = [];
let tickerTimerId = null;
const TASK_INTERVAL = 60 * 1000; // 定时执行间隔,测试可改为5000

// 核心执行逻辑:按顺序处理队列中所有待执行的task
async function processTaskQueue() {
  if (isTaskRunning) return;
  isTaskRunning = true;

  // 循环处理所有排队的任务
  while (pendingTaskReasons.length > 0) {
    const currentReason = pendingTaskReasons.shift();
    try {
      await task(currentReason);
    } catch (err) {
      // 这里自行处理task执行报错,避免阻塞整个队列
      console.error('task执行失败:', err);
    }
  }

  isTaskRunning = false;
  // 所有任务处理完成后,重新调度下一次定时任务
  rescheduleTicker();
}

// 通用调度入口:定时/API触发都调用这个方法
function addTaskToQueue(reason) {
  pendingTaskReasons.push(reason);
  // 用nextTick避免同步连续多次触发重复执行process逻辑
  process.nextTick(processTaskQueue);
}

// 定时任务调度逻辑:从上一次所有任务执行结束后开始算间隔
function rescheduleTicker() {
  if (tickerTimerId) clearTimeout(tickerTimerId);
  tickerTimerId = setTimeout(() => {
    addTaskToQueue('ticker');
  }, TASK_INTERVAL);
}

// API触发接口
app.get("/task", async (req, res) => {
  // 方案1:加入队列后直接返回
  addTaskToQueue('trigger');
  res.send("task已加入调度队列");

  // 方案2:需要等task执行完成再返回,就用下面的逻辑替换上面两行
  /*
  addTaskToQueue('trigger');
  const waitTaskFinish = () => {
    if (!isTaskRunning && pendingTaskReasons.length === 0) {
      res.send("ok");
    } else {
      setTimeout(waitTaskFinish, 100);
    }
  };
  waitTaskFinish();
  */
});

// 初始化启动第一次定时调度
rescheduleTicker();

app.listen(port, () => {
  console.log(`服务运行在端口${port}`);
});

方案特性

  1. 完全保证task不会并发执行,同一时间永远只有一个task实例在运行
  2. 定时触发、API触发的任务都会按触发顺序排队执行,不会丢失
  3. 定时间隔从上一轮所有任务执行完成后开始计算,不会因为task本身执行时间长导致堆积或者间隔不准
  4. 无任何第三方依赖,单进程内对象可以正常共享,符合你的要求

内容的提问来源于stack exchange,提问作者piec

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 13:48:02