Node.js에 익숙하지 않을 때 자주 하는 실수 중 하나입니다.


예제로 설명하도록 하겠습니다.


for (var i = 0; i < 3; i++) {
  console.log(i)
}


위의 코드를 node로 실행하면 결과는 너무나도 쉽게 예상하듯이 아래와 같이 나올 것입니다.

0
1
2



하지만 아래의 코드를 실행하면 결과가 위와 같을까요?

var reserve = function(cb) {
  process.nextTick(function() {
    cb();
  });
}


for (var i = 0; i < 3; i++) {
  reserve(function() {
    console.log(i);
  });
}


그렇지 않습니다. 결과는 아래와 같습니다.

3
3
3



좀 더 헷깔리게 만들어보겠습니다. 아래의 코드는 결과가 어떻게 될까요?

var reserve = function(cb) {
  process.nextTick(function() {
    cb();
  });
}

var makeReserve = function(index) {
  reserve(function() {
    console.log(index);
  });
}

for (var i = 0; i < 3; i++) {
  makeReserve(i);
}


결과는 아래와 같습니다.

0
1
2



예제1은 별도의 설명이 필요없을 것 같습니다.


예제2의 결과에 대한 설명은 아래와 같습니다.


reserve 함수는 node.js에서 일반적으로 많이 사용하는 비동기 콜백 방식의 구조입니다.

예제2의 코드를 C개발자가 좀 더 이해하기 편하도록 바꾸면 아래와 같습니다.

var reserve = function(cb) {
  process.nextTick(function() {
    cb();
  });
}

var printi = function() {
    console.log(i);
}

for (var i = 0; i < 3; i++) {
  reserve(printi);
}


즉, for 반복문에서 하는 것은 reserve 함수에 printi 함수를 인자로 넣고 실행하는 것만 합니다.

실제 reserve 함수의 내용에는 다음 tick에 실행할 내용만 정하고 연산은 하지 않습니다.

그렇기 때문에 printi는 for 반복문이 다 완료되고 다음 tick에서 실행되게 됩니다.

그 시점에 i는 이미 for 반복문을 종료했기 때문에 3이라는 값을 가지고 있고, 따라서 3이 출력이 되게 되는 것입니다.


이것은 i라는 변수의 scope와도 관련이 깊습니다.


예제3을 보면 예제2와 비슷하지만 makeReserve라는 함수를 만들어서 i대신에 index라는 지역변수로 받아서 처리하고 있습니다.


예제2의 경우에는 printi가 실행되는 3번 모두 i의 주소값은 모두 같지만,

예제3의 경우에는 makeReserve가 실행되는 3번 모두 index의 주소값은 다릅니다.


그렇기 때문에 결과3처럼 0,1,2라는 결과를 출력할 수 있습니다.




이와 같이 node.js와 같은 비동기 콜백 방식의 언어를 사용할 때는  사용하는 변수의 life cycle과 scope에 대해서 주의를 하고 사용해야합니다.

그리고 콜백함수등을 작성할 때는 이 코드가 실행되는 시점이 언제인지 분명히 해두지 않으면 원하지 않는 결과를 받기 쉽습니다.