-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Description
When following the suggestions for handling ctrl+c gracefully, I've found that trying to await some asynchronous code after handling the ExitPromptError will result in an unresolved top-level await error.
Example:
repro.ts
import {ExitPromptError} from '@inquirer/core';
import {input} from '@inquirer/prompts';
async function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function main(): Promise<void> {
try {
const name = await input({message: 'What is your name?'});
console.log('Hello', name);
} catch (err) {
if (err instanceof ExitPromptError) {
console.log('I respect your privacy. Give me a second.');
await sleep(1000);
} else {
throw err;
}
}
console.log("Okay, let's begin.");
}
await main();
When answering the prompt, everything works as expected:
% node --disable-warning=ExperimentalWarning ./repro.ts
✔ What is your name? Foobar
Hello Foobar
Okay, let's begin.
However, when using ctrl+c in the prompt, I get an unsettled top-level await error:
% node --disable-warning=ExperimentalWarning ./repro.ts
? What is your name? Warning: Detected unsettled top-level await at file:///path/to/repro.ts:25
await main();
^
I respect your privacy. Give me a second.
I've tried adding a custom SIGINT listener with process.on('SIGINT', sigintHandler), removing all existing SIGINT listeners, and everything in between to no avail.
However, I did find that adding a handler for the readline interface's SIGINT event in createPrompt()of @inquirer/core seemed to "fix" the problem:
/* --------------- EXISTING CODE --------------- */
return withHooks(rl, (cycle) => {
// The close event triggers immediately when the user press ctrl+c. SignalExit on the other hand
// triggers after the process is done (which happens after timeouts are done triggering.)
// We triggers the hooks cleanup phase on rl `close` so active timeouts can be cleared.
const hooksCleanup = AsyncResource.bind(() => effectScheduler.clearAll());
rl.on('close', hooksCleanup);
cleanups.add(() => rl.removeListener('close', hooksCleanup));
/* --------------- NEW CODE --------------- */
const sigint = () => reject(new ExitPromptError(`User force closed the prompt with SIGINT`));
rl.on('SIGINT', sigint);
cleanups.add(() => rl.removeListener('SIGINT', sigint));
When pressing ctrl+c with the patched version:
% node --disable-warning=ExperimentalWarning ./repro.ts
? What is your name?
I respect your privacy. Give me a second.
Okay, let's begin.
However, I don't have enough context to know if this is the "correct" fix.
For context, I've tested this on Node 22 and 24 with @inquirer/prompts versions 7.2.1 and 7.5.0.
If any other information is needed, please let me know.