如何使用 Promises 在循环中调用 inquirer.js 菜单?
How do I invoke inquirer.js menu in a loop using Promises?
我写了一个简单的 Node.js 程序,带有一个漂亮的菜单系统,由 inquirer.js 提供便利。然而,在 select 在菜单中选择一个选项并完成一些操作后,程序退出。我需要再次显示菜单,直到我 select 退出菜单中的 [last] 选项。我想使用 Promise 来做到这一点, 而不是 async/await 的 。
我尝试使用一个函数来显示菜单并在一个永久循环中调用该函数(例如 while (true) { ... }
),但这使程序无法使用。我将其更改为 for 循环只是为了观察问题。下面是简单的程序和结果输出。
PROGRAM
"use strict";
const inquirer = require('inquirer');
const util = require('util')
// Clear the screen
process.stdout.write("\u001b[2J\u001b[0;0H");
const showMenu = () => {
const questions = [
{
type: "list",
name: "action",
message: "What do you want to do?",
choices: [
{ name: "action 1", value: "Action1" },
{ name: "action 2", value: "Action2" },
{ name: "Exit program", value: "quit"}
]
}
];
return inquirer.prompt(questions);
};
const main = () => {
for (let count = 0; count < 3; count++) {
showMenu()
.then(answers => {
if (answers.action === 'Action1') {
return Promise.resolve('hello world');
}
else if (answers.action === 'Action2') {
return new Promise((resolve, reject) => {
inquirer
.prompt([
{
type: 'input',
name: 'secretCode',
message: "Enter a secret code:"
}
])
.then(answers => {
resolve(answers);
})
});
}
else {
console.log('Exiting program.')
process.exit(0);
}
})
.then((data) => { console.log(util.inspect(data, { showHidden: false, depth: null })); })
.catch((error, response) => {
console.error('Error:', error);
});
}
}
main()
OUTPUT
? What do you want to do? (Use arrow keys)
❯ action 1
action 2
Exit program ? What do you want to do? (Use arrow keys)
❯ action 1
action 2
Exit program ? What do you want to do? (Use arrow keys)
❯ action 1
action 2
Exit program (node:983) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 keypress listeners added to [ReadStream]. Use emitter.setMaxListeners() to increase limit
如何在第一次调用生成菜单后阻塞,等待选项被 selected 和相应的操作完成,然后循环回到显示菜单的下一次迭代?
您可以使用 async/await
语法:
声明您的 main
函数 async
,并 await
查询者返回的 Promise:
const main = async () => {
for (let count = 0; count < 3; count++) {
await showMenu()
.then(answers => {
[...]
}
};
您的代码没有按预期工作,因为简而言之,解释器在 运行 任何回调(来自承诺)之前执行同步代码。因此,您的同步 for
循环会在任何 I/O 回调被解析之前执行。所有对 showMenu()
returns 承诺的调用都是异步解决的,这意味着在循环之前不会打印任何内容,也不会解释任何输入。
编写 await
会阻止 async
函数内的后续同步代码,这似乎是您想要做的。
以您的代码为起点,我整合了自己的库来显示 cli 菜单。它去除了 Inquirer 的许多样板,让您可以简洁地声明菜单 graph/tree。
main.ts 文件显示了您如何使用它。您声明了一个 MenuPrompts 字典,您可以向其中添加 Menus、Actions 和 LoopActions。每个提示都有一个键,其他提示可以路由到该键。
// main.ts
import { Menu, Action, MenuPrompt, openMenuPrompt, LoopAction } from "./menus";
// Set of prompts
let prompts = {
menu_1: new MenuPrompt("Menu 1 - This list is ordinal - What would like to do?", 20, true, [
new Menu("Menu 2", "menu_2"),
new LoopAction("Action", () => console.log("Menu 1 action executed")),
new Action("Back", context => context.last),
new Action("Exit", () => process.exit(0)),
]),
menu_2: new MenuPrompt("Menu 2 - This list is NOT ordinal - What would like to do?", 20, false, [
new Menu("Menu 1", "menu_1"),
new LoopAction("Action", () => console.log("Menu 2 action executed")),
new Action("Back", context => context.last),
new Action("Exit", () => process.exit(0)),
]),
};
// Open the "menu_1" prompt
openMenuPrompt("menu_1", prompts);
这是 lib 文件,其中包含用于打开初始提示的类型和函数。
// menus.ts
import * as inquirer from "inquirer";
// MAIN FUNCTION
export let openMenuPrompt = async (current: string, prompts: Dict<MenuPrompt>, last?: string): Promise<any> => {
let answer: Answer = (await inquirer.prompt([prompts[current]])).value;
let next = answer.execute({current, last});
if (!next) return;
return await openMenuPrompt(next, prompts, current == next? last : current );
};
// PUBLIC TYPES
export class MenuPrompt {
type = "list";
name = "value";
message: string;
pageSize: number;
choices: Choice[];
constructor(message: string, pageSize: number, isOrdinalList: boolean, choices: Choice[]) {
this.message = message;
this.pageSize = pageSize;
this.choices = choices;
if (isOrdinalList) {
this.choices.forEach((choice, i) => choice.name = `${i + 1}: ${choice.name}`)
}
}
}
export interface Choice {
name: string;
value: Answer;
}
export class Action implements Choice {
name: string;
value: Answer;
constructor(name: string, execute: (context?: MenuContext) => any) {
this.name = name;
this.value = {execute};
}
}
export class LoopAction implements Choice {
name: string;
value: Answer;
constructor(name: string, execute: (context?: MenuContext) => any) {
this.name = name;
this.value = {execute: context => execute(context) ?? context.current};
}
}
export class Menu implements Choice {
name: string;
value: Answer;
constructor(name: string, menuKey: string) {
this.name = name;
this.value = {execute: () => menuKey};
}
}
// INTERNAL TYPES
type Dict<T = any> = {[key: string]: T};
interface Answer {
execute: (context: MenuContext) => any;
}
interface MenuContext {
current: string;
last: string;
}
我写了一个简单的 Node.js 程序,带有一个漂亮的菜单系统,由 inquirer.js 提供便利。然而,在 select 在菜单中选择一个选项并完成一些操作后,程序退出。我需要再次显示菜单,直到我 select 退出菜单中的 [last] 选项。我想使用 Promise 来做到这一点, 而不是 async/await 的 。
我尝试使用一个函数来显示菜单并在一个永久循环中调用该函数(例如 while (true) { ... }
),但这使程序无法使用。我将其更改为 for 循环只是为了观察问题。下面是简单的程序和结果输出。
PROGRAM
"use strict";
const inquirer = require('inquirer');
const util = require('util')
// Clear the screen
process.stdout.write("\u001b[2J\u001b[0;0H");
const showMenu = () => {
const questions = [
{
type: "list",
name: "action",
message: "What do you want to do?",
choices: [
{ name: "action 1", value: "Action1" },
{ name: "action 2", value: "Action2" },
{ name: "Exit program", value: "quit"}
]
}
];
return inquirer.prompt(questions);
};
const main = () => {
for (let count = 0; count < 3; count++) {
showMenu()
.then(answers => {
if (answers.action === 'Action1') {
return Promise.resolve('hello world');
}
else if (answers.action === 'Action2') {
return new Promise((resolve, reject) => {
inquirer
.prompt([
{
type: 'input',
name: 'secretCode',
message: "Enter a secret code:"
}
])
.then(answers => {
resolve(answers);
})
});
}
else {
console.log('Exiting program.')
process.exit(0);
}
})
.then((data) => { console.log(util.inspect(data, { showHidden: false, depth: null })); })
.catch((error, response) => {
console.error('Error:', error);
});
}
}
main()
OUTPUT
? What do you want to do? (Use arrow keys)
❯ action 1
action 2
Exit program ? What do you want to do? (Use arrow keys)
❯ action 1
action 2
Exit program ? What do you want to do? (Use arrow keys)
❯ action 1
action 2
Exit program (node:983) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 keypress listeners added to [ReadStream]. Use emitter.setMaxListeners() to increase limit
如何在第一次调用生成菜单后阻塞,等待选项被 selected 和相应的操作完成,然后循环回到显示菜单的下一次迭代?
您可以使用 async/await
语法:
声明您的 main
函数 async
,并 await
查询者返回的 Promise:
const main = async () => {
for (let count = 0; count < 3; count++) {
await showMenu()
.then(answers => {
[...]
}
};
您的代码没有按预期工作,因为简而言之,解释器在 运行 任何回调(来自承诺)之前执行同步代码。因此,您的同步 for
循环会在任何 I/O 回调被解析之前执行。所有对 showMenu()
returns 承诺的调用都是异步解决的,这意味着在循环之前不会打印任何内容,也不会解释任何输入。
编写 await
会阻止 async
函数内的后续同步代码,这似乎是您想要做的。
以您的代码为起点,我整合了自己的库来显示 cli 菜单。它去除了 Inquirer 的许多样板,让您可以简洁地声明菜单 graph/tree。
main.ts 文件显示了您如何使用它。您声明了一个 MenuPrompts 字典,您可以向其中添加 Menus、Actions 和 LoopActions。每个提示都有一个键,其他提示可以路由到该键。
// main.ts
import { Menu, Action, MenuPrompt, openMenuPrompt, LoopAction } from "./menus";
// Set of prompts
let prompts = {
menu_1: new MenuPrompt("Menu 1 - This list is ordinal - What would like to do?", 20, true, [
new Menu("Menu 2", "menu_2"),
new LoopAction("Action", () => console.log("Menu 1 action executed")),
new Action("Back", context => context.last),
new Action("Exit", () => process.exit(0)),
]),
menu_2: new MenuPrompt("Menu 2 - This list is NOT ordinal - What would like to do?", 20, false, [
new Menu("Menu 1", "menu_1"),
new LoopAction("Action", () => console.log("Menu 2 action executed")),
new Action("Back", context => context.last),
new Action("Exit", () => process.exit(0)),
]),
};
// Open the "menu_1" prompt
openMenuPrompt("menu_1", prompts);
这是 lib 文件,其中包含用于打开初始提示的类型和函数。
// menus.ts
import * as inquirer from "inquirer";
// MAIN FUNCTION
export let openMenuPrompt = async (current: string, prompts: Dict<MenuPrompt>, last?: string): Promise<any> => {
let answer: Answer = (await inquirer.prompt([prompts[current]])).value;
let next = answer.execute({current, last});
if (!next) return;
return await openMenuPrompt(next, prompts, current == next? last : current );
};
// PUBLIC TYPES
export class MenuPrompt {
type = "list";
name = "value";
message: string;
pageSize: number;
choices: Choice[];
constructor(message: string, pageSize: number, isOrdinalList: boolean, choices: Choice[]) {
this.message = message;
this.pageSize = pageSize;
this.choices = choices;
if (isOrdinalList) {
this.choices.forEach((choice, i) => choice.name = `${i + 1}: ${choice.name}`)
}
}
}
export interface Choice {
name: string;
value: Answer;
}
export class Action implements Choice {
name: string;
value: Answer;
constructor(name: string, execute: (context?: MenuContext) => any) {
this.name = name;
this.value = {execute};
}
}
export class LoopAction implements Choice {
name: string;
value: Answer;
constructor(name: string, execute: (context?: MenuContext) => any) {
this.name = name;
this.value = {execute: context => execute(context) ?? context.current};
}
}
export class Menu implements Choice {
name: string;
value: Answer;
constructor(name: string, menuKey: string) {
this.name = name;
this.value = {execute: () => menuKey};
}
}
// INTERNAL TYPES
type Dict<T = any> = {[key: string]: T};
interface Answer {
execute: (context: MenuContext) => any;
}
interface MenuContext {
current: string;
last: string;
}