How do I sequentially chain promises with angularjs $q?
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 initialresult
isinitialValue
. 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
isfoo
(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; });
Related videos on Youtube
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, 2020Comments
-
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 about 9 yearsThis 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 about 9 yearsThe 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 about 9 yearsAh, 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 about 9 years@FlavorScape, good catch. It's good to know there are people out there checking this stuff.
-
Roamer-1888 about 9 years
initialValue
appears asresult
in the first iteration of thereduce()
loop. It's value depends on the application. IfdoSomethingAsync()
does not need the previous result passed to it, then the reduction initializer would simplify to$q.when()
-
Roamer-1888 about 9 yearsAnd no, the
doSomethingAsync()
calls are not simultaneous. Thereduce(...)
process builds a.then()
chain immediately, but its execution is sequential in exactly the same way as writing outdoSomethingAsync(...).then(...).then(...)
longhand. -
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 about 9 yearsThis looks interesting, I shall try to refactor after writing a million unit tests on my plate for today...
-
Roamer-1888 about 9 years@FlavorScape, read the "The Collection Kerfuffle" section here and see if you still think that.
-
pulkitsinghal almost 9 yearsCould you elaborate on the benefit of
angular.bind
in this scenario? -
pulkitsinghal almost 9 yearsFor 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 almost 9 yearsActually 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 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 almost 9 yearsI 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 almost 9 yearsThanks @Roamer-1888 for a concise solution to this problem.
-
Zbynek over 7 yearsHow would I know when all promises in chain are succesfully resolved?
chain.then
or$q.all(chain).then
seem not to work -
redgeoff over 7 years@Zbynek, at the very end you could use
chain.then(function () { console.log('all resolved'); });
-
supersan about 7 yearsThis is definitely a much simpler approach because it is easier to read the code later on.
-
user3426603 almost 6 yearsVery elegant !You saved me day :)
-
Tony_Henrich almost 6 years$q.serial is a non existent doc page
-
Claudio almost 6 yearsThe link point to some shady website that try to install something.
-
Tony Brasunas about 5 yearsDoes this work if you have in an indeterminate number of promises that need to be resolved sequentially?
-
Matthias about 5 yearsI'm pretty sure you can, I think that's exactly what is happening in redgeoff's answer
-
Tony Brasunas about 5 yearsyes, 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 over 4 yearsHere 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).