Javascript - 콜백함수(Callback function)이란?

들어가기

Callback 함수를 모르지는 않는다. 하지만 잘 안다고 말할수도 없다. 요즘 Node, Vue 등을 공부하면서 callback 함수의 진화형인 Promise, async 등을 접했고 곧 벽에 막혔다. 이 글을 통해 Javascript callback 함수를 제대로 공부하고, 정리하고자 한다.

CallBack 콜백함수란

CallBack 함수란 이름 그대로 나중에 호출되는 함수를 말한다. 콜백함수라고 해서 그 자체로 특별한 선언이나 문법적 특징을 가지고 있지는 않다. 콜백함수도 일반적인 자바스크립트 함수일 뿐이다. 콜백 함수는 코드를 통해 명시적으로 호출하는 함수가 아니라, 개발자는 단지 함수를 동록하기만 하고, 어떤 이벤트가 발생했거나 특정 시점에 도달했을 때 시스템에서 호출하는 함수를 말한다. 즉 콜백함수는 콜백함수라는 유니크한 문법적 특징을 가지고 있는 것이 아니라, 호출방식에 의한 구분이다.

대표적인 콜백 함수의 사용 예로는 자바스크립트에서 이벤트 핸들러 처리이다.

1
2
3
4
5
6
<button id="button1" onclick="button1_click();">버튼1</button>
<script>
function button1_click() {
alert("버튼1을 누르셨습니다.");
}
</script>

Html에 onclick에 button1_click함수는 브라우저의 javascript API에서 DOM 이벤트 핸들러에 전달(등록)되고, 해당 버튼에 클릭이벤트가 발생했을 이벤트 핸들러가 콜백함수를 호출한다.

1
2
3
$( "#target" ).click(function() {
alert( "Handler for .click() called." );
});

위처럼 Jquery에서도 콜백함수를 자주 사용하게 된다.

콜백함수란 특정함수에 전달되어 특정함수가 어떤조건에 의해 호출하는 함수라고 후려칠수 있겠다.


Callback을 활용한 비동기적 프로그래밍

Callback 함수를 사용하는 이유는, 자바스크립트에서 비동기적 프로그래밍을 할수 있기 때문이다. 이 콜백함수기법은 자바스크립트에서 가장 오래된 비동기적 메커니즘이라고 한다.

비동기적 테크닉 : 소중한 싱글스레드의 멈춤을 방지한다. 즉 블록킹을 방지하여 싱글스레드가 논블록킹으로 동작하게 한다.

비동기적 프로그래밍이 필요한 이유는 다음과 같다.

비동기적 테크닉을 사용하는 경우

1.사용자 이벤트 처리

브라우저 화면에서 발생하는 사용자의 이벤트는 예측이 불가능하다. 따라서 이런 화면이벤트를 관리담당하는 녀석에게 우리는 특정이벤트가 발생할 때 호출을 원하는 내용을 callback 함수에 전달하게 된다.

2.네트워크 응답 처리

화면단에서 서버에게 요청을 보냈을 때, 그 응답이 언제 올지 알 수 없다. 따라서 이런 서버에 대한 응답처리 등도 비동기적으로 처리해야 한다.

3.파일을 읽고 쓰는 등의 파일 시스템 작업

4.의도적으로 시간 지연을 사용하는 기능(알람 등)

위와 같이 이벤트 등을 기다리는데 하나뿐인 소중한 스레드를 사용한다면, 또 서버의 응답을 기다리기 위해 하나뿐인 소중한 스레드를 사용한다면... 사용자는 멈춰져 있는 화면을 보게되는 것이다. 위와 같이 스레드의 블록킹을 야기하는 작업은 필수적으로 비동기적 프로그래밍을 해야 한다.


Callback 함수의 비동기적 사용의 예

콜백 함수는 일반적으로 다른 함수에 넘기거나 객체의 프로퍼티로 사용한다. 드물게는 배열에 넣어서 쓸 때도 있다. 항상 그런건 아니지만 보통의 콜백함수는 익명함수로 사용한다.

