Reputation: 9715
const simplePromise = i => {
return new Promise(function(resolve, reject) {
console.log(i);
setTimeout(function(){
resolve();
}, 2000);
});
}
var anchor = simplePromise(0);
for (var i=1; i<4; i++) {
anchor = anchor.then(_ => simplePromise(i));
}
prints:
0
4
4
4
4
instead of:
0
1
2
3
4
1. Can someone explain why? and 2. tell me how to achieve this?
I can see that the first promise is executed (i=0), then the loop runs and then the value of i(=4) gets passed to the next promise. Shouldn't this be solved by having a function inside then (_ => simplePromise(i)) ?
Upvotes: 1
Views: 63
Reputation: 469
It's happened due you use var
. Try to change var
to let
and that fix your problem.
UPDATE
That problem more clearly explained at this article and this (scroll to section Difference Details -> Closure in Loop
) and great explanation of let
key word
EXPLANATION
Let take that piece of code:
for (var i = 0; i < 5; ++i) {
setTimeout(function () {
console.log(i); // output '5' 5 times
}, 100);
}
In that example each iteration create function
with closure on variable i
, which will be executed in the future. Problem is var
declare variable which
...is scoped to the nearest function block and let is scoped to the nearest enclosing block, which can be smaller than a function block.
i.e. all of the created functions will create closure to the same variable. And when the execution time comes i === 5
. And All of the function will print the same value.
How let
solve that problem...
let
in the loop can re-binds it to each iteration of the loop, making sure to re-assign it the value from the end of the previous loop iteration, so it can be used to avoid issue with closures.
Upvotes: 3
Reputation: 338158
Your mistake is one of most common mistakes in JS - if not the most common - using a for
loop to manipulate a state variable in an asynchronous situation. Your promises and your loop do not run in sync. The loop finishes much faster than any of your promises do.
Yet you use i
in your promise callback, which only ever runs after the loop is done. Don't do that. There are ways to prevent it, but this has been discussed so often that I will only suggest to research and read a few of the existing answers.
I strongly suspect that you do not even want to loop over a fixed range of numbers. You actually have an array of items.
Simply drop the for
loop and use the array iteration tools to dodge the scoping problem. Array#reduce
is the perfect candidate here.
const simplePromise = val => {
return new Promise(function(resolve, reject) {
setTimeout(function(){
console.log(val);
resolve(val);
}, 200);
});
}
var items = [0,1,2,3,4];
console.log("array iteration starts");
items
.reduce((acc, i) => acc.then(_ => simplePromise(i)), Promise.resolve())
.then(val => console.log("chain execution end w/ " + val));
console.log("array iteration done");
/*
acc = Promise.resolve()
then simplePromise(0)
then simplePromise(1)
then simplePromise(2)
then simplePromise(3)
then simplePromise(4)
then console.log("...")
*/
Upvotes: 1