본문 바로가기

TIL

Closure의 양면성 ft. 재귀함수 [자바스크립트]

클로저. 함수를 생성할 때 주변 환경과 묶이는 것. 즉 클로저 덕분에 내부 함수가 외부 함수의 스코프에 접근할 수 있게 된다.

 

Closures

A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function’s scope from an inner function. In JavaScript, clos

developer.mozilla.org

위 MDN 글과 아래 블로그 글 등에서 볼 수 있듯이 클로저를 잘 활용하면 코드를 쓸 때 private method를 만들어내는 등 여러 기능을 구현할 수 있다.

 

Master the JavaScript Interview: What is a Closure?

“Master the JavaScript Interview” is a series of posts designed to prepare candidates for common questions they are likely to encounter…

medium.com

 

그러나 반대로 클로저의 효력을 간과해서 코드가 예상과 달리 실행하는 문제가 생길 수도 있다. 그런 경험을 기록해두려 한다.

 

[
  function funcOne(callback) {
    // ..;
    callback();
  }, 
  function funcTwo(itemFromPreviousFunc, callback) {
    // ..
    callback();
  }, 
  function funcThree(itemFromPreviousFunc, callback) {
    // ..
    callback();
  }
]
function finalFunc() {
  //..
  done();
}

함수로 이뤄진 배열이 있다. 이 함수들은 차례대로 다음 함수를 부른다. 배열 속 마지막 함수는 별도 함수를 마지막으로 부른다.

이런 기능을 지닌 함수 waterfall(funcs, finalCallback)을 써보려 했다.

 

일단 코드를 무작정 써보니

funcs[0](function(firstItem) {
    funcs[1](firstItem, function(secondItem) {
        funcs[2](secondItem, function() {
            finalCallback();
        });
    });
});

이런 걸 만들어야 하는 것 같았다. 재귀함수를 사용해야겠군..

 

결과물은 아래처럼 나왔고 정상적으로 작동했다.

function waterfall(funcs, finalCallback) {
  var index = 0;

  funcs[index](callback);    
    
  function callback(itemFromPreviousFunction) {
    index++;
    
    if (index === funcs.length) return finalCallback();
    
    funcs[index](itemFromPreviousFunction, callback);
    }
}

 

그리고 코드를 조금 깔끔하게 바꾸려다가 마침내 문제가 발생했다.

var func = funcs[index];

이 코드를 넣어서 funcs[index]들을 모두 func로 바꿔 가독성을 높이고 싶었다.

function waterfall(funcs, finalCallback) {
  var index = 0;
  var func = funcs[index];

  func(callback);    
    
  function callback(itemFromPreviousFunction) {
    index++;
    
    if (index === funcs.length) return finalCallback();
    
    func(itemFromPreviousFunction, callback);
    }
}

실질적으로 바꾼 건 없다고 생각했는데 느닷없이 오류가 발생했다. 콘솔을 보니 callback is not a function이라는 에러가 떴다.

 

한참을 고민하니 클로저가 문제일 수 있다는 생각이 들었다.

즉 callback()이라는 함수가 생성될 때의 주변 환경을 기억해 index++와 상관없이 func = funcs[0]인 채로 코드가 계속 돌았던 것이다.

 

역시나 func를 내부 함수 안에서 업데이트 해주니 문제가 사라졌다.

function waterfall(funcs, finalCallback) {
  var index = 0;
  var func = funcs[index];

  func(callback);    
    
  function callback(itemFromPreviousFunction) {
    index++;
    func = funcs[index];
    
    if (index === funcs.length) return finalCallback();
    
    func(itemFromPreviousFunction, callback);
    }
}

그러나 func = funcs[index];라는 코드를 반복해서 쓴다는 문제가 새롭게 발생했으니 클로저 때문에 일어나는 문제를 피해가면서도 바라는 기능을 수행할 수 있도록 좀 더 생각을 해봐야겠다.