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

TypeScript CLI程序控制台日志覆写异步与并发问题求助

解决TypeScript CLI进度条异步覆写混乱的问题

1. 用互斥锁或任务队列同步控制台操作

Node.js没有原生互斥锁,但可以通过第三方库或自定义队列保证同一时间只有一个操作修改stdout,避免异步调用导致的光标顺序混乱。

方案一:使用async-mutex库实现互斥

import { Mutex } from 'async-mutex';

// 全局互斥锁,控制所有stdout进度更新操作
const stdoutMutex = new Mutex();

async function updateProgress(emoji: string, message: string) {
  // 获取锁,等待前面的操作完成
  const releaseLock = await stdoutMutex.acquire();
  try {
    // 将光标移到行首,覆盖当前行内容
    process.stdout.write('\r');
    // 写入新的进度内容
    process.stdout.write(`${emoji} ${message}`);
  } finally {
    // 释放锁,允许后续操作执行
    releaseLock();
  }
}

方案二:自定义任务队列(无第三方依赖)

如果不想引入额外库,可以手动实现一个串行任务队列:

let isProcessingQueue = false;
const taskQueue: (() => Promise<void>)[] = [];

// 将控制台操作加入队列,串行执行
async function enqueueConsoleTask(task: () => Promise<void>) {
  taskQueue.push(task);
  if (!isProcessingQueue) {
    isProcessingQueue = true;
    while (taskQueue.length > 0) {
      await taskQueue.shift()!();
    }
    isProcessingQueue = false;
  }
}

// 封装进度更新函数
async function updateProgress(emoji: string, message: string) {
  await enqueueConsoleTask(async () => {
    process.stdout.write('\r');
    process.stdout.write(`${emoji} ${message}`);
  });
}

2. 处理多位置内容更新

如果需要在同一行更新多个动态元素(比如两个进度条),可以用cursorTo定位光标,配合clearLine清除残留内容:

async function updateMultiProgress(emoji1: string, p1: number, emoji2: string, p2: number) {
  const releaseLock = await stdoutMutex.acquire();
  try {
    // 移到行首
    process.stdout.cursorTo(0);
    // 写入第一个进度
    process.stdout.write(`${emoji1} Task 1: ${p1}%`);
    // 定位到指定列,开始写入第二个进度
    process.stdout.cursorTo(30);
    process.stdout.write(`${emoji2} Task 2: ${p2}%`);
    // 清除光标到行尾的内容,避免旧文本残留
    process.stdout.clearLine(1);
  } finally {
    releaseLock();
  }
}

3. 异步场景下的使用规范

所有进度更新操作必须通过上述同步函数调用,包括Promise回调、定时器等异步代码中,不能直接调用process.stdout.write:

// 示例:异步任务中更新进度
async function runLongTask() {
  for (let i = 0; i <= 100; i++) {
    const emoji = i < 33 ? '⏳' : i < 66 ? '🚀' : '✅';
    await updateProgress(emoji, `Processing: ${i}%`);
    await new Promise(resolve => setTimeout(resolve, 50));
  }
  // 任务结束后换行,避免命令行提示符和进度条重叠
  process.stdout.write('\n');
}

额外注意事项

  • 非进度类日志(比如错误信息)要输出到process.stderr,避免干扰stdout的进度覆写。
  • 确保所有涉及stdout的进度操作都经过同步层,遗漏的直接调用会再次导致顺序混乱。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 06:25:10