var app = angular.module('app', []); app.controller('ctrl', ['$scope', '$timeout', '$q', function($scope, $timeout, $q) { var results = [{ id: 1, name: 'one' }, { id: 2, name: 'two' }, { id: 3, name: 'three' }]; var promisesFunction = function() { var uploadPromises = [], printforms = [], printPromises = [], printDefer = [], counter = 0, defer = $q.defer(); angular.forEach(results, function(temp, key) { // first delay $timeout(function() { var html = '<div><span>My Name Is Jack</span></div>'; var data = { sFunnyName: 'fluffy.html', sContent: '<p>some content</p>' }; printDefer[key] = $q.defer(); printforms[key] = { html: html, data: data }; printPromises[key] = printDefer[key].promise; defer.resolve(); }); uploadPromises.push(defer.promise); }); var asyncUpload = function(i, print, defs) { if (i < print.length) { // emulation of $http.post console.log('Start upload ' + i); $timeout(function() { results[i].value = 'done'; printDefer[i].resolve(); console.log(results[i].value + ' ' + i); ++i; asyncUpload(i, print, defs); }, 1000); } }; // this $q.all wait until first delay is done for all 3 elemets. // Then upload this items. var first = $q.all(uploadPromises).then(function() { console.log('First promise is done!'); asyncUpload(counter, printforms, printDefer); }); // this $q.all wait until all items is loaded. $q.all([first, printPromises]).then(function(uploadResults) { console.log('RESOLVED!') }); } promisesFunction(); } ]) 
 <!DOCTYPE html> <html ng-app="app"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width"> <title>Example</title> </head> <body ng-controller='ctrl'> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular.min.js"></script> </body> </html> 

I have an array of objects. I want to load them all and then return all the results together, in a single ensemble. But in my function there are 2 delays, in connection with this I decided to use promises:

  1. $timeout (I need it to wait for $compile compile content from my objects, but that's not the point), 2
  2. $http.post (when I already compiled the content with the objects passed to the server).

Therefore, I use $q.all to wait for all these actions to be carried out. In debbager, I see that some promises with status still 0, but already $q.all missed them. Tell me where I was wrong?

An example of jsbin that completely simulates the situation.

  • The code must be directly in the question, in addition, a snippet showing the problem can be inserted into the question - Grundy
  • Now I'll try to insert what I need) just in the jsbin console where you can see the execution .. - YoroDiallo
  • one
    In the built-in snippets, it is also shown by default - Grundy
  • and yes, I already see the wrong use :-) so add - Grundy
  • @Grundy turned out!) Updated) - YoroDiallo

2 answers 2

The problem is the loss of promises.

If inside then a new promise is created, but not returned, then it is lost and cannot be traced.

In the code provided there are several places with lost promises:

  1. In the first loop, the promise of $ timeout is lost.

     angular.forEach(results, function(temp, key) { // first delay $timeout(function() {...}) 
    1. inside the $ timeout handler, the global defer is resolved into resolve, that is, it will work once for the first $ timeout, and in fact there are several links to the same object in uploadPromises .
  2. same leaked $ timeout in asyncUpload function.

Solution: return all promise

 var app = angular.module('app', []); app.controller('ctrl', ['$scope', '$timeout', '$q', function($scope, $timeout, $q) { var results = [{ id: 1, name: 'one' }, { id: 2, name: 'two' }, { id: 3, name: 'three' }]; var promisesFunction = function() { var uploadPromises = [], printforms = [], printPromises = [], printDefer = [], counter = 0; angular.forEach(results, function(temp, key) { // first delay uploadPromises.push($timeout(function() { var html = '<div><span>My Name Is Jack</span></div>'; var data = { sFunnyName: 'fluffy.html', sContent: '<p>some content</p>' }; printDefer[key] = $q.defer(); printforms[key] = { html: html, data: data }; printPromises[key] = printDefer[key].promise; })); }); var asyncUpload = function(i, print, defs) { if (i < print.length) { // emulation of $http.post console.log('Start upload ' + i); return $timeout(function() { results[i].value = 'done'; printDefer[i].resolve(); console.log(results[i].value + ' ' + i); return asyncUpload(i + 1, print, defs); }, 1000); } }; // this $q.all wait until first delay is done for all 3 elemets. // Then upload this items. var first = $q.all(uploadPromises).then(function() { console.log('First promise is done!'); return asyncUpload(counter, printforms, printDefer); }); // this $q.all wait until all items is loaded. $q.all([first, printPromises]).then(function(uploadResults) { console.log('RESOLVED!') }); } promisesFunction(); } ]) 
 <!DOCTYPE html> <html ng-app="app"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width"> <title>Example</title> </head> <body ng-controller='ctrl'> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular.min.js"></script> </body> </html> 

  • Thanks for the help, it really helped, I will digest the code a little more in order to finally understand it. Thanks for the hint about snipes, I have not used it before, now I will! - YoroDiallo
  • tell me, plz, all the same why does $ q.all skip if there is an element in the array with $$ status 0. in theory, should you skip only with status 1? - YoroDiallo
  • @YoroDiallo, what do you mean it skips an element with $$status 0 ? The then handler for the promise that $q.all will be fulfilled when all promises in the passed array are in the status of resolved. It is not clear where you can see $$ status 0 . And how do you check? - Grundy
  • I understood your explanation, made edits to the "combat" code, and it all worked up to the 2nd request for $http.post which did not wait and executed the 2nd $q.all . this is somewhere my side, of course, I try to identify it) traced in the debugger, in the object with promises, on the 1st ( first ) status 1, on the 2nd array ( printPromises ) on the 1st element status 1, on second, 0, but then works - YoroDiallo
 var ... defer = $q.defer(); angular.forEach(results, function(temp, key) { $timeout(function() { ... defer.resolve(); }); uploadPromises.push(defer.promise); 

The same defer belongs to all elements of the array. Accordingly, the first of them resolvit all promises (more precisely, the only promise that is repeatedly placed in the array).

Corrected as follows:

 angular.forEach(results, function(temp, key) { var defer = $q.defer(); $timeout(function() { ... defer.resolve(); }); uploadPromises.push(defer.promise); 

By the way, it is quite possible to raise the premises of promise to the top:

 angular.forEach(results, function(temp, key) { var defer = $q.defer(); uploadPromises.push(defer.promise); $timeout(function() { ... defer.resolve(); }); 

or even use map and other syntax:

 uploadPromises = results.map(function(temp, key) { return $q(function (resolve, reject) { ... resolve(); }); }); 

or even like this:

 uploadPromises = results.map(function(temp, key) { return $timeout(function() { // Ну или $http.get ... }); }); 
  • there seems to be no one defer , for uploadPromises this is defer ( defer = $q.defer() ), and for printPromises this is printDefer ( printDefer [key] = $ q.defer () `), or I misunderstand? - YoroDiallo
  • There it’s better to add the $ timeout results to the array right away, without manual defer - Grundy
  • @Grundy, yes, I forgot that there is promise. But it's all the same for the example was. Completed. - Qwertiy
  • Thank you all very much for your help, everything seems to be clear, but I'll sit back with your code a little more to go deeper, a little confused) - YoroDiallo