Search code examples
javascriptparse-platformpromise

Parse multi chain promises with a sequential for loop


In the code below, I am trying to do the following:

  • Have Stats(), getOverallStats() and GetGroups() to run in parallel. Each returns a promise.
  • The forEach in GetGroups.then() should run sequentially to ensure the output is in the correct order.
  • Once ALL of the above is complete, then run some more code.

However, I am getting very confused with the promises! The logging gives me:

looping
here
looping
looping

But what I am looking for is here to be at the end.

Finally, at the moment I have hardcoded loopAgelist[1] for testing purposes. But, I actually want to be able to loop through loopAgelist[] with a timeout in between! I would appreciate if someone could explain some promise 'rules' to use in these complicated cases.

    var loopAgeList;
    var looppromises = [];
    getAgeGroupList().then(function (loopAgeList) {
        var statsPromise = Stats(loopAgeList[1]);
        var oStatsPromise = getOverallStats();
        var grpPromise = GetGroups(loopAgeList[1]).then(function (groups) {
            var promise = Parse.Promise.as();
            groups.forEach(function (grp) {
                promise = promise.then(function () {    // Need this so that the tables are drawn in the correct order (force to be in series)
                    console.log("looping")
                    if (grp != "KO"){
                        var standingsPromise = Standings(loopAgeList[1], grp);
                        looppromises.push(standingsPromise);
                    }

                    var fixPromise = GetFixtures(loopAgeList[1], grp);
                    looppromises.push(fixPromise);
                    return fixPromise;
                });
            });
            return Parse.Promise.all(looppromises);
        });
        var promises = [statsPromise, oStatsPromise, grpPromise, looppromises];
        Parse.Promise.all(promises).then(function(results) {
            console.log("here");
        });
    });

Solution

  • The rewrite can be improved significantly by adopting a couple simple style rules: (1) there's no need to create a resolved promise and then chain to it (in fact, most would consider this an anti-pattern), (2) promises to be run together by iterating an array of operands is the perfect application of array .map (not reduce), (3) most importantly, smaller, testable, promise-returning functions always clears up the mystery.

    Putting all that together, the main function can be as simple as this...

    function loopOverOnce(agegroup) {
        let statsPromise = Stats(agegroup);
        let oStatsPromise = getOverallStats();
        let grpPromise = GetGroups(agegroup).then(function(groups) {
            return getStandingsAndFixturesForGroups(groups, agegroup);
        });
        return Parse.Promise.all([statsPromise, oStatsPromise, grpPromise]);
    }
    

    Let's write getStandingsAndFixturesForGroups. It's only job will be map the groups and aggregate promises to do work on each...

    function getStandingsAndFixturesForGroups(groups, agegroup) {
        let promises = groups.map(function(group) {
            return getStandingsAndFixturesForGroup(group, agegroup);
        });
        return Parse.Promise.all(promises);
    }
    

    Now, getStandingsAndFixturesForGroup, a function to do the async work on a single group, conditionally for part of the work...

    function getStandingsAndFixturesForGroup(group, agegroup) {
        let promises = (group != "KO")?  [ Standings(agegroup, grp) ] : [];
        promises.push(GetFixtures(agegroup, group));
        return Parse.Promise.all(promises);  // this is your standings promise (conditionally) and fixtures promise
    }
    

    Done. I'd test this code in the reverse order that it's presented here.

    EDIT The OP also asks how to perform several promises, serially, interspersed with timeouts. Here's my advice.

    First, a slightly simpler version of your delay function, which is a good example when it is right to create a new promise (because there's nothing at bottom to call to get one)

    function delay(interval) {
        return new Promise(function(resolve, reject){
            setTimeout(function() {resolve();}, interval);
        });
    };
    

    And reducing is a good way to build a list of promises, including interspersed delays...

    getAgeGroupList().then(function (loopAgeList) {
        loopAgeList.reduce(function(promise, agegroup) {
            return promise.then(function() {
                let promises = [loopOverOnce(agegroup), delay(15000)];
                return Promise.all(promises);
            });
        }, Promise.as());
    });
    

    A couple notes: this results in a sequence like loopOverOnce, timeout, loopOverOnce, timeout, ... etc.. If you'd like a timeout first, reverse the order of the little chain in the inner loop:

    [ delay(15000), loopOverOnce(agegroup) ]
    

    Final note is that all this could be made even shorter and prettier by adopting ES6 fat arrow syntax for anonymous functions, e.g.

    loopAgeList.reduce((promise, agegroup) => {
        promise.then(() => Promise.all([loopOverOnce(agegroup), delay(15000)]));
    }, Promise.as());