콜백 함수에 대해서

이명범·2022년 3월 9일
0
post-thumbnail

0. 개요

자바스크립트에는 콜백 함수(Callback Function)가 있다. 이름 그대로 나중에 호출되는 함수이다. 개발자가 등록해놓은 어떤 이벤트가 발생하면 그때서야 실행되는 함수이다. 그리고 이러한 이벤트들은 보통 비동기(Asynchronous) 방식으로 실행되는 경우가 많다.

그런데 의문점이 들었다. 어디에선가 자바스크립트는 싱글 쓰레드 방식을 채택하고 있다는 말을 들었다. 그런데 어떻게 이벤트를 비동기 방식으로 구현한다는거지? 그리고 어떻게 쓰레드가 콜백 함수가 있는 곳으로 돌아와 실행시킬 수 있는가에 대한 부분이 이해가 되지 않았다.

그래서 오늘은 그에 대해서 한번 알아보고자 한다!!!!


1. 자바스크립트에서의 비동기

우선 기본적으로 자바스크립트는 싱글 스레드로 동작하는 동기 방식 언어이다.

<script>
  	for (let index = 0; index < 10000; index++) {
    	console.log(1);
  	}
	console.log(2)
</script>

위 코드는 자바스크립트가 싱글 스레드로 위에서 내려오면서 실행한다는 것을 보여주기 위한 코드이다.
콘솔 1

위의 사진을 보면 크롬의 콘솔에 1을 10000번 찍은 후 2가 출력된 것을 알 수 있다. 자바스크립트는 메모리도 자바의 JVM처럼 스택에 쌓는 방식으로 함수 처리를 한다.

자바 스크립트 V8 엔진의 구성 요소

어떤 이벤트가 완전히 실행되는데 30초 이상이 걸린다고 가정해보자. 동기(Synchronous) 방식의 경우 이 이벤트가 끝나기 전에는 사용자가 다른 동작을 할 수 없게 된다.

예를 들면 처리하는 데 오랜 시간이 걸리는 복잡한 이미지 변환 메서드를 수행한다고 가정해보자. 이 때 스택에서 복잡한 메서드를 수행하는 동안 브라우저는 어떠한 다른 일도 진행할 수 없다. 화면을 렌더링할 수도 없고 또 다른 이벤트도 진행할 수도 없고 그냥 멈춰있다는 뜻이다.

이러한 점은 사용성 면에서 매우 불편한다. 그래서 사용자는 웹 페이지가 비동기(Asynchronous) 방식으로 실행되면 좋을 것 같다고 생각한다. 그리고 실제로 javascript는 비동기 방식으로 실행이 가능하다!

그런데 기본적으로 자바스크립트 엔진은 싱글 스레드에 스택을 이용해 함수를 호출하기 때문에 자바스크립트만으로는 여러 이벤트를 동시에 동작하도록 만들기는 어렵다.


2. Web API

여기서 등장하는게 Web API이다. Web API는 자바스크립트가 실행될 때 웹 브라우저에서 제공하는 별도의 API이다. ajax를 위한 fetch나 setInterval과 같은 비동기 방식 메서드 지원을 위한 인터페이스를 제공한다.

그럼 여기서 Web API는 어떻게 비동기 방식을 지원할까? 자바스크립트 엔진의 Call Stack에 Web API의 메서드가 들어오면 스택에서 바로 빠지고 Web API에서 따로 실행시키게 된다. 따로 실행시킨 메서드의 작업이 끝나면 메서드가 가지고 있던 콜백 함수를 다시 Call Stack에 넣어주고, 자바스크립트의 쓰레드가 Call Stack의 콜백 함수를 처리하도록 만들어주면 개발자의 입장에서 보았을 때 마치 자바스크립트가 비동기 방식으로 작동하는 것처럼 보이는 것이다!!

그런데 또 한 가지 문제점이 있다. Web API가 Call Stack에다가 무차별로 콜백 함수를 막 넣어버리면 자바스크립트가 작업하고 있던 메서드에 문제가 생길 가능성이 생긴다. 그래서 여기에도 또한 규칙이 있다.


3. Event Loop

웹 브라우저는 Callback Queue(혹은 Message Queue, 이름 부르는 방식이 다양한 듯)라는 메모리가 존재한다. 여기에는 Web API가 작업한 이후 Call Stack에 다시 넣어주기 위한 콜백 함수가 보관되는 영역이다.

그리고 중요한 Event Loop라는 키워드가 있다. 이것은 Call Stack과 Callback Queue의 상태를 수시로 체크한다. 그리고 자바스크립트 엔진이 Call Stack에 있는 작업을 완료하고 스택이 비어있으면 그 때 Callback Queue에 들어있는 콜백 함수를 순차적으로 Call Stack으로 밀어넣는다. 이러한 반복적인 행동을 틱(tick)이라고 부른다.

그럼 setInterval() 메서드를 활용한 코드를 통해 웹 브라우저가 어떤 방식으로 비동기 방식을 지원하는지 알아보자.

<script>
	setInterval(function() {
  		console.log(2)
	}, 1000)
	console.log(1);
</script>

실행 순서(V8 엔진 시점)

  1. 먼저 setInterval() 메서드를 Call Stack에 push한다.
  2. setInterval() 메서드의 경우 Web API에서 제공하는 메서드이므로 Call Stack에서 빼낸 후 Web API를 호출한다.
  3. console.log(1)을 Call Stack에 push한다.
  4. Call Stack에서 빼내면서 메서드를 실행해 웹 브라우저의 console에 1을 출력한다.
  5. Callback Queue에서 받은 콜백 함수를 Call Stack에 push한다.
  6. 콜백 함수 내부에 console.log(2)를 Call Stack에 push한다.
  7. Call Stack에서 console.log(2)를 빼내면서 웹 브라우저의 console에 2를 출력한다.
  8. 콜백 함수도 Call Stack에서 빼낸다.

실행 순서(Web API 시점)

  1. V8 엔진으로부터 setInterval 메서드 실행을 요청받는다.
  2. 실행 후 콜백 함수를 Callback Queue에 밀어넣는다.
  3. Event Loop가 Call Stack이 비었는지 확인한다.
  4. Call Stack이 비면 콜백 함수를 Call Stack에 push한다.


4. 고찰

비동기라는 개념에 대해서는 많이 들어보았지만, 항상 자바와 같은 동기 방식 프로그래밍에 익숙하다보니 이 개념을 어떻게 구현하였는지에 대해서는 잘 모르고 있었다. 단순히 멀티 쓰레드를 이용해서 구현하였을 것이라고 생각했는데 생각보다는 조금 더 복잡한 방법을 사용하고 있었고 굉장히 신기했다.

다만 V8 엔진이 정확히 무엇인지, Web API는 어떻게 V8 엔진의 스택에서 메서드를 가져오는지 등 디테일한 부분에 의문점이 풀리지는 않았기 때문에 나중에 추가로 공부해야 할 필요가 있을 것 같다.


참고 자료

How JavaScript works - Alexander Zlatkov

How JavaScript works - Bipin Rajbhar

Event Loop (이벤트 루프)

자바스크립트 - 동기? 비동기?

profile
백엔드 개발자가 될거야

0개의 댓글