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
相关产品推荐
相关产品推荐

