3

I have been trying to wrap my head around the issue I have but without success therefore I am asking for your help.

Let's consider following example:

const async = require('async')
var counter = 0

var test = cb => {
  if (++counter < 3) {
    setTimeout( () => {
      async.each([0,1,2], (item, callback) => {
        console.log(item)
        console.log('counter inner ' + counter)
        test(cb)
        callback()
      }, () => {
          cb()
       })
    })
  } else {
      console.log('counter ' + counter)
  } 
}

var prom = new Promise( (res, rej) => test( () => res('done') ) )
prom.then(res => console.log(res))

The output is:

0
counter inner 1
1
counter inner 2
counter 3
2
counter inner 3
counter 4
done
0
counter inner 4
counter 5 
1 
counter inner 5
counter 6
2
counter inner 6
counter 7

What I do not understand is how it's printed counter inner 4 right after the second 0. Shouldn't be printed counter inner 1? And a promise is being resolved only once? What happens with the second resolve call? Thank you.

scarably
  • 333
  • 1
  • 4
  • 11
  • Possible duplicate of [Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference](https://stackoverflow.com/questions/23667086/why-is-my-variable-unaltered-after-i-modify-it-inside-of-a-function-asynchron) – Liam Aug 17 '17 at 12:48
  • *[One word answer: asynchronicity.](https://stackoverflow.com/a/23667087/542251)* – Liam Aug 17 '17 at 12:48

1 Answers1

0

Kind of difficult to explain it step by step, but I will try backwards - meaning from your output I will try to mention which line in your code outputed it and when.

// obviously starting inside the callback async runs for each of your 3 items

    0 // console.log(item);
    counter inner 1 // console.log('counter inner ' + counter);

    1 // console.log(item);
    counter inner 2 // console.log('counter inner ' + counter);
    counter 3 // let's not forget you also call callback() for each item 
             // which evaluates this condition if (++counter < 3) and starting from here,
             // it will be true and will run your else statement
             // console.log('counter ' + counter);

    2 // console.log(item);
    counter inner 3 // console.log('counter inner ' + counter);
    counter 4 // console.log('counter ' + counter);

// at this point your promise is already resolved (and yes, only once)
    done // prom.then(res => console.log(res))

// Now you are probably questioning where the following output comes from
// remember this condition? if (++counter < 3)
// before starting to evaluate to false and printing to console, once it
// evaluated to true so setTimeout was called with your async callback
// which scheduled it to run on the next "tick"*, which is right after the
// promised was resolved

    0
    counter inner 4
    counter 5 
    1 
    counter inner 5
    counter 6
    2
    counter inner 6
    counter 7

This is the effect that setTimeout() with a timeout of 0 does. It is like a thread/process yield in C. Although it seems to say "run this immediately" it actually re-queues the new javaScript code at the end of the execution queue.

According to setTimeout documentation, its second parameter is the following:

The time, in milliseconds (thousandths of a second), the timer should wait before the specified function or code is executed. If this parameter is omitted, a value of 0 is used, meaning execute "immediately", or more accurately, as soon as possible. Note that in either case, the actual delay may be longer than intended;

And here you can read more about reasons for delays longer than specified.

You can easily verify what I just explained by changing your if condition to if(++counter < 2). Since it will never evaluate to true, you will see that the console ouput stops at "done".

Sotiris Kiritsis
  • 3,178
  • 3
  • 23
  • 31