开发者

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 s开发者_运维问答imple 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


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.


Although you usually use bind to ensure that the value of this inside a callback function is what you want it to be, you can also use it to modify the arguments passed to the bound function. In your case, the code could look something like this (I changed some variable names so it was clearer what's going on):

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

In this case, the function has .bind(this, i) attached to it. The this is just for padding; the first argument to bind is always what you want this to resolve to when inside the bound function. After that, however, we can pass in any values that we want to be added to the arguments of the bound function--in this case, we want the current value of i (when the function is bound) to be passed to the function.

You can read more about bind at the MDN Docs.


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

Functions called from setTimeout will not be called with any parameter. You need to create a closure.

Check out this question and the accepted answer.

(This is not a node.js-specific problem)

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