Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Async calls with async response in NodeJS

I'm building my first project with NodeJS in these days but I'm a bit confused on a task I believe it's a simple one, I guess the problem is my lack of knowledge of these async approach but I cannot find the answer anywhere.

I've a simple loop iterating over an array and for any element, based on some rule, I'll call a function or another. Now some operation will be faster than others so I could end up with a function on element N returning sooner than the function on element N-1. To make it simple something like this

for (var i = 0 ; i < 10 ; i++) {
      if (i%2 === 0) {
          setTimeout(function(i) {
               console.log(i);
          }, 2000);
      }
      else { console.log(i); }
}

so any even number will be printed with 2 seconds lag while the odd numbers will be printed immediately. Anyway running it I get

1
3
5
7
9

<<2 seconds break>>

undefined
undefined
undefined
undefined
undefined

looks like the even value is "lost". How can I pass the value making sure the function won't lose the input value? Am I missing something?

Thanks, Mauro

like image 842
Mauro Avatar asked Dec 13 '22 10:12

Mauro


1 Answers

Your setTimeout argument function is declared to take a single parameter, i. When setTimeout calls the function with no arguments, as it does, the parameter is thus set to undefined.

This would seem to be be slightly better, as it no longer shadows the outer i variable you were originally trying to reference...

setTimeout(function() {
  console.log(i);
}, 2000)

...but if you run it, you'll find it prints 10 5 times, because every function you create is referencing the same i variable, whose value will be 10 when the loop exit condition becomes true and it terminates.

Creating a closure which holds the value of i as it was during the loop in which each setTimout argument function was created will do the trick:

setTimeout((function(i) {
  return function() {
    console.log(i);
  }
})(i), 2000)

Renaming the argument to the Immediately-Invoked Function Expression we just used might help make things clearer:

setTimeout((function(loopIndex) {
  return function() {
    console.log(loopIndex);
  }
})(i), 2000)

We're:

  • Creating a function which takes a single argument and returns another function.
  • Immediately calling the "outer" function, passing it the current value of i (which is not an object, so is effectively passed by value).
  • The "outer" function returns the "inner" function, which is passed as the argument to setTimeout.

This works because:

  1. Creating a function in JavaScript creates a new scope to hold the function's argument variables and any other variables declared within it using the var keyword. Think of this like an invisible object with properties corresponding to the variable names.

  2. All functions hold a reference to the scope in which they were defined, as part of their scope chain. They still have access to this scope even if it's no longer "active" (e.g. when the function it was created for returns). The "inner" function was created in a scope which contained a loopIndex variable, set to the value of i at the time the IIFE was called. So when you try to reference a variable named loopIndex from inside the inner function, it first checks its own scope (and doesn't find a loopIndex there) then starts walking up its scope chain - first, it checks the scope in which it was defined, which does contain a loopIndex variable, the value of which is passed to console.log().

That's all a closure is - a function which has access to the scope in which it was defined, even if the function the scope was created for has finished executing.

like image 191
Jonny Buchanan Avatar answered Dec 14 '22 23:12

Jonny Buchanan