Skip to content

Instantly share code, notes, and snippets.

@invakid404
Created March 29, 2023 11:18
Show Gist options
  • Save invakid404/60b69ff654d0173a07ac3bf86609c79b to your computer and use it in GitHub Desktop.
Save invakid404/60b69ff654d0173a07ac3bf86609c79b to your computer and use it in GitHub Desktop.
Mulitiple spinners example implementation
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