콜백 패턴


함수는 객체다. 즉 함수를 다른 함수에 인자로 전달할 수 있다. introduceBugs() 함수를 writeCode() 함수의 인자로 전달하면, 아마도 writeCode()는 어느 시점에 introduceBugs()를 실행(또는 호출)할 것이다. 이때 introduceBugs()를 콜백함수 또는 간단하게 콜백이라고 부른다.

function writeCode(callback){
// 어떤작업을 수행한다.
callback();
// ...
}

function introduceBugs(){
console.log('bug');
}

writeCode(introduceBugs); // bug


introduceBugs()가 writeCode()의 인자로 괄호없이 전달된 사실을 눈여겨 보자. 괄호를 붙히면 함수가 실행되는데 이 경우에는 함수의 참조만 전달하고 실행은 추후 적절한 시점에 writeCode()가 해줄 것이기 때문에 괄호를 덧붙이지 않았다.



콜백 예제


아래 함수는 findNodes()와 같은 형식으로 호출되며 DOM 트리를 탐색해 필요한 엘리먼트의 배열을 반환한다.

var findNodes = function(callback){
var i = 100000, // 긴루프
nodes = [], // 결과를 저장할 배열
found; // 노드 탐색 결과

// 콜백 함수를 호출할 수 있는지 확인한다.
if(typeof callback !== "function"){
callback = false;
}

while(i){
i -= 1;
// 이 부분에 복잡한 로직이 들어간다.

// 여기서 콜백을 실행
if(callback){
callback(found)
}
nodes.push(found);
}
return nodes;
};

var hide = function(node){
nodes[i].style.display = "node";
}

// 노드를 찾아서 바로 숨긴다.
findNodes(hide);


이미 존재하는 함수를 콜백으로 함수로 쓸 수도 있지만 findNodes() 함수를 호출할 때 익명 함수를 생성해서 쓸 수도 있다.


// 익명함수를 콜백으로 전달한다.
findNodes(function(node){
node.style.display = "block";
})



콜백과 유효범위


만약 콜백 메서드가 자신이 속해있는 객체를 참조하기 위해 this를 사용하면 예상치 않게 동작할 수도 있다.
myapp이라는 객체의 메서드인 paint()함수를 콜백으로 사용한다고 가정해보자.

var myapp = {};
myapp.color = 'green';
myapp.paint = function(node){
node.style.color = this.color;
};

// findNodes() 함수는 이런 식으로 동작한다.

var findNodes = function(callback){
// ...
if(typeof callback === "function"){
callback(found)
}
}


findNodes(myapp.paint)를 호출하면 this.color가 정의되지 않아 예상대로 동작하지 않는다. findNodes()가 전역 함수이기 때문에 객체 this는 전역 객체를 참조한다. findNodes()가 (dom.findNodes()처럼) dom 이라는 객체의 메서드라면 콜백 내부의 this는 예상과는 달리 myapp이 아닌 dom을 참조하게 된다.


이 문제를 해결 하기 위해서는 콜백함수와 함께 콜백이 있는 객체를 전달하면 된다.


findNodes(myapp.paint, myapp);



var findNodes = function(callback, callback_obj){
// ...
if(typeof callback === "function"){
callback.call(callback_obj, found)
}
}


다음과 같이 바꿀수도 있다. 메서드와 객체를 전달할 때 메서드를 문자열로 전달하게 되면 객체를 두번 반복하지 않아도 된다.


findNodes("paint", myapp);

var findNodes = function(callback, callback_obj){
// ...
if(typeof callback === "string"){
callback = callback_obj[callback];
}

// ...
if(typeof callback === "function"){
callback.call(callback_obj, found)
}
}



비동기 이벤트 핸들러


페이지의 엘리먼트에 이벤트 리스너를 붙이는것도 콜백이 발생하는 것이므로 document 이벤트 리스너에 콜백을 전달 할 수 있다.

document.addEventListener("onload", console.log('resdy'), false);



타임아웃


setTimeout()과 setInterval()도 콜백 함수를 받아서 실행 한다.

var thePlotThickens = function(){
console.log('500ms later...');
};
setTimeout(thePlotThickens, 500);


함수의 name 프로퍼티


