1

I am having a hard time figuring out how promises work. I have the following code that I am executing iterate through an array.

runOpts.automationResults.reduce(function (p, val) {
        return p.then(function () {
              return  pd.run(val);
});
}, Promise.resolve()).then(function (rallyResults) {
     console.log('returned from method');
     console.log('rallyResults ', rallyResults);
}, function (err) {
     console.log('error in reduce', err);
});

It is calling the following method

ProcessDatabase.prototype.run = function (options) {
    return new Promise((resolve, reject) => {
        var dal = new db();
        var resultsArray = {
            results: [],
            options: [],
            testSet: [],
            testIteration: '',
            multipleSets: 0,
            totalCount: 0
        }
        resultsArray.options = options;
        dal.getAutomationResultsByBuild(options.Build).then((results) => {
            resultsArray.results = results;
            resultsArray.totalCount = results.data.length;
            resolve(resultsArray);
        })
    }).then(function (resultsArray) {
        var results = [];
        //console.log('resultsArray', resultsArray);
        //console.log(`Starting Rally publishing for Build '${resultsArray.options.Build}'...`)

        if (resultsArray.options.Executiontype == 'ci') {

            rallyApi.autoDiscoverTestSets().then((sets) => {
                resultsArray.testSet = sets.sets;
                resultsArray.testIteration = sets.iteration;
                resultsArray.multipleSets = sets.sets.length > 1;
                results.push(resultsArray);
                console.log('results', results);
            }, (err) => { reject(err); });
            // totalResults = totalResults + results.data.length;
        } else {
            rallyApi.addTestSet('Automation: ' + resultsArray.options.type + ' - ' + resultsArray.options.build, config.get('rally.projects.testing.ref'), null, resultsArray.options.SessionUser).then((resp) => {
                //console.log(resp);
                console.log('New test set ' + resp.FormattedID + ' created.');
                resultsArray.multipleSets = 0;
                resultsArray.testSet = resp.FormattedID;
                //console.log('resultsArray', resultsArray);
                results.push(resultsArray);
                console.log('results', results);
            }, (err) => {
                console.log(err);
            });
        }

        console.log('results', results);
        return Promise.all(results);

    })
}

I have tried several different iterations of what I currently have and it always goes back to the calling .reduce before the results are complete in the ProcessDatabase.prototype.run method.

I have put console logs in and this is what I get. I can see that the final results array has all the information I need. I just haven't been able to figure out how to get it passed back to the calling .reduce.

---These are actually from the .reduce and are written to the log before the results in the method called
returned from method 
rallyResults  [] 

**----These are from the ProcessDatabase.prototype.run method called**
**This is the contents of the "results" set the first iteration through**
-- Found 2 test sets.
results [ { results: { _success: true, _message: '', _data: [Array] },
    options:
     { Executiontype: 'ci',
       Build: 'test_030518_103956',
       Environment: 'dev',
       SessionUser: 'https://rally1.rallydev.com/slm/webservice/v2.0/user/165547093296' },
    testSet: [ [Object], [Object] ],
    testIteration: 'https://rally1.rallydev.com/slm/webservice/v2.0/iteration/184203152680',
    multipleSets: true,
    totalCount: 4 } ]
New test set TS2969 created.

**This is the contents of the "result" set after the second iteration 
through and what I trying to get passed back to the calling .reduce.**

results [ { results: { _success: true, _message: '', _data: [Array] },
    options:
     { Executiontype: 'ci',
       Build: 'test_030518_103956',
       Environment: 'dev',
       SessionUser: 'https://rally1.rallydev.com/slm/webservice/v2.0/user/165547093296' },
    testSet: [ [Object], [Object] ],
    testIteration: 'https://rally1.rallydev.com/slm/webservice/v2.0/iteration/184203152680',
    multipleSets: true,
    totalCount: 4 },
  { results: { _success: true, _message: '', _data: [Array] },
    options:
     { Executiontype: 'regression',
       Build: 'test_030518_110447',
       Environment: 'dev',
       SessionUser: 'https://rally1.rallydev.com/slm/webservice/v2.0/user/165547093296' },
    testSet: 'TS2969',
    testIteration: '',
    multipleSets: 0,
    totalCount: 6 } ]
    

