Grofit
Grofit

Reputation: 18435

A bit confused with q and promises in nodejs

I currently have a few js files in nodejs which are loaded as module and augment the app object (using express).

So their signatures look like:

module.exports = function(app, callback) { 
   // ... 
   callback();
}

So currently as I have about 5 of them my code would look like:

require("./setup/a")(app, function() {
    require("./setup/b")(app, function(){
        require("./setup/c")(app, function(){
            require("./setup/d")(app, function(){
                require("./setup/e")(app, function(){
                    startApp();
                })
            })
        })
    })
});

Now that looks unsightly as its the "pyramid of doom", however I am not entirely sure how I need to change this pattern to use Q, as I was assuming I would use Q.fcall(...a).then(...b).etc.done(). However I am unsure how I pass the app into it and if I need to return the callback for it to process as a promise.

Ideally I do not want to start whacking Q all through my code I only want it in the places where I want to remove the pyramid use cases, so in the above example how do I use Q with promises to pass the app into each required module and then start the app at the end?

Upvotes: 1

Views: 171

Answers (2)

redben
redben

Reputation: 5686

Promises are not the silver bullet to callback pyramid of doom. I have seen code where even with promises it looked like a pyramid of doom.

You could get rid of the pyramid and stay with the callback style by doing something like this :

  // var app;
  // ...etc
  var paths = ['a','b','c','d'];
  setupNext();

  function setupNext() {
    var p = paths.pop(); // or shift
    var next = paths.length > 0 ? setupNext : startApp
    require(p)(app, next);
  }

  function startApp() {}

Upvotes: 0

Benjamin Gruenbaum
Benjamin Gruenbaum

Reputation: 276276

Assuming your modules don't already use promises you can do something like this:

module.exports = function(app) { 
    // do some stuff with app
    return new Promise(function(resolve,reject){
        // when ready to resolve after some actions on app
        resolve(); // you can also return a value here as a cb param
    });
};

Promise.all(["./setup/a","./setup/b","./setup/c"].map(require.bind(null,app)))
  .then(startApp);

You should however use promises at the lowest level possible, which would mean that you can simply return the promise which you used in the process:

module.exports = function(app){
     return something(app).then(function(){
         return somethingElseAsyncWithApp(app);
     });
};

So the promise constructor isn't required. Note that this answer uses native promises but will also work on libraries that use that syntax like Bluebird. For Q change new Promise to new Q.Promise and Promise.all to Q.all.

Alternatively, you can change every require(x) to Q.fcall(require,x) and use Q.all on that directly, but that is both slow (Although Q is slow anyway) and more error prone than promisifying the modules directly. It is best to promisify the lowest level API possible.

Upvotes: 2

Related Questions