이번에 JS의 이벤트 루프라는 주제로 테크톡(발표)를 했었다. 해당 내용을 간략하게 요약해 보겠다.

- memory heap - 메모리 할당이 일어남
- Heap - 구조화되지 않은 넓은 메모리 영역
- Memory heap에서 객체가 담김( 변수, 함수 )- Call Stack - 실행될 코드의 한 줄 단위로 할당. 코드 실행 또한 한줄 단위로 해석 및 실행( 이는 자바스크립트가 동기적인 언어라는 것 )
- Web APIs - DOM(document), AJAX(XMLHttpRequest), Timeout (setTimeout). 이는 비동기적인 처리를 담당.
- Callback Queue(taskQueue, Event Queue) 등 다양한 형태로 설명됨.
비동기 처리가 끝난 후 실행되어야 할 콜백 함수가 차례로 할당됨.- Event Loop - Queue에 할당된 함수를 순서에 맞춰 Call Stack에 할당해준다.
console.log(i); //변수 i는 어디서 가져올까?
당연히 변수가 할당되어 있는 Heap에서 가져와 씀
처음 Promise, async / await 테크톡을 할 때 자바스크립트는 동기적인 언어로, 코드를 한줄씩 순서대로 처리한다고도 얘기하고 비동기적인 처리도 가능하다고 했었다.
근데 사실 나도 당시 비동기 처리 공부를 하고 발표를 하면서 '이게 뭔 헛소리야? 그래서 동기인데 비동기인데?' 라고 생각을 했었는데 이번에 Event Loop를 하면서 확실하게 이해를 하게 됐다.
console.log('a')
setTimeout(()=> {console.log('b')},1000)
console.log('c')
지난 비동기 처리에 대한 테크톡때도 동일하게 쓰였던 예시이다. 실행 결과는 어떻게 될까?
a - c - b 순이다. 비동기 처리 발표때 발표했던 내용으로는 자바스크립트는 동적인 언어이니 가장 첫 줄부터 a를 출력하고
setTimeout은 WebAPI이기 때문에 브라우저에 1초뒤 콜백 해달라고 요청함. 이를 기다리지 않고 c를 출력한 후 1초가 지났으니 b를 출력한다고 설명 했었다.
이를 통해 자바스크립트는 동적인 언어임에도 비동기적인 처리가 가능하다. 라고 말했었는데 이에 대해 자바스크립트의 동작원리를 통해 좀 더 세부적인 설명을 해보겠다.
첫줄부터 살펴보자.
console.log(a)
이 코드는 Call Stack에 가장 첫줄로 쌓이게 된다.(이는 당연하게도 JS의 동기적인 특성으로 한줄씩 코드를 해석하고 실행) 그리고 console엔 a가 찍힌다.
그리고 setTimeout은 WebAPI이기 때문에, Call Stack에서 바로 함수를 실행한다기 보다 WebAP(이른 바 대기석)I로 들어가게 됨.
그 후 console.log(c)가 쌓인다.
setTimeout이 1초를 기다리는 동안 console.log(c)는 이미 Call Stack에서 코드를 해석하고 실행하면서 console에 c가 찍히게 된다.
그 후, setTimeout은 WebAPI에서 1초가 지난 후, Callback Queue에 쌓이고, Event Loop가 돌면서 Call Stack 으로 쌓이게 된다. 그 후 b가 출력이 되는 것이다.
(추가 설명을 하면 Callback Queue에서 Call Stack으로 보내기 전엔 한가지 조건이 필요하다. 이는 Call Stack가 비어있다는 조건.)
그렇다면 한가지 의문이 들 것 이다. 만약 1초를 기다리는게 아니라 0초를 기다리는 것이라면?
console.log('a');
setTiemout(() => {console.log('b')}, 0)
console.log('c');
뭐 당연하게도 결과는 똑같다. 처음에 Call Stack에 console.log('a')가 쌓이고, 바로 a가 출력됨.
그 후 setTimeout은 설정 해놓은 시간이 몇초인지를 떠나서 바로 WebAPI로 들어감.(일명 대기실)
마찬 가지로 setTimeout은 CallbackQueue에 바로 쌓이게 되는데, 여기서 분기점이 갈린다. 하지만 어떻게 되든 결과는 같을 것이다.
만약 setTimeout이 WebAPI로 들어가감과 동시에 바로 console.log('c')가 Call Stack에 쌓이고 실행되는 과정. 너무나 당연하게도 console.log('c')는 바로 console에 출력. 그리고 Callback Queue에 setTimeout이 들어가고 EventLoop를 통해 Call Stack에 setTimeout이 쌓임. 그 후 b 출력.
만약 setTimeout이 WebAPI로 들어가고, consolog('c')가 Call Stack에 쌓이면서 바로 0초가 지나 CallbackQueue에 쌓이게 됨. 이 상황에선 CallbackQueue에서 setTimeout이 Call Stack으로 쌓인다고 생각할 수도 있지만, 전에 말했던 전제조건대로 Call Stack이 비어있지 않으면 Callback Queue에선 Call Stack으로 넘어가지 않음. 결론적으로 결과는 같음.

어떻게 될까? 처음엔 나는 대충 보고 a - b - c - c 순서로 콘솔에 출력이 될 것이라고 예측 했었다. 하지만 결과는 정반대로 c - b - a - c 였다. 좀 더 상세히 살펴보자.

