Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to return single promise after for loop (which produces a promise on every iteration) is complete?

I have a problem with my promise return code, I have a function getTagQuotes which contains a for loop which can make multiple calls an API to return data into an array.

How my code for this begins below:

// If there are tags, then wait for promise here:
if (tags.length > 0) {

    // Setting promise var to getTagQuotes:
    var promise = getTagQuotes(tags).then(function() {
        console.log('promise =',promise);

        // This array should contain 1-3 tags:
        console.log('tweetArrayObjsContainer =',tweetArrayObjsContainer);

        // Loop through to push array objects into chartObj:
        for (var i=0; i<tweetArrayObjsContainer.length; i++) {
            chartObj.chartData.push(tweetArrayObjsContainer[i]);
        }

        // Finally draw the chart:
        chartDirective = ScopeFactory.getScope('chart');
        chartDirective.nvd3.drawChart(chartObj.chartData);
    });
}

My getTagQuotes function with the promise return:

function getTagQuotes(tags) {
    var deferred = $q.defer(); // setting the defer
    var url      = 'app/api/social/twitter/volume/';

    // My for loop, which only returns ONCE, even if there are 3 tags
    for (var i=0; i<tags.length; i++) {
        var loopStep = i;
        rawTagData   = [];

        // The return statement
        return GetTweetVolFactory.returnTweetVol(url+tags[i].term_id)
            .success(function(data, status, headers, config) {
                rawTagData.push(data);

                // One the last loop, call formatTagData
                // which fills the tweetArrayObjsContainer Array
                if (loopStep === (rawTagData.length - 1)) {
                    formatTagData(rawTagData);
                    deferred.resolve();
                    return deferred.promise;
                }
            });
    }

    function formatTagData(rawData) {

        for (var i=0; i<rawData.length; i++) {
            var data_array = [];
            var loopNum = i;

            for (var j=0; j<rawData[loopNum].frequency_counts.length; j++) {
                var data_obj = {};
                data_obj.x = rawData[loopNum].frequency_counts[j].start_epoch;
                data_obj.y = rawData[loopNum].frequency_counts[j].tweets;
                data_array.push(data_obj);
            }

            var tweetArrayObj = {
                "key" : "Quantity"+(loopNum+1), "type" : "area", "yAxis" : 1, "values" : data_array
            };

            tweetArrayObjsContainer.push(tweetArrayObj);
        }
    }
}

Take notice of this line

return GetTweetVolFactory.returnTweetVol(url+tags[i].term_id)

it's inside my for loop:

for (var i=0; i<tags.length; i++)

Everything works great if I only have to loop through once. However as soon as there is another tag (up to 3) it still only returns the first loop/data. It does not wait till the for loop is done. Then return the promise. So my tweetArrayObjsContainer always only has the first tag.

like image 535
Leon Gaban Avatar asked Oct 07 '15 16:10

Leon Gaban


People also ask

How do I resolve a promise in a for loop?

To use Javascript promises in a for loop, use async / await . This waits for each promiseAction to complete before continuing to the next iteration in the loop. In this guide, you learn how async/await works and how it solves the problem of using promises in for loops.

Can a promise return a promise?

We can apply not only normal functions to a Promise but also functions that itself return a Promise . In fact this is the domain of functional programming. A Promise is a specific implementation of a monad, then is bind / chain and a function that returns a Promise is a monadic function.

Does promise all always return same order?

Yes, the values in results are in the same order as the promises .

How to use promise inside a for each loop?

The Promise.allmethod returns a promise when all the promises inside the promisesarray is resolved. So, this is how you can use promise inside a for each loop. A similar approach can be applied to use promise inside for loop or while in JavaScript or Node.js.

How to wait for promise to be resolved after getpromise?

This happens because after making a call to getResult method, it in turns calls the getPromise method which gets resolved only after 2000 ms. getResult method doesn’t wait since it doesn’t returns a promise. So, if you return a promise from getResult method it can then be used to wait for the Promise to get resolved. Let’s try it.

Why does getpromise return a promise after 2000ms?

This happens because after making a call to getResult method, it in turns calls the getPromise method which gets resolved only after 2000 ms. getResult method doesn’t wait since it doesn’t returns a promise. So, if you return a promise from getResult method it can then be used to wait for the Promise to get resolved.

When does the return promise resolve?

This returned promise will resolve when all of the input's promises have resolved, or if the input iterable contains no promises. It rejects immediately upon any of the input promises rejecting or non-promises throwing an error, and will reject with this first rejection message / error.


Video Answer


4 Answers

