1

Given this IDs array:

var array = [1,2,3,4,5];

How can I return a promise AFTER all the other calls made within the loop are finished?

            var deferred = $q.defer(),
                result = [];

            for (var i = 0; i < array.length; i++) {
                var id = array[i];

                Tools.remove(campaignID).then(function(result) {
                    result.push(result.id);
                }).catch(function (response) {
                    result.push(response)
                });
            }

            deferred.resolve(result);

            return deferred.promise;
Raul Vallespin
  • 1,323
  • 2
  • 11
  • 16

3 Answers3

4

You're looking for $q.all which waits for an array of promises.

I'd do it like this:

return $q.all(array.map(Tools.remove)).then(function(results){
    return results.map(function(result){ return result.id; });
});

The other answers implement things you get for free from JavaScript or promises already - and mostly have anti-patterns.

You just map every item to Tools.remove, wait for it - get the IDs and return them.

Community
  • 1
  • 1
Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
  • Are you really mapping a function? – Adrian Salazar Feb 19 '15 at 09:49
  • 2
    @AdrianSalazar yes, `Tools.remove` is a function and `Array#map` takes every item in the array and calls the function on it. So `array.map(Tools.remove)` is the same as calling `Tools.remove` on every item of `array` and pushing the result to a new array. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map – Benjamin Gruenbaum Feb 19 '15 at 09:51
  • Nice one. Every day you learn something new... Just clarify that map won't work on each browser out of the box. Now how to solve the problem of "return a value (he says promise because he understands async) after all async operations have finished... To put you in context here, you're inside a method... – Adrian Salazar Feb 19 '15 at 09:54
  • @AdrianSalazar it works on every browser since IE8 and it can be polyfilled in IE8. Since OP is using AngularJS ($q here) chances are he is not supporting IE8 anyway. The method I'm using - `$q.all` does that - it takes an array of promises and returns a promise that completes when all of these finish - this is $.when in jQuery. – Benjamin Gruenbaum Feb 19 '15 at 09:56
  • @BenjaminGruenbaum I have read the various links you have posted, but I'm still not sure on one point. This will return a promise which when resolved will return an array of ids. If any of the individual promises failed the Id will just be undefined. Would it not be better to create and return a new promise in the function so that if any of the individual promises failed you can reject the return promise with a meaningful error rather than just setting the 'undefinded'? – Anduril Feb 19 '15 at 12:20
  • @Anduril actually, in my code above and _unlike_ what OP actually had (I admit - this is an opinionated choice I've made here) : in this implementation if a single call fails rather than returning undefined - it will reject the whole aggregated promise (the `$q.all`) with that error as a rejection. So if this performs HTTP calls you'll get the correct stack trace of where it failed and why here instead of a resolved undefined. This is like a method `throw`ing a meaningful error instead of doing a `return null` to let you know something bad happened. Rejections are like exceptions in sync code. – Benjamin Gruenbaum Feb 19 '15 at 12:44
  • @BenjaminGruenbaum Ok I understand, definitely better (in my opinion as well, partial list of Ids probably wouldn't be particularly useful in this case anyway). Thank you – Anduril Feb 19 '15 at 12:51
-1

I presume you don't actually want to return a promise when all your tasks are finished, but resolve it.

You probably have to implement some custom mechanism for tracking the number of outstanding async calls. The easiest is a simple integer counter: increment it every time you dispatch an async call, decrement it every time an async call returns. If at returning, any async calls finds 0, it can conclude it is the last async call to return, and can trigger the next step of execution.

Example with your code:

var asyncCount = 0;
for (var i = 0; i < array.length; i++) {
            var id = rows[i];

            asyncCount++;
            Tools.remove(campaignID).then(function(result) {
                result.push(result.id);
                asyncCount--;
                if(asyncCount === 0) nextStep();
            });
        }

If you're worried about mixed responsibilities, you can turn this into some sort of async-manager class.

doldt
  • 4,466
  • 3
  • 21
  • 36
-1

I'll do it like this :D

var arrayOfIds = [1,2,3,4,5];
var results = [];
var mySingleDeferred = new $.Deferred();

var arrayOfDeferreds = $.map(arrayOfIds, function(campaignID){       

    var deferredForThisOperation = Tools.remove(campaignID);

    deferredForThisOperation.done(function(result) {
       results.push(result.id);
    }).catch(function (response) {
       results.push(response)
    });  

    return deferredForThisOperation;
});

$.when(arrayOfDeferreds).then(function(){
    mySingleDeferred.resolve(results);
});

return mySingleDeferred.promise(); 
Adrian Salazar
  • 5,279
  • 34
  • 51
  • 1
    Why the deferred antipattern? – Benjamin Gruenbaum Feb 19 '15 at 09:40
  • Suppose this is a method and you want to provide a single result at the end of all your blackbox-crap... then I'd return a promise, not an array of deferreds... If this code was just a final front-end block (no caller) then yes, a single wait all is needed... The anti pattern if you really understood is creating a new deferred for each already existing promise. – Adrian Salazar Feb 19 '15 at 09:51
  • You can return you `$.when` call - there is no need to wrap it. Just `return $.when(arrayOfDeferreds` - or even better - don't create an array of deferreds just push the promises from `deferredForThisOperation` to an array. At this point you can just `map` the `arrayOfId`s to Tools.remove like in my answer and `$.when` it (by the way- you mean `$.when.apply($, array` since it takes varargs and also - OP isn't using jQuery. – Benjamin Gruenbaum Feb 19 '15 at 09:53