# 싱글 스레드
자바스크립트는 한번에 하나의 작업만 수행하는 싱글 스레드 언어이다. 하지만, 웹 애플리케이션은 네트워크 요청, 애니메이션 처리, 타이머 등 멀티로 작업하는 경우가 많다.
? 자바스크립트는 한번에 하나의 작업을 할 수 있기 때문에 네트워크 요청을 보내면 올 때까지 기다려야 하고, 애니메이션이 시작하면 끝날 때까지 아무것도 못하고 기다려야 한다. 하지만 웹 애플리케이션은 네트워크 요청을 보내면서 응답을 기다리지 않고 다른 작업을 하고 애니메이션이 동작하는 동안에도 계속 무언가를 한다.
# 이벤트 루프
다시 한번 말 하지만 자바스크립트는 한번에 하나의 동작만을 수행한다. 그렇다면 자바스크립트는 오래 걸리거나 반복적인 작업을 어떻게 처리할까? 바로 이벤트 루프 덕이다. 비동기적으로 동작하는 작업들은 자바스크립트 엔진(V8)이 아닌 브라우저 내부 Web APIs에서 처리하고 완료된 작업은 콜백 큐에 들어가게 된다. 이벤트 루프는 call stack이 비어있을 때 콜백 큐에서 대기 하고 있는 작업을 call stack으로 가져와 처리한다.
•자바스크립트 엔진 : call stack
•브라우저 런타임 : Web APIs, 콜백 큐, 이벤트 루프
Web APIs이란?
Web APIs는 타이머, 네트워크 요청, 파일 입출력, 이벤트 처리등 브라우저에서 제공하는 다양한 API들을 전부 포함한다. 대표적으로 setTimeout, setInterval, XMLHttpRequest, fetch 등등 뭔가 반복적이고 오래걸리는 것들이 포함되어있다.
각 API는 별도의 스레드에서 처리되고, 이들이 모여 멀티 스레드로 이우러져있다. 대표적인 비동기 API는 아래 두가지 이다.
•Timer API : setTimeout과 같은 일정한 시간 간격으로 함수를 실행하거나 지연시키는 API
•AJAX : 서버와 비동기적으로 데이터를 교환 할 수 있는 API
Callback Queue의 종류
Web APIs도 여러 API들을 포함하는 단어 듯, Callback Queue도 두가지의 Queue를 묶어 총칭한다. task queue와 microtask queue다.
•Task queue : setTimeout, setInterval, fetch, addEventListener 와 같이 일반적인 비동기 작어들이 들어가는 큐로 이벤트 루프가 한번에 하나씩 처리한다.
•Microtask queue: Promise.then(), Promise.catch(), Promise.finally(), queueMicrotask() 등 더 높은 우선순위를 가진 비동기 작업들이 들어가는 큐로 task queue보다 처리 우선순위가 높다.
Callback Queue의 종류에 따라 이벤트 루프가 콜 스택으로 옮기는 순서가 달라진다. 일반적으로 microtask queue가 가장 우선순위가 높아 먼저 microtask queue를 처리하여 비우고 task queue의 콜백을 처리한다.
# 이벤트 루프 동작 원리
이벤트 루프의 동작 원리를 설명하기 전에 브라우저의 내부 구성도와 각각의 구성 요소들이 어떤 작업을 하는지 알아보자
•call stack : 자바스크립트 엔진이 코드를 실행하기 위해 사용하는 메모리 구조
•web apis : 브라우저에서 제공하는 API 모음으로, 비동기적으로 실행되는 작업들을 직접 수행한다.
•callback queue : 비동기적인 작업이 완료되면 실행되는 함수들이 대기하는 공간
•Event Loop : callback queue에 대기 중인 함수들을 call stack이 비어있을때 적절히 실행시킨다.
정리를 하자면 싱글 스레드인 자바스크립트가 동시에 여러가지 작업을 수행할 수 있는 방법은 이벤트 루프가 자바스크립트 엔진과 브라우저의 Web APIs를 연결하여 비동기적으로 작업을 할 수 있게 끔 조율 해주기 때문이다.
작업 순서
•call stack → Web APIs : 자바스크립트 엔진이 비동기 함수를 만나면 자동으로 Web APIs로 전달한다.
•Web APIs → 콜백 큐 : Web APIs에서 작업 완료 시 자동으로 콜백 큐에 넣음
•콜백 큐 → call stack : 이벤트루프가 call stack이 비어있으면 Microtask queue를 우선 옮기고 전부 비워지면 task queue에 있는 작업들을 옮긴다.
console.log('1. 동기 작업 시작');
setTimeout(() => {
console.log('4. Task Queue (setTimeout)');
}, 0);
Promise.resolve().then(() => {
console.log('3. Microtask Queue (Promise)');
});
console.log('2. 동기 작업 끝');
// 실행 결과:
// 1. 동기 작업 시작
// 2. 동기 작업 끝
// 3. Microtask Queue (Promise)
// 4. Task Queue (setTimeout)
•동기 작업들 (console.log) 먼저 콜 스택에서 실행
•setTimeout → Web APIs로 전달 → 완료 후 task queue에 대기
•Promise.then() → Web APIs로 전달 → 완료 후 microtask queue에 대기
•콜 스택이 비면 → 이벤트 루프가 microtask queue 먼저 처리
•microtask queue가 비면 → task queue 처리