Any help would be GREATLY appreciated. Thanks Christine

  • 1
    reduce Is not promise aware. It's a sync method. Iow: you can't do async inside a reduce. – Keith Mar 06 '18 at 02:45
  • @Keith - Using `.reduce()` in this way is a common design pattern for serializing async operations. It absolutely works when done properly. It ends up doing `Promise.resolve().then(fn).then(fn).then(fn)` as it cycles through the contents of an array which serializes access to async operations on an array. – jfriend00 Mar 06 '18 at 03:15
  • You are not using `Promise.all()` properly. You need to pass it an array of ***promises***. – jfriend00 Mar 06 '18 at 03:17
  • `run` needs to avoid the [`Promise` constructor antipattern](https://stackoverflow.com/q/23803743/1048572?What-is-the-promise-construction-antipattern-and-how-to-avoid-it)! – Bergi Mar 06 '18 at 20:07

1 Answers1

2

There are a number of issues in this code, but the main one is that when you run another async operation inside a .then() handler, you have to return that promise from the .then() to get it properly chained into the main promise chain. If you don't, then the parent promise is not linked to that internal promise at all (it becomes its own independent promise chain with no way for you to monitor it) and the parent promise does not wait for the internal promise.

Here are the changes made:

  1. Remove promise anti-pattern of unnecessarily wrapping a promise in another promise (just return the promise you already have)
  2. Return nested promises from within .then() handlers to chain them appropriately
  3. Consolidate error logging in one place and rethrow error so it is returned properly
  4. Remove Promise.all().
  5. Remove results.push(resultsArray); since there does not appear to be any point to that. The way I've code it below, the top level promise resolves to resultsArray and since there is only one of them, I don't see a need to embed it in another array.

Here's the modified code:

ProcessDatabase.prototype.run = function (options) {
    var dal = new db();
    var resultsArray = {
        results: [],
        options: {},
        testSet: [],
        testIteration: '',
        multipleSets: 0,
        totalCount: 0
    }
    resultsArray.options = options;
    return dal.getAutomationResultsByBuild(options.Build).then((results) => {
        resultsArray.results = results;
        resultsArray.totalCount = results.data.length;
        return resultsArray;
    }).then(function (resultsArray) {
        //console.log('resultsArray', resultsArray);
        //console.log(`Starting Rally publishing for Build '${resultsArray.options.Build}'...`)

        if (resultsArray.options.Executiontype == 'ci') {

            return rallyApi.autoDiscoverTestSets().then((sets) => {
                resultsArray.testSet = sets.sets;
                resultsArray.testIteration = sets.iteration;
                resultsArray.multipleSets = sets.sets.length > 1;
                return resultsArray;
            });
        } else {
            return rallyApi.addTestSet('Automation: ' + resultsArray.options.type + ' - ' + resultsArray.options.build, config.get('rally.projects.testing.ref'), null, resultsArray.options.SessionUser).then((resp) => {
                //console.log(resp);
                console.log('New test set ' + resp.FormattedID + ' created.');
                resultsArray.multipleSets = 0;
                resultsArray.testSet = resp.FormattedID;
                return resultsArray;
            });
        }

    }).catch(err => {
        // log error and rethrow
        console.log(err);
        throw err;
    });
}
jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • Hi jfriend00. Thank you for your response. I have updated my initial post to try and explain better what I was needing to happen. Multiple records are in the array in the .reduce. What I was trying to get was an array back that included additional information for each. The second "result" set in the above console logs is what I was needing back to the calling .reduce. So that is why I was trying to write the ProcessDatabase.prototype.run as a promise. Obviously I did it wrong. Is it possible to get a consolidate "results" array from the ProcessDatabase.prototype.run like I need – Christine Edwards Mar 06 '18 at 19:16