Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

I still get the pyramid of doom when using promises, what am I doing wrong?

I am using the Inquirer library with Node.js and I still get the pyramid of doom when using promises, what am I doing wrong?

Just FYI the inquirer library API is basically:

inquirer.prompt([
question1,
question2,
question3,
...
questionX
]).then(function(answers){});

where answers is a hash, with keys that represent each question. Nothing really out of the ordinary here.

Anyway, using the API, I always get getAnswersToPrompts().then(function(answers){}) and it seems more convenient to keep nesting the promises inside the previous one...like so:

function run (rootDir) {

  return watchHelper().then(function (answers) {

    return chooseDirs({

      allowDirs: answers.allow,
      originalRootDir: rootDir,
      onlyOneFile: false

    }).then(function (pathsToRun) {

      assert(pathsToRun.length > 0, ' You need to select at least one path.');

      return getOptions(availableOptionsForPlainNode).then(function (answers) {

        const selectedOpts = answers[ 'command-line-options' ];

        return localOrGlobal().then(function (answers) {

          const sumanExec = answers.localOrGlobal;

          console.log(' => ', colors.magenta.bold([ '$', sumanExec, '--watch', pathsToRun, selectedOpts ].join(' ')));


        });

      });

    });

  }).catch(rejectionHandler);

}

I could possibly do this instead:

function run(){

  return makePromise()
    .then(fn1(data1))
    .then(fn2(data2))
    .then(fn3(data3))

}

where fn1,fn2,fn3 look like:

function fnX(data){

   return function(answers){

      return promise(data);

   }
}

but this just makes things more complicated to understand AFAICT

Just be as clear as possible, I definitely need the result of the previous promise, but sometimes I need the result from the promise prior to that or even the result prior to that.

Nesting the functions allows the data I need to be in scope, thanks to closures etc.

like image 591
Alexander Mills Avatar asked Nov 13 '16 02:11

Alexander Mills


1 Answers

Return the next Promise before calling then:

function run (rootDir) {
  var pathsToRun;

  return watchHelper()
    .then(function (watchHelperAnswers) {
      return chooseDirs({
        allowDirs: watchHelperAnswers.allow,
        originalRootDir: rootDir,
        onlyOneFile: false
      });
    }).then(function (chooseDirsResult) {
      assert(chooseDirsResult.length > 0, ' You need to select at least one path.');
      pathsToRun = chooseDirsResult;
      return getOptions(availableOptionsForPlainNode);
    }).then(function (getOptionsAnswers) {
      const selectedOpts = getOptionsAnswers[ 'command-line-options' ];
      return localOrGlobal();
    }).then(function (localOrGlobalAnswers) {
      const sumanExec = localOrGlobalAnswers.localOrGlobal;
      console.log(' => ', colors.magenta.bold([ '$', sumanExec, '--watch', pathsToRun,
        selectedOpts ].join(' ')));
    }).catch(rejectionHandler);
}

but sometimes I need the result from the promise prior to that or even the result prior to that

The only instance of this in your example is pathsToRun. I think nesting functions two or three deep to accommodate this is still readable, but your other option is to define a variable outside the promise chain, which I have shown above for pathsToRun.

Lastly, your example uses three different variables all called answers throughout the promise chain, which might be adding to the confusion. In general I think it is fine to use the same name for promise callback results, but I have renamed them here for clarity in this answer.

like image 66
Joe Daley Avatar answered Sep 25 '22 10:09

Joe Daley