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

Node.js模块化加载动画异步函数异常及行覆盖需求

问题修复方案

1. 动画提前结束的问题

你的something函数中调用sleep时未使用await,导致函数会同步执行所有赋值操作,瞬间跑完所有步骤,spinner里的await func()会立即得到结果,直接进入finally块打印"Done!",完全没给动画运行的时间。

修复方法很简单,给每个sleep调用加上await:

async function something() {
  loading.totalSteps = 3;
  loading.statusMessage = "doing something";
  await sleep(2000); // 此处及下方sleep都加await
  loading.currentStep = 1;
  loading.statusMessage = "working harder";
  await sleep(2000);
  loading.currentStep = 2;
  loading.statusMessage = "almost done";
  await sleep(2000);
  loading.currentStep = 3;
}

2. 控制台整行覆盖的问题

用ANSI转义序列\x1b[K可以清除从当前光标位置到行尾的所有内容,无需依赖大量空格覆盖残留文本。只需在每次process.stdout.write的末尾加上该序列即可。

修改后的完整代码

loading.js

module.exports = {
    currentStep : 0,
    totalSteps : 0,
    statusMessage : '',

    spinner : async function (func) {
        const spinnerAnimation = ['[⠋]', '[⠙]', '[⠹]', '[⠸]', '[⠼]', '[⠴]', '[⠦]', '[⠧]', '[⠇]', '[⠏]'];
        const dots = ['   ', '   ', '   ', '.', '.', '.', '.. ', '.. ', '.. ', '...', '...', '...'];
        let spinnerCounter = 0;
        let dotCounter = 0;

        const intervalId = setInterval(() => {
            spinnerCounter = (spinnerCounter + 1) % spinnerAnimation.length;
            dotCounter = (dotCounter + 1) % dots.length;
            if (this.statusMessage === '') this.statusMessage = 'Loading';
            
            let output = '';
            if (this.currentStep <= this.totalSteps && this.currentStep >= 0 && this.totalSteps >= 1) {
                const percentage = Math.round(this.currentStep / this.totalSteps * 10000)/100;
                output = `\r\x1b[32m\x1b[?25l${spinnerAnimation[spinnerCounter]} ${percentage}% (Step: ${this.currentStep}/${this.totalSteps} complete)\x1b[0m - ${this.statusMessage}${dots[dotCounter]}`;
            } else {
                output = `\r\x1b[32m\x1b[?25l${spinnerAnimation[spinnerCounter]}\x1b[0m - ${this.statusMessage}${dots[dotCounter]}`;
            }
            // 用\x1b[K清除行尾残留内容
            process.stdout.write(`${output}\x1b[K`);
        }, 100);
    
        let error = null;
        try {
            await func();
        } catch (err) {
            error = err;
        } finally {
            clearInterval(intervalId);
            if (this.statusMessage === '') this.statusMessage = 'Loading';
            let finalOutput = '';
            
            if (error) {
                if (this.currentStep <= this.totalSteps && this.currentStep >= 0 && this.totalSteps >= 1) {
                    const percentage = Math.round(this.currentStep / this.totalSteps * 10000)/100;
                    finalOutput = `\r\x1b[31m[✗] ${percentage}% (Step: ${this.currentStep}/${this.totalSteps})\x1b[0m - ${this.statusMessage}... >> FAILED!\x1b[?25h\n`;
                } else {
                    finalOutput = `\r\x1b[31m[✗]\x1b[0m - ${this.statusMessage}... >> FAILED!\x1b[?25h\n`;
                }
                process.stdout.write(`${finalOutput}\x1b[K`);
                console.error(`\x1b[31m └-${error}\x1b[0m`);
            } else {
                if (this.currentStep <= this.totalSteps && this.currentStep >= 0 && this.totalSteps >= 1) {
                    const percentage = Math.round(this.currentStep / this.totalSteps * 10000)/100;
                    finalOutput = `\r\x1b[32m[✓] ${percentage}% (Step: ${this.currentStep}/${this.totalSteps})\x1b[0m - ${this.statusMessage}... >> Done!\x1b[?25h\n`;
                } else {
                    finalOutput = `\r\x1b[32m[✓]\x1b[0m - ${this.statusMessage}... >> Done!\x1b[?25h\n`;
                }
                process.stdout.write(`${finalOutput}\x1b[K`);
            }
            
            // 重置状态
            this.currentStep = 0;
            this.totalSteps = 0;
            this.statusMessage = '';
            return error;
        }
    }
}

index.js

const loading = require('./loading.js');

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function something() {
  loading.totalSteps = 3;
  loading.statusMessage = "doing something";
  await sleep(2000);
  loading.currentStep = 1;
  loading.statusMessage = "working harder";
  await sleep(2000);
  loading.currentStep = 2;
  loading.statusMessage = "almost done";
  await sleep(2000);
  loading.currentStep = 3;
}

async function main() {
  await loading.spinner(something);
}

main();

额外调整说明

  • 将错误状态图标从[✓]改为[✗],更符合失败语义
  • 提取百分比计算逻辑,避免重复代码
  • 使用===严格相等判断,代码更规范

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 19:35:26