How to return a single promise after a loop (which gives a promise at each iteration) completed?

I have a problem with the promise return code, I have a getTagQuotes function that contains a for loop that can make several API calls to return data to an array.

How my code for this starts 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); }); } 

GetTagQuotes return promise function:

 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); } } } 

Pay attention to this line.

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

inside the for loop:

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

Everything works fine if I only need to scroll once. However, as soon as another tag appears (up to 3), it returns only the first loop / data. It does not wait for the for loop to complete. Then return the promise. So my tweetArrayObjsContainer always has only the first tag.

+6
source share
6 answers

Three questions:

  • You did not return a pending promise using the getTagQuotes method.
  • you looked i to find out if you went through the loop and the for loop is already completed ( i == (tags.length - 1) ) before the first success is called.
  • you called return in the first iteration of the loop so you don't even fall into the second element.

Here's the corrected code (not tested 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; } 
+2
source

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

Your problem is that you are calling multiple GetTweetVolFactory.returnTweetVol() and then you need to combine all these asynchronous calls in order to fulfill your promise. To do this, you only have to promise one call to GetTweetVolFactory.returnTweetVol() :

 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 problems with your code, but you should be able to work with my tip.

+2
source

Here you should return an array of promises, which means you have to change getTagsQuotes as follows:

 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 scroll through these promises as follows:

 if (tags.length > 0) { var promises = getTagQuotes(tags); promises.map( function( promise ) { promise.then( function( data ) { //Manipulate data here }); }); } 

Edit: If you want all promises to be completed as indicated in the comment, you should do this:

 if (tags.length > 0) { Promise.all( getTagQuotes(tags) ).then( function( data ) { //Manipulate data here }); } 

Edit : full data processing:

 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); }); 
+1
source

Put each promise in an array, and then do:

 $q.all(arrayOfPromises).then(function(){ // this runs when every promise is resolved. }); 
+1
source

The use of deferred is considered widespread as an anti-pattern. If your promise library supports the promise constructor, this is an easier way to create your own promises.

Instead of trying to resolve all promises in one, I usually use a promise implementation that has the all function. Then I create one function that returns a promise for a thing, and then another function that returns a promise for all things.

Using the map() function is also usually much cleaner than using a for loop.

Here is a general recipe. Assuming the implementation of your promise has some taste for the all function:

 var fetchOne = function(oneData){ //Use a library that returns a promise return ajax.get("http://someurl.com/" + oneData); }; var fetchAll = function(allData){ //map the data onto the promise-returning function to get an //array of promises. You could also use `_.map` if you're a //lodash or underscore user. var allPromises = myData.map(fetchOne); return Promise.all(allPromises); }; var allData = ["a", "b", "c"]; var promiseForAll = fetchAll(allData); //Handle the results for all of the promises. promiseForAll.then(function(results){ console.log("All done.", results); }); 
0
source

In connection with this question and an earlier question :

  • the code as a whole will be much cleaner with array.map () instead of for loops in several places.
  • getTagQuotes() will be cleared by creating an array of promises, sending it to $q.all() and returning the cumulative promise.
  • formatTagData() , and its connection with the caller will be cleared, returning the converted rawData .

With a few assumptions, the code should be simplified to the following:

 getTagQuotes(tags).then(function(tweetArrayObjsContainer) { chartObj.chartData = chartObj.chartData.concat(tweetArrayObjsContainer); // concat() ... // chartObj.chartData = tweetArrayObjsContainer; // ... or simply assign?? chartDirective = ScopeFactory.getScope('chart'); chartDirective.nvd3.drawChart(chartObj.chartData); }); function getTagQuotes(tags) { var url = 'app/api/social/twitter/volume/'; var promises = tags.map(function(tag) { var deferred = $q.defer(); GetTweetVolFactory.returnTweetVol(url + tag.term_id) .success(function(data, status, headers, config) { deferred.resolve(data); }) .error(function(data, status) { console.log(tag.term_id + ': error in returning tweet data'); deferred.resolve(null); // resolve() here effectively catches the error }); return deferred.promise; }); return $q.all(promises).then(formatTagData); //this is much much cleaner than building an explicit data array and resolving an outer deferred. function formatTagData(rawData) { return rawData.filter(function(data) { return data || false; // filter out any nulls }).map(function(item, i) { return { 'key': 'Quantity' + (i+1), 'type': 'area', 'yAxis': 1, 'color': tagColorArray[i], 'values': item.frequency_counts.reverse().map(function(c) { return { x: addZeroes(c.start_epoch), y: c.tweets, }; }) }; }); } } 
0
source

All Articles