해당 사진은 콜스택을 표현한 사진이다. js는 가장 먼저 우리가 작성한 코드들을 가지고 있는 annoymous(혹은 main)이라는 스택이 담기게 된다.
그 후부터 우리가 작성한 코드들이 한줄씩 담기게 되는 것이다.

이런 식으로 말이다. 그리고 first()가 실행이 되었으니 first()의 내부에서 다음 코드인 second()를 호출하게 된다.

이렇게 second()함수가 실행이 됐으니, 너무 당연하게도 그 다음 코드 블럭을 실행하면서 third() 함수가 호출되어 쌓인다는 것을 알 수 있다.

이제 third()가 쌓였으니, 하나하나 콜스택에서 실행되어 빠져나갈 차례다.(후입선출 혹은 선입후출의 구조)

console.log('c')가 쌓이고,

콘솔에 c라는 문자열을 띄우면서 빠져 나간다. 마찬가지로 실행을 마친 third() 함수 또한 콜스택에서 빠져나가게 된다.
이 이후부턴 너무 뻔하게 실행되기 때문에 글이 너무 길어질 수 있는 관계로 생략하겠다.
한가지 추가할 정보로는 annoymous(main)은 모든 코드의 실행을 마친 뒤에 빠져 나간다.

해당 내용은 annoymous에 대한 이곳 저곳에서 긁어와서 나름 내 머리 속에서 정리한 정보이다.(틀린 점이 있다면 알려주시면 감사하겠습니다..!)
이벤트 루프에 대한 동작까지 세세하게 담은 예시까지 보여주고 싶지만, 마찬가지로 쓸데없이 글이 커질 것 같아서 글로 설명을 대신 하겠다.
Event Loop는 Call Stack과 Callback Queue를 확인함.
여기서 Call Stack이 빈 상태가 되면, Callback Queue의 첫번째 콜백 함수 하나를 Call Stack으로 밀어넣는다.
이러한 반복적인 행동을 틱(tick) 이라 부른다.(당연히 Call Stack에 밀어넣은 콜백 함수가 실행을 마치고 Call Stack에서 빠져 나오기 전 까진 Callback Queue의 다른 콜백 함수를 밀어넣지 않는다.)
글의 초반에 나온 setTimeout()의 예시를 보면 될 것 같습니다.
왜 갑자기 Promise에 대한 얘기가 나왔을까?

해당 코드를 살펴보자. 어떤 순서로 출력이 될까?
나는 당당히 a - d - b - c 의 순서라고 얘기 할 수 있을 것 같다. 하지만 결과는 정반대.
a - d - c - b의 순서였다. 이에 대한 이유는 다음의 설명을 통해 알 수 있다.

setTimeout(), setInterval(), setImmediate()와 같은 task(Macrotask)를 넘겨받는다.Promise나 async/await, process.nextTick, Object.observe, MutationObserver과 같은 비동기 호출을 넘겨받는다.requestAnimationFrame과 같이 브라우저 렌더링과 관련된 task를 넘겨받는 Queue이다.이에 대해 알기 쉽게 애니메이션으로 만들어져 있는게 있어서 가져와 봤습니다.

여기서 MACROTASK QUEUE라는 것은 Task Queue와 같은 것입니다.
EventLoop는 우선 순위를 정해서 MicroTask Queue에 쌓여 있는 Task들을 먼저 Call Stack으로 보내준 뒤, Call Stack과 MicroTask Queue가 모두 빈 것을 확인한 EventLoop는 그때부터 MacroTask Queue의 Task들을 Call Stack으로 하나하나 내보냅니다.
async / await 또한 애니메이션으로 알기 쉽게 설명을 하겠습니다.

당연히 console.log('Before function!')이라는 코드를 가장 먼저 Call Stack에 쌓고 실행합니다.

이후 myFunc()를 Call Stack에 쌓고 console.log('In Function')을 쌓아서 출력하게 됩니다.
(참고로 async 함수는 함수 내의 모든 코드 블록을 비동기로 실행하는 함수가 아니라 반환 값을 Promise 객체로 반환하고, await을 사용하기 위한 함수일 뿐이다. await의 또 다른 기능 중 하나는 뒤에 오는 값이 Promise 객체의 값이면 promise의 resolve 값으로 바꿔준다.)

one() 비동기 함수를 호출한다. 이때 await 키워드를 통해 실행 되었기 때문에, 나머지 함수의 실행은 Microtask Queue 에 적재됩니다. 이는 자바스크립트 엔진이 await 키워드를 인식하면 async 함수의 실행은 지연되는 것으로 처리하기 때문입니다.

이후 myFunc() 함수의 다음 줄인 console.log('After function!')가 마저 실행된 후 다시 Call Stack에 myFunc()를 쌓아 마저 실행하게 됩니다.

비동기 함수인 one의 반환값이 할당된 변수 res를 console로 출력하면서 마무리가 되게 됩니다.
해당 내용에 대해 발표한 영상과 애니메이션의 출처를 첨부하면서 마무리 하겠습니다.
참고 문헌: https://dev.to/lydiahallie/javascript-visualized-promises-async-await-5gke#syntax
발표 자료: https://www.youtube.com/watch?v=EZMXyGIbrzY&t=2s
부족한 점은 따끔하게 지적해주시면 감사하겠습니다🙇.