0

I'm looking to do two things with the Q library for node.

1) Issue a number of asynchronous queries, each of which uses the result of the previous one, and 2) Have access to the results of every query, all at once, once they're all complete

E.g. let's say a database has ridings, each of which has a city, each of which has a state, each of which has a country. Given a riding, I want to print out all this geographical data, all at once.

var ridingObj = // Already have access to this database object

ridingObj.getRelated('city')
    .then(function(cityObj) {
        // Have access to cityObj.getField('name');
        return cityObj.getRelated('state');
    })
    .then(function(stateObj) {
        // Have access to stateObj.getField('name');
        return stateObj.getRelated('country');
    })
    .then(function(countryObj) {
        // Have access to countryObj.getField('name');
        // Can't console.log anything but the country, because we no longer have access :(
    })

With this pattern I'm getting access to all the data, but not at the same time.

What is considered a clean, conventional pattern for getting all the data at once??

4castle
  • 32,613
  • 11
  • 69
  • 106
Gershom Maes
  • 7,358
  • 2
  • 35
  • 55
  • with es6 you can use Promise.all() - I'm sure there must be an equivalent with Q ... (but why not just use the native ES6 functionality anyway now it is there?) – Sam Redway Dec 22 '16 at 22:10
  • 1
    One thing you might want to consider is, if those are database calls, you might want to do them at once if possible as part of a transaction (depending on the DB you are using - SQL Joins or MongoDB populate). Doing 4 database calls instead of 1 will have a considerable performance impact as I am sure you can imagine.... But if you can't, I think 4castle's answer is best. It has 'side-effects' and doesn't seem clean, but I think is best.. – mkhanoyan Dec 22 '16 at 22:56
  • Though, you are firing sequential calls, your code seems to be interested in getting all the results at once. Why it needs to be sequential? Is it fine, if you get all the results at once without making sequential calls? – Sridhar Dec 23 '16 at 05:14
  • This is just an example case! I'm not actually working with a database, so I'm not interested in joins. I'm interested in the conventional promise pattern that should be used when 1) a series of non-blocking functions need to be executed in order, and 2) the results of all those function calls need to be accessed at the same time. – Gershom Maes Dec 27 '16 at 08:49

2 Answers2

2

A simple way that I've seen done on multiple occasions is to progressively write to an object that lies in the surrounding scope, and then reading from the object at the end of the promise chain:

var ridingObj = ...;
var result = {};

ridingObj.getRelated('city')
    .then(function(cityObj) {
        result.city = cityObj;                // write city
        return cityObj.getRelated('state');
    })
    .then(function(stateObj) {
        result.state = stateObj;              // write state
        return stateObj.getRelated('country');
    })
    .then(function(countryObj) {
        result.country = countryObj;          // write country
        console.log(result);                  // read all
    })
4castle
  • 32,613
  • 11
  • 69
  • 106
  • Cool, I'm sure that will work! I'm looking for what's considered to be the "conventional" method of dealing with this pattern - imaginably something that will avoid higher-scope variables. I'm hoping for something nifty involving `Q.spread` or something like that, but if it turns out there's no conventional way I'll check this! – Gershom Maes Dec 22 '16 at 21:12
  • Every modification I can think of would essentially pass `result` as a second parameter to `spread` at every stage of the promise chain. It adds a bit more boilerplate to each handler in the chain. I feel like this answer is as simple as I can get it, without sacrificing readability or adaptability. – 4castle Dec 22 '16 at 21:58
  • I think I've found a nifty way to do this without side-effects. I'm posting my answer, give me some feedback, and I'll decide which one of us to mark as accepted! – Gershom Maes Dec 27 '16 at 08:36
1

Here's a cool way I've thought up.

It uses a higher-scoped variable, but it doesn't have side-effects and it lets you access all the results as the arguments to a function - which seems clean.

var p = queryForRiding();
Q.spread([
    p,
    p = p.then(function(riding) { return riding.getRelated('city'); }),
    p = p.then(function(city) { return city.getRelated('state'); }),
    p = p.then(function(state) { return state.getRelated('country'); })
], function(riding, city, state, country) {
    console.log(riding, city, state, country);
});
Gershom Maes
  • 7,358
  • 2
  • 35
  • 55
  • 1
    This whole topic is well explored in [How do I access previous promise results in a .then() chain?](http://stackoverflow.com/questions/28250680/). This answer is an example of "Break the chain". – Roamer-1888 Dec 27 '16 at 11:58