중요한 점은 콜백함수는 그냥 일반적인 함수이다. 우리는 비동기적 프로그래밍을 하기 위해서는, 비동기적으로 콜백함수를 호출하는 함수에게 비동기적으로 호출되기를 원하는 코드콜백함수에 담아서 전달해야 한다.

1
2
3
4
5
6
7
8
9
10
11
function fn_fakeAsync(callback){
calback();
}

console.log("------- fn_fakeAsync 호출 직전 -------");

fn_fakeAsync(function(){
console.log("이게 비동기적으로 동작하길 바래");
});

console.log("------- fn_fakeAsync 호출 이후 -------");

위 코드 결과

1
2
3
------- fn_fakeAsync 호출 직전 -------
이게 비동기적으로 동작하길 바래
------- fn_fakeAsync 호출 이후 ------

위처럼 단순히 아무 함수에게나 콜백함수를 전달하여 호출시키는 것으로는 비동기적으로 콜백함수를 호출 할수 없다.

다시한번 말하지만 자바스크립트에서 비동기프로그래밍을 하려면 비동기적으로 콜백함수를 호출하는 함수비동기적으로 호출되기를 원하는 콜백함수가 필요하다.

그럼 비동기적으로 콜백함수를 실행하는 시스템 함수들을 알아보자.

setTimeout 함수

setTimeout은 콜백함수의 실행을 지정된 밀리초만큼 지연하는 내장함수이다.

1
2
3
4
5
6
7
8
9
10

function fn_newCallBack(){
console.log("비동기적으로 호출되고 싶다.");
}

console.log("------- 호출 직전 -------");

setTimeout(fn_newCallBack, 3 * 1000); // 3초 뒤 콜백 호출

console.log("------- 호출 이후 -------");

1
2
3
-------  호출 직전 -------
------- 호출 이후 -------
비동기적으로 호출되고 싶다.

3초뒤에 fn_newCallBack함수가 호출되는 것을 확인 할 수 있다. 여기서 이게 무슨 비동기적 프로그래밍인지 갸웃 할수 있다.

동기적 프로그래밍에서 만약 3초뒤에 fn_newCallBack를 호출시키려 한다면, 3초를 자바스크립트의 하나뿐인 메인스레드가 카운팅을 하고 있어야 한다. 즉 프로그래밍이 멈추는 것이다. setTimeout함수는 메인스레드가 할일을 자바스크립트 API에게 위임시킴으로써 메인쓰레드의 멈춤(블록킹)을 피하게 해준다.

다음 예제를 보자.

1
2
3
4
5
6
7
8
9
10

function fn_newCallBack(){
console.log("비동기적으로 호출되고 싶다.");
}

console.log("------- 호출 직전 -------");

setTimeout(fn_newCallBack, 0); // 즉시 실행되지 않을까?

console.log("------- 호출 이후 -------");

1
2
3
-------  호출 직전 -------
------- 호출 이후 -------
비동기적으로 호출되고 싶다.

위 코드를 보면 setTimeout함수에 지연시간을 0으로 설정하여 즉시 실행하게 하였다. 그 결과를 보면 3초 셋팅과 마찬가지로 맨 마지막에 호출되는 것을 확인 할 수 있다. 물론 딜레이 없이 console에 찍히는것은 3초 셋팅과 다르긴 하다. 음 setTimeout에 지연시간을 0으로해도 내장으로 지연시간이 있는 것일까?

그렇다면 다음코드는 어떻까?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function fn_newCallBack(){
console.log("비동기적으로 호출되고 싶다.");
}

console.log("------- 호출 직전 -------");

setTimeout(fn_newCallBack, 0); // 콜백이 언제 동작할지 예측해보자.

console.log("------- 호출 이후 -------");

//무한로프로 콘솔을 찍어낸다.
while(true){
console.log("한국 일교차 너무해...");
}

위 코드를 실행하면 while문의 콘솔이 무한히 찍기고 fn_newCallBack함수는 절대 호출되지 않는다. 이게 무슨 말일까?

