I am making a series of http requests and I need to file the results into a list object when they return. I'm using angular promises.
Because the promises only resolve after the for loop is finished, they all get filed into the last index of list.
for (var i = 0;i < list.length; i+=1) {
Promise.do(action).then(function(result) {
list[i] //i is always at last index because the for loop has already pleted
}
}
I am making a series of http requests and I need to file the results into a list object when they return. I'm using angular promises.
Because the promises only resolve after the for loop is finished, they all get filed into the last index of list.
for (var i = 0;i < list.length; i+=1) {
Promise.do(action).then(function(result) {
list[i] //i is always at last index because the for loop has already pleted
}
}
Share
Improve this question
edited Aug 15, 2014 at 10:40
Qantas 94 Heavy
16k31 gold badges72 silver badges88 bronze badges
asked Aug 15, 2014 at 10:38
TomTom
1,5652 gold badges22 silver badges45 bronze badges
7
-
3
possible duplicate of the infamous loop issue in case you question is just about the
i
index in the callback, and not about how to get a promise for a list. Use closures! – Bergi Commented Aug 15, 2014 at 11:44 - @Bergi: Technically the problem is due to the closure. Use functions to break closures! – slebetman Commented Aug 15, 2014 at 12:04
- @slebetman: OK, technically: Use an IIFE to give the closure its own parent scope. (without closure, callbacks wouldn't work well) – Bergi Commented Aug 15, 2014 at 12:12
- @Bergi: Callbacks would work perfectly well without closures. I should know - I've been using them in languages without closures for years before I encountered javascript. In fact, the pervalent setTimeout/setInterval design patterns don't need closures at all and we even used to use string callbacks (remember them) which didn't use closures at all and the web worked fine. First class functions and closures are different concepts. There are functional languages out there with first class functions but not closures. In fact the original Lisp didn't have closures (first introduced in Scheme) – slebetman Commented Aug 15, 2014 at 12:44
- 1 Let us continue this discussion in chat. – Bergi Commented Aug 15, 2014 at 13:33
5 Answers
Reset to default 3Bind the index as an argument of the function receiving the result:
for (var i = 0;i < list.length; i+=1) {
Promise.do(action).then((function(index, result) {
list[index]
}).bind(null, i));
}
Don't use a standard loop. Use Array.forEach instead. Every time the function supplied to forEach is called, you'll get a new closure and therefore a new copy of i
I would try to use $q.all
for this:
var promises = [];
for (var i = 0; i < list.length; i += 1) {
promises.push(Promise.do(action));
}
$q.all(promises).then(function(results) {
console.log(results);
});
From the documentation:
Returns a single promise that will be resolved with an array/hash of values, each value corresponding to the promise at the same index/key in the promises array/hash. If any of the promises is resolved with a rejection, this resulting promise will be rejected with the same rejection value.
Agree with Bergi's ment that this looks like the infamous loop issue. However, I typed my answer before noticing Bergi's ment, so here are a couple of proposals for solution:
for (var i = 0;i < list.length; i+=1) {
var callback = function(result) {
list[arguments.callee.i]...
}
callback.i = i;
Promise.do(action).then(callback);
}
OR:
for (var i = 0;i < list.length; i+=1) {
var callback = function(n) {
return function(result) {
list[n]...
};
}(i);
Promise.do(action).then(callback);
}
You can call the next round of the 'loop' within the Promise.then block, like this:
function do_thing(i) {
// Are we finished? If so, return.
if (i == list.length) return;
Promise.do(action).then(function(result) {
list[i]
// Do the next thing, only once the previous thing pleted.
do_thing(i+1)
}
}
// Start at the first thing.
do_thing(0)
The problem with this is that you lose the benefit of having parallel async calls happening - everything is serialised - so it will be slower. With a jQuery promise you could attach the index to the promise object... would this work in angular?
for (var i = 0;i < list.length; i+=1) {
var p = Promise.do(action)
p.index = i
p.then(function(result) {
// this.index is the value of i so do...
list[this.index]
}
}
Here's a fiddle showing that in jQuery flavour: http://jsfiddle/sifriday/0v6vr77y/