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

Node.js中如何管理独立任务队列且仅允许同时运行一个任务

解决方案

你的问题核心在于:把**标题发放逻辑(fetchTitleForUser)和持有期等待(promptForCancel)**打包进了同一个主队列任务里,导致主队列被60秒的持有期完全阻塞,不同标题的请求也无法插队。

要实现预期逻辑,需要拆分这两个步骤:

  • 主队列仅处理必须全局串行的fetchTitleForUser,处理完立即释放主队列,让其他标题的请求可以使用。
  • 每个标题的独立队列负责管控自身的持有期,同标题的下一个请求必须等当前用户的持有期结束后,才能向主队列发起新的发放请求。

修正后的代码如下:

import PQueue from "p-queue";
import { AbortError } from "p-queue";

// 主队列:仅负责全局串行处理标题发放逻辑
const mainQueue = new PQueue({ concurrency: 1 });

enum Title {
  A = "a",
  B = "b"
}

// 每个标题的独立队列:管控同标题的请求排队(等待上一个用户的持有期结束)
const titleQueues = {
  [Title.A]: new PQueue({ concurrency: 1 }),
  [Title.B]: new PQueue({ concurrency: 1 }),
};

// 模拟标题发放逻辑(必须全局串行)
async function fetchTitleForUser(title: Title): Promise<void> {
  console.log(`开始发放标题 ${title}`);
  // 模拟耗时操作
  await new Promise(resolve => setTimeout(resolve, 1000));
  console.log(`完成标题 ${title} 发放`);
}

// 模拟持有期等待(用户可手动取消或超时自动结束)
async function promptForCancel(timeoutMs: number): Promise<void> {
  return new Promise((resolve, reject) => {
    const timeoutId = setTimeout(() => {
      console.log("持有期超时结束");
      reject(new Error("Timeout"));
    }, timeoutMs);

    // 这里可替换为实际的用户取消逻辑
    // 比如监听用户的取消按钮事件:button.addEventListener('click', () => { clearTimeout(timeoutId); resolve(); });
  });
}

async function requestTitle(title: Title) {
  try {
    // 先进入对应标题的队列,等待上一个用户的持有期结束
    await titleQueues[title].add(async () => {
      // 向主队列申请处理发放逻辑
      await mainQueue.add(() => fetchTitleForUser(title));

      // 发放完成后,进入持有期等待
      try {
        await promptForCancel(60 * 1000);
        console.log("用户手动放弃标题");
      } catch {
        // 超时或其他错误,持有期结束
      }
    });
  } catch (error) {
    if (!(error instanceof AbortError)) {
      throw error;
    }
  }
}

// 测试场景:
// User1 请求A → 进入A队列,主队列处理发放,然后进入持有期
// User2 请求A → 进入A队列,等待User1的持有期结束
// User3 请求B → 进入B队列,主队列此时已释放,立即处理B的发放,然后进入持有期
// 执行顺序:User1发放 → User3发放 → User1持有期结束 → User2发放
requestTitle(Title.A);
setTimeout(() => requestTitle(Title.A), 500);
setTimeout(() => requestTitle(Title.B), 500);

关键改动说明:

  • 拆分任务边界:主队列只处理fetchTitleForUser,这个步骤完成后主队列就会处理下一个等待的任务(不管是哪个标题)。
  • 标题队列管控持有期:同标题的请求必须等待上一个用户的持有期结束,才能发起新的发放请求;不同标题的请求不受其他标题持有期的影响。
  • 移除全局AbortController:避免多个任务共享控制器导致的冲突,每个任务的生命周期独立管控。

这样就能实现你预期的逻辑:不同标题的用户无需等待其他标题的60秒持有期,仅需等待主队列的发放处理完成即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 15:15:41