How do I sequentially chain promises with angularjs $q?

30,059

Solution 1

Simply use the $q.when() function:

var items = ['one', 'two', 'three'];
var chain = $q.when();
items.forEach(function (el) {
  chain = chain.then(foo(el));
});
return chain;

Note: foo must be a factory, e.g.

function setTimeoutPromise(ms) {
  var defer = $q.defer();
  setTimeout(defer.resolve, ms);
  return defer.promise;
}

function foo(item, ms) {
  return function() {
    return setTimeoutPromise(ms).then(function () {
      console.log(item);
    });
  };
}

var items = ['one', 'two', 'three'];
var chain = $q.when();
items.forEach(function (el, i) {
  chain = chain.then(foo(el, (items.length - i)*1000));
});
return chain;

Solution 2

Redgeoff, your own answer is the way I used to translate an array into a chained series of promises.

The emergent de facto pattern is as follows :

function doAsyncSeries(arr) {
    return arr.reduce(function (promise, item) {
      return promise.then(function(result) {
        return doSomethingAsync(result, item);
      });
    }, $q.when(initialValue));
}

//then
var items = ['x', 'y', 'z'];
doAsyncSeries(items).then(...);

Notes:

  • .reduce is raw javascript, not part of a library.
  • result is the previous async result/data and is included for completeness. The initial result is initialValue. If it's not necessary to pass `result, then simply leave it out.
  • adapt $q.when(initialValue) depending on which promise lib you use.
  • in your case, doSomethingAsync is foo (or what foo() returns?) - in any case, a function.

If you are like me, then the pattern will look, at first sight, like an impenetrable cludge but once your eye becomes attuned, you will start to regard it as an old friend.

Edit

Here's a demo, designed to demonstrate that the pattern recommended above does in fact execute its doSomethingAsync() calls sequentially, not immediately while building the chain as suggested in the comments below.

Solution 3

Having this:

let items = ['one', 'two', 'three'];

One line (well, 3 for readability):

return items
    .map(item => foo.bind(null, item))
    .reduce($q.when, $q.resolve());

Solution 4

var when = $q.when();

for(var i = 0; i < 10; i++){
    (function() {
         chain = when.then(function() {
        return $http.get('/data');
      });

    })(i); 
}

Solution 5

In perhaps a simpler manner than redgeoff's answer, if you don't need it automated, you can chain promises using $q.when() combined with .then() as shown in the beginning of this post. return $q.when() .then(function(){ return promise1; }) .then(function(){ return promise2; });

Share:
30,059

Related videos on Youtube

redgeoff
Author by

redgeoff

Lead Software Developer at Knock Rentals. We make a CRM for property managers. We're hiring! Geoff is also the creator of MSON, a new declarative programming language that will allow anyone to develop software visually. In his spare time, he hacks on JS, React, GraphQL, Docker, Serverless and whatever else seems fun.

Updated on August 06, 2020