함수를 정의하는 패턴을 선택할 때는 읽기 전용인 name 프로퍼티를 쓸 일이 있는지도 고려해보아야 한다. name 프로퍼티는 표준이 아니지만 많은 실행 환경에서 사용이 가능하다.

function foo(){} // 함수 선언문
var bar = function(){} // 함수 표현식
var baz = function baz(){} // 기명 함수 표현식

console.log(foo.name); // foo
console.log(bar.name); // ""
console.log(baz.name); // baz



함수 호이스팅


모든 변수는 함수 본문 어느 부분에서 선언되더라도 내부적으로 함수의 맨 윗부분으로 끌어올려 진다. 함수 또한 결국 변수에 할당되는 객체이기 때문에 동일한 방식이 적용된다. 함수 선언문을 사용하면 변수 선언뿐 아니라 함수 정의 자체도 호이스팅되기 대문에 자칫 오류를 만들어내기 쉽다.

// 안티패턴이다.
// 전역 함수
function foo(){
console.log("global foo");
}

function bar(){
console.log("global bar");
}

function hoistMe(){

console.log(typeof foo); //function
console.log(typeof bar); // undefined

foo(); //local foo"
bar(); // type error : bar is not a function

// 함수 선언문;
// 변수 'foo'와 정의된 함수 모두 호이스팅 된다.
function foo(){
console.log('local foo');
}

// 함수 표현식
// 변수 'bar' 는 호이스팅 되지만 정의된 함수는 호이스팅되지 않는다.
var bar = function(){
console.log('local bar');
}
}
hoistMe();


보다시피, hoistMe() 함수 내에서 foo와 bar를 정의하면, 실제 변수를 정의한 위치와 상관없이 끌어올려져 전역변수인 foo와 bar를 덮어쓰게 된다. 그런데 지역변수 foo()는 나중에 정의되어도 상단으로 호이스팅되어 정상 동작하는 반면, bar()의 정의는 호이스팅 되지 않고 선언문만 호이스팅 된다. 때문에 bar()의 정의는 호이스팅 되지 않고 선언문만 호이스팅된다. 때문에 bar()의 정의가 나오기 전까지는 undefined 상태이고, 따라서 함수로 사용할 수도 없다. 또한 선언문 자체는 호이스팅 되었기 때문에 유효범위 체인 내에서 전역 bar()도 보이지 않는다.

에러 객체


자바스크립트에는 Error(), SyntaxError(), TypeError() 등 여러 가지 에러 생성자가 내장되어 있으며 throw 문과 함께 사용된다.
이 생성자들을 통해 생성된 에러 객체들은 다음과 같은 프로퍼티를 가진다.

name
- 객체를 생성한 생성자 함수의 name 프로퍼티. 범용적인 'Error' 일 수도 있고 'RangError'와 같이 좀더 특화된 생성자일 수도 있다.

message
- 객체를 생성할 때 생성자에 전달된 문자열

throw문은 어떤 객체와도 함께 사용할 수 있다. 이 객체는 'name', 'message' 외에도 임의의 프로퍼티를 가질수 있기 때문에 catch문에서 처리할 정보를 담아 전달하면된다.

var num = 2;

try{
// 에러를 발생시킨다.
if(num === 2){
throw{
name : "MyErrorType", // 임의의 에러타입
message : "user message",
extra : "extra message",
remedy : genericErrorHandler // 에러 처리 함수
};
}
}catch(e){
// 사용자에게 공지
console.log(e.name);
console.log(e.message);
console.log(e.extra);

// 에러처리 함수 호출
e.remedy();
}
function genericErrorHandler(){
console.log('genericErrorHandler 입니다.');
}

/*
'MyErrorType'
'user message'
'extra message'
'genericErrorHandler 입니다.'
*/



아래와 같이 Error type 별로 세분화 할 수 있다.

try {
myroutine(); // may throw three types of exceptions
} catch (e) {
if (e instanceof TypeError) {
// statements to handle TypeError exceptions
} else if (e instanceof RangeError) {
// statements to handle RangeError exceptions
} else if (e instanceof EvalError) {
// statements to handle EvalError exceptions
} else {
// statements to handle any unspecified exceptions
logMyErrors(e); // pass exception object to error handler
}
}


+ Recent posts