javascriptnode.jsforeachpromisewhen-js

Node.js/Javascript - nested promises and for loop with when.js


I am currently struggling with the control flow of promise (promise newbie!).

I make a call to Redis which returns an array object. I then iterate through each of the results and call back to Redis to get a value and wish to populate these in to a final object out.

The out object is never populated, im guessing as the forEach has not completed:

(note the Redis client lib returns a when.js based promise as default)

var out = {};

REDISCLIENT.keys("apikey:*")
    .then(function (replies) {

        replies.forEach(function (key, index) {

            REDISCLIENT.get(key)
                .then(function (value) {
                    out[key] = value;
                })
                .done(console.log(out));

        });

    })
    .catch(console.log)
    .done(console.log(out));

How can I guarantee that the forEach loop is complete?

I have read many similar posts (I know this is a duplicate) however have not been able to understand why the inner done() method does not contain the fully complete out obj.

Im guessing I need to wrap the forEach itself in a promise? Appreciate any direction.


Update 1: huge thanks to @David_Aurelio. I now need to populate out with the key and values. Here is my attempt:

GLOBAL.REDISCLIENT.keys("apikey:*")
        .then(function (replies) {
            return when.all(replies.map(function (key, index) {
                return GLOBAL.REDISCLIENT.get(key)
                    .then(function (val) {
                        out[key] = val;
                        console.log(key, val);
                    });
            }));
        })
        .catch(console.log)
        .done(function (out) {console.log(out); });

The inner console.log prints the correct key/values

key1 val1
key2 val2

The final done now prints:

[ undefined, undefined ]

Solution

  • It's important to understand that flow control and the data conveyed by a promise chain, are determined by :

    Here's how to achieve what you want with out as an inner member.

    REDISCLIENT.keys("apikey:*")
    .then(function (replies) {
        var out = {}: //<<<<< initiate `out` as an inner member
        return when.all(replies.map(function (key, index) { //<<<<< here's David Aurelio's when.all(replies.map(...))
            return REDISCLIENT.get(key).then(function (value) { //<<<<< `return` here causes `.map()` to build an array of promises.
                out[key] = value;
            });
        })).then(function() { //<<<< here's an additional `.then()` chained to `when.all(...)`
            return out; //<<<<< `return` here makes the populated `out` available to the `.done()` callback below.
        });
    })
    .catch(console.log)
    .done(function (out_) {
        console.log(out_);
    });
    

    The ugly outer member has disappeared!

    In the .done() callback, I have changed the member name to out_ in order to emphasize that it is passed as a consequence of that return out, which happens only when all [geddit] the promises returned by REDISCLIENT.get() calls have successfully settled.