Comments

  • redgeoff
    redgeoff over 3 years

    In the promise library Q, you can do the following to sequentially chain promises:

    var items = ['one', 'two', 'three'];
    var chain = Q();
    items.forEach(function (el) {
      chain = chain.then(foo(el));
    });
    return chain;
    

    however, the following doesn't work with $q:

    var items = ['one', 'two', 'three'];
    var chain = $q();
    items.forEach(function (el) {
      chain = chain.then(foo(el));
    });
    return chain;
    
  • FlavorScape
    FlavorScape about 9 years
    This does not work. It executes all of them simultaneously. I know this because I perform a series of requests that take about 500 MS. watching my network traffic, they all go out concurrently (but in order).
  • FlavorScape
    FlavorScape about 9 years
    The original answer was not syntactically correct. I fixed that. Also, what should initialValue be set to? Like the answer above, this will fire all of them simultaneously.
  • FlavorScape
    FlavorScape about 9 years
    Ah, ok making it a factory makes it so that it does not execute immediately in the call stack when we are building the chain, right?
  • Roamer-1888
    Roamer-1888 about 9 years
    @FlavorScape, good catch. It's good to know there are people out there checking this stuff.
  • Roamer-1888
    Roamer-1888 about 9 years
    initialValue appears as result in the first iteration of the reduce() loop. It's value depends on the application. If doSomethingAsync() does not need the previous result passed to it, then the reduction initializer would simplify to $q.when()
  • Roamer-1888
    Roamer-1888 about 9 years
    And no, the doSomethingAsync() calls are not simultaneous. The reduce(...) process builds a .then() chain immediately, but its execution is sequential in exactly the same way as writing out doSomethingAsync(...).then(...).then(...) longhand.
  • FlavorScape
    FlavorScape about 9 years
    @redgeoff pointed out, making it a factory chains it properly, if you miss that nuance, the reduce executes the functions immediately while building the chain.
  • FlavorScape
    FlavorScape about 9 years
    This looks interesting, I shall try to refactor after writing a million unit tests on my plate for today...
  • Roamer-1888
    Roamer-1888 about 9 years
    @FlavorScape, read the "The Collection Kerfuffle" section here and see if you still think that.
  • pulkitsinghal
    pulkitsinghal almost 9 years
    Could you elaborate on the benefit of angular.bind in this scenario?
  • pulkitsinghal
    pulkitsinghal almost 9 years
    For roughly 7000 items in an array (i know i know background server side job would be better) I ran into net::ERR_INSUFFICIENT_RESOURCES some 2 minutes into it so I wonder why that would happen even when I'm using this to throttle execution to be serial rather than parallel.
  • pulkitsinghal
    pulkitsinghal almost 9 years
    Actually it gets through it all and then pops that up at the very end, I wonder if its just related to chrome debug console not being able to do some of its own introspection magic and not to the app.
  • Roamer-1888
    Roamer-1888 almost 9 years
    @pulkitsinghal, you might like to read my question Building a promise chain recursively in javascript - memory considerations, which actually starts from a slightly different perspective, but attracted two answers that cover the ground pretty well.
  • gleb bahmutov
    gleb bahmutov almost 9 years
    I used bind to create promise-returning functions that do not need any arguments. Thus can they be just used like .then(foo).then(bar)
  • Deniz
    Deniz almost 9 years
    Thanks @Roamer-1888 for a concise solution to this problem.
  • Zbynek
    Zbynek over 7 years
    How would I know when all promises in chain are succesfully resolved? chain.then or $q.all(chain).then seem not to work
  • redgeoff
    redgeoff over 7 years
    @Zbynek, at the very end you could use chain.then(function () { console.log('all resolved'); });
  • supersan
    supersan about 7 years
    This is definitely a much simpler approach because it is easier to read the code later on.
  • user3426603
    user3426603 almost 6 years
    Very elegant !You saved me day :)
  • Tony_Henrich
    Tony_Henrich almost 6 years
    $q.serial is a non existent doc page
  • Claudio
    Claudio almost 6 years
    The link point to some shady website that try to install something.
  • Tony Brasunas
    Tony Brasunas about 5 years
    Does this work if you have in an indeterminate number of promises that need to be resolved sequentially?
  • Matthias
    Matthias about 5 years
    I'm pretty sure you can, I think that's exactly what is happening in redgeoff's answer
  • Tony Brasunas
    Tony Brasunas about 5 years
    yes, it does! I got it to work. I might just be slow, but I think the explanations of the loop on this page aren't as clear as they could be so it took me a while to figure it out. Might make a clarifying answer here if I have the time. But yours was helpful.
  • John Barton
    John Barton over 4 years
    Here is a similar solution in a codepen. Essentially uses the same approach as @redgeoff's, but in a nice reusable function, and with the addition of returning the results of all the promises in an array (like $q.all() does).