진짜 ! 쉽게 알아보는 자바스크립트 동작 원리

너무나도 어려운 비동기 동작 뿌시기

toy-crane
10 min readDec 31, 2020
Photo by Irvan Smith on Unsplash

Javascript를 처음 접하는 분들이 가장 어려워 하는 부분이 비동기 동작입니다. 그래서 오늘은 Javascript가 어떻게 비동기 기반으로 작동하고, 원리는 무엇인지 간단하게 알아보도록 하겠습니다.

자바스크립트는 머리가 하나 ! (싱글 스레드)

자바스크립트는 싱글 쓰레드 언어입니다. 이 의미는 쉽게 풀어쓰면 한 번에 한 가지 일 밖에 처리할 수 없다는 것을 의미합니다. (어려운 용어로는 Call stack이 하나라고 표현 합니다)

현실 세계에 비유해서 이야기 하면 멀티 태스킹 안되는 글쓴이와 같습니다.

글쓴이는 하나의 일을 할 때, 다른 일을 동시에 하지 못합니다. 예를 들어, 게임을 하면 대화를 듣지 못하고, 대화를 하면 게임에 집중 못하는 그런 부류의 사람인거죠.

즉, 현재 실행하고 있는 함수가 있는 경우에 다른 일을 할 수가 없고, 다른 일들이 블락되게 됩니다. 이렇게 되면 브라우저에서 오래 걸리는 작업이 실행될 경우, 웹 페이지의 UI는 멈춰버리고, 사용자는 어떠한 행동도 할 수 없게 됩니다.

예를 들어, 크롬에서 alert 창을 띄어 보면 alert 창을 닫기 전까지는 아무것도 할 수 없는 것과 마찬가지 입니다.

1분 이상이 시간이 소요되는 오래 걸리는 작업이 있다고 가정 해보겠습니다. 자바스크립트는 머리가 하나이기 때문에, 1분 동안 아무것도 안하고 기다리면 브라우저는 아무런 행동(화면 스크롤, 버튼 클릭…)도 하지 못하게 됩니다. 그렇다면 어떻게 해야할까요? 이 때 사용하는 것이 바로 비동기 콜백입니다.

비동기 콜백을 이해하기 위해서는 JS Engineweb API 그리고 이벤트 루프에 대한 이해가 먼저 필요합니다.

자바스크립트 코드를 실행 시켜 줘 (JS Engine)

자바스크립트 엔진은 Javascript 코드를 이해하고 실행을 도와주는 녀석입니다. 대표적인 JS engine으로 V8엔진(Chrome, Node.js에서 사용)이 있으며, 이외에도 각 브라우저 별로 여러가지 엔진들이 존재합니다. 자바스크립트 엔진은 크게 Memory HeapCall Stack으로 이루어져 있습니다.

Memory Heap

데이터를 임시 저장하는 곳으로, 함수나 변수, 함수를 실행할 때 사용하는 값들을 저장합니다.

Call Stack

코드가 실행되면 코드의 내부의 실행 순서를 기록해 놓고, 하나씩 순차적으로 진행할 수 있도록 도와주는 곳입니다.

예를 들어 위의 함수를 실행하면 아래와 같은 Call Stack이 차례대로 생기게 됩니다.

실제로 코드를 실행했을 때, 다음에 실행되어야 할 코드를 순서대로 기록을 하며, 순차적으로 코드를 실행 할 수 있게 도와 줍니다.

Call Stack이 호출되는 과정이 좀 더 궁금 하시다면 사이트(링크)에서 테스트 해보실 수 있습니다. 그런데 만약에 Call Stack에 들어간 코드 중 일부가 시간이 오래 걸린다면 어떻게 될까요?

노랑색으로 칠해진 코드가 어떠한 이유로 시간이 오래걸리게 된다면 당연히 다음에 호출되어야 코드는 실행에 시간이 걸릴 수 밖에 없습니다. 이와 같은 상황이 서두에서 이야기했던 특정 코드가 오래 걸려 다른 코드를 실행하지 못하게 되어 block이 된 상태입니다.

마냥 오래 걸리는 일을 지켜볼 순 없으니, 어떻게 해야 할까요?

이 때 효과적으로 event를 관리하기 위해 필요한 것이 바로 web APICallback Queue, event loop입니다.

능률적으로 일하기 (web API, Callback Queue, event loop)

https://blog.sessionstack.com/how-does-javascript-actually-work-part-1-b0bacc073cf

라면을 끓일 때를 생각해보겠습니다.

라면의 조리 과정은 물을 올리고, 라면을 넣고, 파나 계란을 넣는 순서로 진행됩니다. 이 때, 우리가 물을 끓이면 끓을 때까지 물 앞에서 지켜보고 있다가 라면을 조리하는 분은 거의 없을 것입니다. 왜냐하면 물을 끓이면서 다른 일을 할 수 있는데 기다리는 것은 능률적이지 않기 때문이죠.

그래서 물을 올려놓은 다음 라면도 꺼내고, 파도 썰고, 계란도 꺼내어 조리를 합니다.

Javascript의 세계도 마찬가지로 하나하나씩 순차적으로 진행하는 것이 아니라 효과적으로 시간을 분배하면서 이벤트를 관리합니다. 그럼 어떻게 진행되는지 하나하나씩 알아보도록 하겠습니다.

물이 끓고 나면 해야 하는 일 (Callback Function)

라면에 물이 다 끓으면 어떻게 하나요? 자연스럽게 우리는 팔팔 끓는 물에 라면을 넣거나, 스프를 넣습니다.

