1
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)) ?

kev
  • 8,928
  • 14
  • 61
  • 103
  • 1
    Your `for` loop runs to completion. THEN, the timers fire and the `.then()` handlers run. So, all the `.then()` handlers run AFTER the `for` loop has completed and thus `i` is at its terminal value. You could use `let i` instead of `var i` and it would probably fix the output, but I suspect that's not really the main issue here. You need to understand that promises and timers are not blocking so the `for` loop runs first and schedules everything else to then run later. – jfriend00 Nov 13 '17 at 06:52
  • @jfriend00 you've basically repeated my last paragraph. – kev Nov 13 '17 at 06:55
  • 1
    Well, apparently you don't understand it though because it's not solved with `then (_ => simplePromise(i))`. That makes sure that `i` is evaluated LATER when the `.then()` handler is called when it has the terminal value and NOT when the `for` loop is running each iteration. That's what I'm trying to explain to you. Remember, the `.then()` callback is called sometime later after the promise resolves. It's not called while your `for` loop is iterating. That means it's called after the `for` is done. That fully explains the output you see. I'm trying to explain that to you. – jfriend00 Nov 13 '17 at 07:10

2 Answers2

3

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.

RQman
  • 431
  • 1
  • 5
  • 20
1

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("...")
*/
Tomalak
  • 332,285
  • 67
  • 532
  • 628
  • What does `acc` stand for? What if I have more parameters that I would like to pass into simplePromise(..)? Do they have to be part of the `items` array or can I define them before the `Array.reduce`? E.g. a `total` for debugging output a la "processing item 4/15". – kev Nov 13 '17 at 07:53
  • `acc` stands for accumumator. Please read the [documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce). I would make anything I wanted to pass to `simplePromise` part of the input array. I'm sure you can figure out where to put the debugging output yourself. – Tomalak Nov 13 '17 at 08:12