javascript의 비동기적 동작을 이 글에서 다 설명하기에는 너무 빡세니, 간단히 설명해보겠다. setTimeout함수에 전달된 콜백함수는 javascript 실행환경의 어느 바구니에 담겨진다. 그러면 javascript API가 setTimeout의 지연시간을 보고 카운팅을 대신 해준다.

음? 그런데 지연시간이 0이네? javascript API는 이 콜백함수를 바구니에 꺼내서, TaskQueue라는 곳에 적재한다. 이 TaskQueue는 현재 동작중인 메인스레드가 일을 다 끝내면, 다음으로 해야할 일들이 있는 Queue이다.

즉 위 코드에서 while의 무한루프로 메인스레드가 자기일을 끝내지 못하기 때문에 TaskQueue에 적재된 콜백함수가 호출되지 못하는 것이다.

여기서 중요한 점은 setTimeout과 같은 비동기적으로 콜백을 호출 시켜주는 함수는, 전달된 콜백함수를 현재 실행중인 싱글스레드에서 뽑아내어 특정 장소에 보관하고 특정 조건을 만족시킬때 호출이 가능하게 한다는 점이다. setTimeout은 시간을 카운팅하는 기능 외에, 콜백함수를 비동기영역으로 넘기는 역할을 하는 것이다.

1
2
3
4
5
6
7
8
9
10
setTimeout(function(){
//나라의 말이 중국과 달라
//문자와 서로 맞지 아니해서
//이러한 까닭으로 어리석은 백성이 말하고자 할 바가 있어도
//마침내 제 뜻을 능히 펴지 못하는 사람이 많으니라
//내 이를 위하여 가엾게 여겨
//새로 스물여떯 자를 만드노니
//사람마다 하여금 쉬이 익혀 날로 쓰며 편안케 하고자 할 다름이니라
}
, 1000 * 5);

setTimeout에 콜백으로 익명함수를 주로 사용하는데 익명함수가 길어질 경우 두번째 인자를 식별하기 어려울 수 있다. 다라서 위처럼 두번째 식별인자는 마지막 행에 분리하는 것이 사용팁이다.

setInterval함수와 clearInterval함수

setInterval도 setTimeout과 같이 콜백을 비동기적으로 호출 가능하게 해주는 함수이다. 차이점은 지정된 시간을 기준으로 반복적으로 콜백을 호출해준다는 것이다.

1
2
3
4
5
6
7
8
9
let i = 0;
const intervalId = setInterval(function(){

if(i === 3) return clearInterval(interbalId);

console.log(`${i++}: 인터벌로 호출 되는 콜백입니다.`);

}
, 5 * 1000);

setInterval 실행결과

위 코드에서 console.log 내부에 문자열을 감싼 것은 싱글쿼터가 아니라 백틱(키보드 esc 아래에 있음)이다. Es2015 부터 백틱을 사용하여 위 처럼 템플릿 문자열을 사용 할 수 있다.

setInterval의 기능은 단순하다 중요한 것은 고유의 intervalId를 리턴하고 이것을 clearInterval 함수에 넘겨 해당 setInterval을 중지시킬수 있다. 특시 Node등 서버단에서 setInterval를 사용할 경우 반드시 clearInterval로 인터벌을 정지시키지 않을경우 서버 메모리 누수가 발생할 수도 있으니 주의해야 한다.

지금까지 setTimeout, setInterval, clearInterval등은 모두 전역 객체(브라우저에서는 Window, 노드에서는 global)에 정의 되어 있으므로 어디서나 사용할 수 있다.

마무리

setTimeout 등을 이용하여 javascript의 콜백함수의 비동기적 용법에 대해 간단히 알아보았다. 콜백을 사용할때 주의할점과, 문제점등 정리할 것이 많이 있지만 글이 너무 길어지는 것 같아 줄인다. 다음 글에서 나머지 것들을 정리해 보겠다.

Related Posts

자바스크립트 동기, 비동기 프로그래밍