Created
March 29, 2023 11:18
-
-
Save invakid404/60b69ff654d0173a07ac3bf86609c79b to your computer and use it in GitHub Desktop.
Mulitiple spinners example implementation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export enum SpinnerStatus { | |
NotStarted, | |
Running, | |
Done, | |
} | |
export type Spinner = { | |
text: string; | |
status: SpinnerStatus; | |
runner: () => Promise<void>; | |
}; | |
let spinners: Spinner[] = []; | |
let i = 0; | |
let updateInterval: NodeJS.Timer | null = null; | |
const spinner = (text: string, runner: () => Promise<void>) => { | |
if (updateInterval == null) { | |
updateInterval = setInterval(() => { | |
updateSpinners(); | |
}, 100); | |
} | |
const currentSpinner: Spinner = { | |
text, | |
status: SpinnerStatus.NotStarted, | |
runner, | |
}; | |
spinners.push(currentSpinner); | |
}; | |
const updateSpinners = () => { | |
// If there are no active spinners, do nothing. | |
if (spinners.length === 0) { | |
// Clear interval if needed. | |
if (updateInterval != null) { | |
clearInterval(updateInterval); | |
updateInterval = null; | |
} | |
return; | |
} | |
const prefix = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"[i]; | |
i = (i + 1) % 10; | |
// Get all currently active spinners. | |
const runningSpinners = spinners.filter( | |
(spinner) => spinner.status !== SpinnerStatus.Done | |
); | |
let output = ""; | |
// Print all currently running spinners. | |
runningSpinners.forEach((spinner) => { | |
output += `${prefix} ${spinner.text}\n`; | |
// If spinner hasn't been started, call its runner on the next tick. | |
if (spinner.status === SpinnerStatus.NotStarted) { | |
process.nextTick(() => { | |
spinner.runner().then(() => { | |
// Mark spinner as done when runner resolves. | |
spinner.status = SpinnerStatus.Done; | |
}); | |
}); | |
// Mark spinner as running. | |
spinner.status = SpinnerStatus.Running; | |
} | |
}); | |
// Clear the lines that were previously used by spinners that are now done. | |
// Since we've already written `runningSpinners.length` lines, we need to | |
// subtract that from the total amount of spinners. | |
// After this, our cursor will be at the last line that was previously used. | |
output += "\u001b[2K\r\n".repeat(spinners.length - runningSpinners.length); | |
// Move the cursor before the first spinner. | |
output += `\u001b[${spinners.length}A`; | |
process.stdout.write(output); | |
// Remove done spinners | |
spinners = runningSpinners; | |
}; | |
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); | |
spinner("hello 1", async () => { | |
await sleep(2000); | |
console.log("hello 1 completed"); | |
spinner("hello 4", async () => { | |
await sleep(5000); | |
console.log("hello 4 completed"); | |
}); | |
spinner("hello 5", async () => { | |
await sleep(2000); | |
console.log("hello 5 completed"); | |
}); | |
}); | |
spinner("hello 2", async () => { | |
await sleep(5000); | |
console.log("hello 2 completed"); | |
}); | |
spinner("hello 3", async () => { | |
await sleep(10000); | |
console.log("hello 3 completed"); | |
// Exit to kill the dummy output loop. | |
// Without it, the program terminates just fine without this. | |
process.exit(0); | |
}); | |
// Start a loop that prints dummy output while the spinners are running. | |
(async () => { | |
for (let i = 0; ; i++) { | |
await sleep(1000); | |
console.log("dummy output", i); | |
} | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment