Search code examples
javascriptnode.jsexpressbluebird

How can you modify express.res within bluebird promises, and use res within another promise?


I might be overthinking this, but bear with me... If I have the following code

app.get('/api/endpoint', function(req, res, next) {
    new Promise(function() {
        // doing something that takes lots of code
        return someJson
    })
    .then(function(someJson) {
        // analyze someJson with lots of code
    })
    .then(function() {
        // do more
    })
    // chain a bunch more promises here
    .then(function() {
        res.status(200).send(message)
    })
    .catch(function(err) {
        // error handling
    })
    .finally(function() {
        // clean up
    })
})

If the promise chain gets very long, it can be a pain to navigate the endpoint. So, I want each step of the promise to be it's own function (to simplify the above code). So I can rewrite the top 2 promises as so:

function findSomeJson() {
    return new Promise(function() {
        // doing something that takes lots of code
        return someJson
    })
}

function analyzeSomeJson(someJson) {
    return new Promise(function(someJson) {
        // analyze someJson with lots of code
    })
}

Now, each of these functions can be used in the original example like so:

findSomeJson()
.then(function(someJson) {
    return analyzeSomeJson(someJson)
})
// etc...

But, what happens if I need to tweak res within those promises? Do I need to return res every time and store someJson within res? And, what happens if I have to use next()? How do I ensure res is modified at the end of my promise chain? I can't do that in finally(), do I have to do it in my last promise?


Solution

  • If you really want to be able to mutate res from within your functions you have to pass it around. I think the easiest/cleanest way to pass it around is to use bind like this:

    findSomeJson()
      .then(analyzeSomeJson.bind(this, res))
      .then(doMore.bind(this, res))
      .then(andEvenMore.bind(this, res))...
    

    Then the analyzeSomeJson definition would look like this:

    // .bind makes it so that res is the first argument when it is called
    function analyzeSomeJson(res, someJson) {
      return new Promise(function(someJson) {
        // analyze someJson with lots of code
      })
    }
    

    But I would probably try to avoid passing around res so that only your controller has to know about req and res. You could move all your functions to a service or services and have the controller determine what needs to happen to res based on what the service(s) return. Hopefully that will lead to maintainable/testable code.

    UPDATE very simple service example

    // endpoint.js
    var jsonService = require('./jsonService.js');
    
    app.get('/api/endpoint', function(req, res, next) {
      jsonService.getJson()
      // chain a bunch more promises here
      .then(function(json) {
        if(json === null) {
          return res.status(404).send('Not Found');
        }
        res.status(200).send(json);
      })
      .catch(function(err) {
          // error handling
      });
    });
    

    There are many ways you could implement your service but really its just another .js file that you require in to your "controller" or endpoint.js. Whatever you export will be your "public" methods.

    // jsonService.js
    
    // "public" methods
    var service = {
      getJson: function() {
        return findSomeJson().then(analyzeSomeJson);
      }
    };
    
    module.exports = service;
    
    // "private" methods
    function findSomeJson() {
      return new Promise(function() {
          // doing something that takes lots of code
          return someJson
      });
    }
    
    function analyzeSomeJson(someJson) {
      return new Promise(function(someJson) {
          // analyze someJson with lots of code
      });
    }