kev
kev

Reputation: 9715

Promise flattening fails - why?

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

Answers (2)

RQman
RQman

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

Tomalak
Tomalak

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

Related Questions