Three issues:

  1. you didn't return the deferred promise from the getTagQuotes method.
  2. you were looking at i to see if you were through the loop, and the for loop is already completed (i == (tags.length - 1)) before the first success is even called.
  3. you called return in the first iteration of the loop so that you didn't even get to the 2nd item.

Here's corrected code (didn't test it yet)

function getTagQuotes(tags) {
    var deferred = $q.defer(); // setting the defer
    var url      = 'app/api/social/twitter/volume/';
    var tagsComplete = 0;

    for (var i=0; i<tags.length; i++) {
        rawTagData   = [];
        GetTweetVolFactory.returnTweetVol(url+tags[i].term_id)
            .success(function(data, status, headers, config) {
                rawTagData.push(data);
                tagsComplete++;

                if (tagsComplete === tags.length) {
                    formatTagData(rawTagData);
                    deferred.resolve();
                }
            });
    }

    return deferred.promise;
}
like image 99
Ben Wilde Avatar answered Nov 03 '22 01:11

Ben Wilde


return deferred.promise; should be the return value of your function, not the GetTweetVolFactory.returnTweetVol(), because that's what you intend to promisify.

Your problem is that you are calling several GetTweetVolFactory.returnTweetVol(), and then you need to merge all those async calls to resolve your promise. In order to do that, you should promisify just one GetTweetVolFactory.returnTweetVol() call:

function promisifiedTweetVol(rawTagData, urlStuff) {
    var deferred = $q.defer(); // setting the defer

    GetTweetVolFactory.returnTweetVol(urlStuff)
        .success(function(data, status, headers, config) {
            rawTagData.push(data);

            // One the last loop, call formatTagData
            // which fills the tweetArrayObjsContainer Array
            if (loopStep === (rawTagData.length - 1)) {
                formatTagData(rawTagData);
                deferred.resolve();
            }
        });

    return deferred.promise;
}

And then call each promise in a loop and return the promise that resolves when all promises are completed:

function getTagQuotes(tags) {
    var url      = 'app/api/social/twitter/volume/';
    var promises = [];

    // My for loop, which only returns ONCE, even if there are 3 tags
    for (var i=0; i<tags.length; i++) {
        var loopStep = if;
        rawTagData   = [];

        promises.push( promisifiedTweetVol(rawTagData, url+tags[i].term_id) );
    }

    // ...

    return $.when(promises);
}

There are a few more issues with your code, but you should be able to get this working with my tip.

like image 40
Tiago Fael Matos Avatar answered Nov 03 '22 02:11

Tiago Fael Matos


Put every promise in an array then do:

$q.all(arrayOfPromises).then(function(){
  // this runs when every promise is resolved.
});
like image 27
Fernando Pinheiro Avatar answered Nov 03 '22 00:11

Fernando Pinheiro


You should return an array of promises here, which means that you should change getTagsQuotes like this:

function getTagQuotes(tags) {

    var url      = 'app/api/social/twitter/volume/',
        promises = [];

    for (var i=0; i<tags.length; i++) {

       promises.push( GetTweetVolFactory.returnTweetVol( url+tags[i].term_id ) );

    }

    return promises;
}

And then loop through this promises like this:

if (tags.length > 0) {

    var promises = getTagQuotes(tags);

    promises.map( function( promise ) {

         promise.then( function( data ) { 

            //Manipulate data here

         });

    });
}

Edit: In case you want all promises to be finished as outlined in the comment you should do this:

if (tags.length > 0) {

    Promise.all( getTagQuotes(tags) ).then( function( data ) { 

        //Manipulate data here

    });
}

Edit: Full data manipulation:

Promise.all( getTagQuotes(tags) ).then( function( allData ) {

allData.map( function( data, dataIndex ){

    var rawData = data.data,
        dataLength = rawData.frequency_counts.length,
        j = 0,
        tweetArrayObj = {
            // "key"    : "Quantity"+(i+1),
            // "color"  : tagColorArray[i],
            "key"    : "Quantity",
            "type"   : "area",
            "yAxis"  : 1,
            "values" : []
        };

    for ( j; j < dataLength; j++ ) {

        rawData.frequency_counts[j].start_epoch = addZeroes( rawData.frequency_counts[j].start_epoch );

        tweetArrayObj.values.push( { x:rawData.frequency_counts[j].start_epoch, y:rawData.frequency_counts[j].tweets  } );

    }

    tweetArrayObjsContainer.push( tweetArrayObj );

});

for ( var i= 0,length = tweetArrayObjsContainer.length; i < length; i++ ) {

    chartObj.chartData.push( tweetArrayObjsContainer[ i ] );

}

chartDirective = ScopeFactory.getScope('chart');
chartDirective.nvd3.drawChart(chartObj.chartData);

});
like image 24
guramidev Avatar answered Nov 03 '22 02:11

guramidev