gilad s
gilad s

Reputation: 485

How to execute code after 2 nested loops in node

I'm trying to find the lowest value ("best score") in 2 nested loops and then save the result after the loops are done. The following code seems to execute the final saving before the loops and not after them.

var bestScore = 300;
SaltEdge.getCustomerConnections(customerId, function(res1) {
    Promise.all(res1.json.data.map(function(connection) {
        return Promise.resolve()
            .then(function() {

                SaltEdge.getConnectionAccounts(connection.id, function(res2) {
                    if (res2.json.data) {
                        return Promise.all(res2.json.data.map(function(account) {

                            SaltEdge.get3MonthsIncome(connection.id, account.id, function(threeMonthsIncome) {
                                console.log('threeMonthsIncome', threeMonthsIncome);
                                var accountScore = SaltEdge.threeMonthsIncomeToScore(threeMonthsIncome);
                                console.log('account score', accountScore);
                                if (bestScore > accountScore) bestScore = accountScore;

                                console.log('best score', bestScore);
                                return bestScore;
                            });
                        }));
                    }
                });

            })
            .then(function(result) {
                return bestScore;
            });
    })
    ).then(function(res) {
        console.log("i'm here" + bestScore, res);
        if (bestScore < 300) {
            console.log('--update score', bestScore);
            Borrower.update(borrowerId, {salt_edge_score: bestScore}, function() {
                done(new Response(200,{ updated: true }));
            });
            resolve(bestScore);
        } else {
            done(new Response(200,{ updated: true }));
        }
    });
});

Upvotes: 0

Views: 54

Answers (1)

fubar
fubar

Reputation: 383

Short version:

Aggregate all the Promises into one array and use Promise.all() with that array. Or have an array for the Promise.all() of the inner loops and use another Promise.all() to wait for them to all resolve. Data structure of result [[...],[...],[...]].

Why it fails:

So your call:

SaltEdge.getConnectionAccounts(connection.id, function(res2) {
    if (res2.json.data) {
    // is never used
    return Promise.all(res2.json.data.map(function(account) {

runs into nothing because the Promise.all() is never consumed because the function is in a .then() block without a return, followed by a .then() that does nothing with a non existent value.

.then(function(result) {
    return bestScore;
});

However since you reassign bestScore in the callback of getConnectionAccounts and this is a global you will have a side effect. So when the code above runs result will be undefined and bestScore unknown at the time since you reassign it in a callback. This means that all the Promises from the Promise.all() should resolve to garbage. (some value)

How to for your case:

First order of business should be to get the reassignment, if (bestScore > accountScore) bestScore = accountScore;, some where to the end of the chain. Make it conditional that all Promises have to resolve where Promise.all() comes into play.

So contrary to the comment, you can insert asynchronous calls. BUT you have to return a Promise from within the .then(). Which means that either the callback has to resolve the returned Promise or the called function has to return a Promise of their own.

So does SaltEdge.getConnectionAccounts() return a Promise? If yes just return the function call. If not use something like this:

return new Promise(function (resolve) {
    SaltEdge.get3MonthsIncome(connection.id, account.id, function (threeMonthsIncome) {
        console.log('threeMonthsIncome', threeMonthsIncome);
        var accountScore = SaltEdge.threeMonthsIncomeToScore(threeMonthsIncome);
             console.log('account score', accountScore);
             if (bestScore > accountScore) bestScore = accountScore;

             console.log('best score', bestScore);
             resolve(bestScore);
     });
})

To recap: We now have one promise for each score. We can feed all these Promises to Promise.all() at once. Just return an array from within your inner map() and append a flat() to your outer map().

Promise.all(
    res1.json.data.map(
        function (connection) {

            // will return an array of promises
            return SaltEdge.getConnectionAccounts(connection.id, function (res2) {
                if (res2.json.data) {
                    return res2.json.data.map(function (account) {
                        // for better readability return added
                        return new Promise(function (resolve) {
                            SaltEdge.get3MonthsIncome(connection.id, account.id, function (threeMonthsIncome) {
                                console.log('threeMonthsIncome', threeMonthsIncome);
                                var accountScore = SaltEdge.threeMonthsIncomeToScore(threeMonthsIncome);
                                console.log('account score', accountScore);

                                // move this assignment
                                if (bestScore > accountScore) bestScore = accountScore;

                                console.log('best score', bestScore);
                                // resolving the promise inside the callback
                                resolve(bestScore);
                            });
                        })
                    })
                }
            });
        }
    // flat should get rid of the nested arrays
    ).flat()
) // use .then() for what to do with the array

The Promise.all() will resolve to an array of all values of all calls. That's when you execute your code after 2 loops.

Upvotes: 1

Related Questions