스프 먼저, 면 먼저처럼 기호가 다르듯, 하나의 일이 끝나면 사람마다 다음 해야 할 일이 다를 수 있습니다.

자바스크립트에서는 함수가 실행이 끝나면, 다음에 실행할 일을 정할 수 있는데 이 것을 Callback이라고 부릅니다.

아래의 코드를 예로 들어 보겠습니다.

setTimeout 함수는 첫번째 인자로 callback function을 받고, 두번째 인자로 기다릴 시간을 받는 함수입니다.

위 코드를 실행해보면 1초 후에 function(){ console.log('1초가 지나갔다')} 가 실행 됨을 알 수 있습니다.

즉, callback Function은 함수가 정해놓은 일이 끝난 뒤, 후속으로 하는 일을 알려주는 함수입니다.

setTimeout 이외에도 Callback 함수는 AJAX, Dom을 관리하는 event 등에 사용되며, javascript를 효과적으로 사용하기 위해선 반드시 알아야 하는 개념입니다.

오래 걸리는 일 따로 처리하기 (browser web APIs)

토이가 아침 식사를 준비한다고 생각해보겠습니다.

토스트기 없이 프라이팬에 빵을 구우면, 빵을 굽는 동안은 식탁을 셋팅 한다거나, 음료를 준비하는 등 다른 일을 할 수가 없습니다. 반면에 토스트기가 있다면 토스트기에 빵 굽는 일은 시키고, 저는 다른 일을 할 수 있습니다.

이를 브라우저에 대입 해보면, browser web API가 브라우저에서 토스트기와 같이 오래 걸리는 작업을 대신해 주는 역할을 합니다. browser web API 는 브라우저 안에 C++ 구현된 쓰레드로 주로 DOM event, AJAX request, setTimeout 등 비동기 이벤트를 처리합니다. javascript 싱글 쓰레드의 영향을 받지 않고, 독립적으로 이벤트를 처리할 수 있습니다.

머리 속에 다음에 할 일 생각해 놓기 (Callback Queue)

아까 아침 식사를 준비하던 때로 돌아 가보겠습니다.

저는 지금 토스트기에 식빵을 넣어둔 상황이고, 빵이 다 구워지면 빵에 잼을 바를 생각입니다.

이처럼 사람은 어떤 일이 끝나면 다음에 할 일을 머리 속에 저장해 놓지만, 브라우저의 경우 Callback Queue 에 저장 해놓고 사용합니다.

즉, Callback Queuebrowser web API에 있는 event가 실행되고 나면 javascript에서 실행할 callback을 저장하고 있는 저장소입니다.

예를 들어 위 코드를 실행하게 되면 1초가 지나면 Callback Queuefunction(){console.log(‘1초가 지나갔다’)} 함수가 담기게 됩니다.

할 일을 정돈하기 (event loop)

아침 식사로 다시 돌아가 보겠습니다.

토스트기에 빵을 넣고, 저는 냉장고에서 우유와 과일을 꺼내 셋팅을 하고 있습니다. 우유를 따르던 중에 토스트에서 빵이 다 구어졌습니다. 저는 빵이 다 구어지면 잼을 바를 생각이었지만, 우유를 따르고 있기 때문에 우유를 마저 따르고 빵을 꺼내 잼을 바를 것입니다.

이처럼 사람은 능률적으로 일하기 위해 우유를 마저 따르고, 빵을 굽는 행동을 효과적으로 판단하고 행동합니다.

Javascript 내에서도 효과적으로 일을 처리하기 위한 비슷한 매커니즘이 있는데 이를 event loop라고 부릅니다.

위 코드 실행했을 때, event loop 매커니즘이 어떻게 처리하는 알아보도록 하겠습니다.

처음 코드를 실행하면 console.log(‘시작’)코드가 call stack에 들어가게 되고 실행이 됩니다.

Call StacksetTimeout 함수가 들어갑니다.

setTimeout 함수가 비동기 함수이기 때문에 Call Stack 에서 바로 실행되지 않고, web API로 콜백 함수 timeout()이 들어 갑니다.

다음 코드인 console.log('끝!');을 실행합니다. 아직 1초가 지나지 않아 timeout() 함수는 여전히 web API에 있습니다.

1초가 지나 callback Queuetimeout()함수가 들어 옵니다.

event loopCall Stack이 비어있는지 확인하고, timeout() 함수를 call Stack으로 보냅니다.

timeout 함수 안에 있던 console.log(‘1초가 지났습니다’) 코드가 호출됩니다.

모든 함수가 호출되고 Call Stack이 비워집니다.

결과적으로 event loopCall Stack 비어있는지를 주기적으로 확인하여 Callback Queue에서 Callback function을 가져와 Call Stack에서 Javascript 코드가 실행될 수 있도록 돕는 역할을 합니다. event loop가 반복적으로 Call Stack이 비어있는지 확인 하는 것을 tick이라고 합니다.

Wrap-Up

이로서 javascript환경에서 어떻게 비동기 이벤트들이 처리되는지 알아 보았습니다. 좀 더 자세하게 event loop에 대해서 알고 싶다면 event loop에 관한 전설적인 영상(링크)를 확인 해보시길 바랍니다.

참고 자료

https://medium.com/@gaurav.pandvia/understanding-javascript-function-executions-tasks-event-loop-call-stack-more-part-1-5683dea1f5ec

https://engineering.huiseoul.com/자바스크립트는-어떻게-작동하는가-엔진-런타임-콜스택-개관-ea47917c8442

https://velog.io/@thms200/Event-Loop-이벤트-루프

--

--