How do I dynamically add questions to Inquirer JS during execution, using rxjs?
Asked Answered
P

2

7

I want to ask questions to the user without having all the questions lined up immediately.

The documentation mentions rxjs but I feel there is a gap in the documentation on how to properly add questions while prompts are being executed, or at least it did not quite work for me.

https://www.npmjs.com/package/inquirer#reactive-interface

Internally, Inquirer uses the JS reactive extension to handle events and async flows.

This mean you can take advantage of this feature to provide more advanced flows. For example, you can dynamically add questions to be asked:

var prompts = new Rx.Subject();
inquirer.prompt(prompts);

// At some point in the future, push new questions
prompts.next({
  /* question... */
});
prompts.next({
  /* question... */
});

// When you're done
prompts.complete();

And using the return value process property, you can access more fine grained callbacks:

inquirer.prompt(prompts).ui.process.subscribe(onEachAnswer, onError, onComplete);
Pastiness answered 27/11, 2019 at 21:37 Comment(1)
P
6

So thanks to some inspiration from this blog post by jana e. beck, I made the code I needed. The examples and tips from jana was a little out of date, where using Subject from rxjs would no longer work for some reason, at least it did not work for me. This was easily fixed however by storing the emitter outside the Observer creation callback. Remember to add rxjs as a dependency to your project (same as the one InquirerJS is currently using probably helps).

const inquirer = require("inquirer");
var { Observable } = require("rxjs");

let emitter;

var prompts = Observable.create(function(e) {
  emitter = e;
  // need to start with at least one question here
  emitter.next({
    type: "list",
    name: "fruits",
    message: "What is your favorite fruit?",
    choices: [
      {
        name: "Banana"
      },
      {
        name: "Apple"
      },
      {
        name: "Pear"
      }
    ]
  });
});

let times = 0;

inquirer.prompt(prompts).ui.process.subscribe(
  q => {
    let dots = new Array(times).fill(".").join("");

    if (q.answer.toLowerCase() === "pear") {
      console.log("That's Great. I would never forget a Pear-eater.");
      emitter.complete();
    }

    emitter.next({
      type: "list",
      name: "fruits",
      message:
        "Sorry, what is your favorite fruit? I forgot, was it " +
        q.answer +
        ", or something else?",
      choices: [
        {
          name: "Uh, Banana.." + dots,
          value: "banana"
        },
        {
          name: "Uh, Apple.." + dots,
          value: "apple"
        },
        {
          name: "Pear!",
          value: "pear"
        }
      ]
    });

    times++;
  },
  error => {
    console.log("Hm, an error happened. Why?");
  },
  complete => {
    console.log("I think we are done now.");
  }
);

This prompt along with the blog post should be what you need to get started. Remember that you can queue up multiple questions in one go, should you want to.

When you are done, you put emitter.complete(); somewhere to end the prompt.

Pastiness answered 27/11, 2019 at 21:37 Comment(2)
In newer versions, askAnswered: true is required for new prompts of the same question (name)Corabelle
Newer versions of rxjs prefer a different signature for the subscribe function, documented here: rxjs.dev/deprecations/subscribe-argumentsWhittier
J
0

An alternative that takes less code is to chain the promises returned by inquirer, keeping track of the last one:

import * as inquirer from 'inquirer';

async function main() {
  const questions = [
    `What's your name?`,
    `What's your favorite food?`,
    `Where are you from?`,
  ];

  const answers = await Promise.all(
    questions.map((question) => enqueuePrompt(() => ask(question))),
  );

  console.log(answers);
}

async function ask(question: string) {
  const answers = await inquirer.prompt<{ response: string }>([
    {
      name: 'response',
      message: question,
    },
  ]);

  return answers.response;
}

let lastQuestion = Promise.resolve<unknown>(null);

async function enqueuePrompt<T>(promptFn: () => Promise<T>) {
  const answer = lastQuestion.then(promptFn);
  lastQuestion = answer;
  return answer;
}

main();
Judaica answered 4/4, 2023 at 12:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.