자바스크립트는 단 하나의 콜 스택에 실행 컨텍스트를 차례로 담아 코드를 한 번에 하나씩 순차적으로 (=동기적으로) 수행한다. 이로 인해 자바스크립트는 싱글 스레드라고 불린다.
👿 멀티 스레드가 가질 수 있는 문제점
- 하나의 스레드에 문제가 발생하면 전체 프로세스가 영향을 받을 수 있다.
- 여러 개의 쓰레드가 같은 데이터에 접근하는 경우, 경우에 따라 동시성 문제가 발생할 수 있다.
function callBack() {
console.log("2");
}
console.log("1");
setTimeout(callBack, 5000);
console.log("3");
위 코드의 동작 과정을 예상해보자.
함수 선언 부분을 제외하면 위에서부터 차례로 실행되기 때문에,
1) console.log("1") 실행
2) setTimeout(callBack, 5000)이 실행, 5초 후 콜백 함수가 실행되어 console.log("2")가 출력
3) 마지막으로 console.log("3") 실행
이 순서를 따라 콘솔에 1, (5초 지나고)2, 3 이 실행될 것처럼 보인다.

하지만 실제로 콘솔창을 확인해보면 1과 3이 먼저 출력된 후에 5초가 흐르고 나서 2가 출력된다.
위에서 자바스크립트는 코드를 동기적으로 실행한다고 했고, 그렇다면 setTimeout이 두 번째로 실행되었어야 하는데 왜 순서가 변경됐을까?
만약 setTimeout 이나 http 요청처럼 즉각적으로 처리할 수 없는 함수들까지 동기적으로 처리하게 되면, 해당 함수들이 처리되기 전까진 다른 것들이 처리될 수 없기 때문에 비효율적이다.
즉 자바스크립트는 Web API와 이벤트 루프의 도움을 받아 원래는 못하는 비동기 작업을 수행할 수 있게 되어 이러한 문제를 해결할 수 있다.

자바스크립트 런타임은 자바스크립트가 실행되는 환경이다. 즉 브라우저 또는 Node.js가 될 수 있는데, 여기선 브라우저 환경으로 언급한다.
런타임은 자바스크립트 엔진, Web API, Task Queue, Event Loop로 구성된다.
JS에서의 비동기 처리는 Web API, Task Queue, Event Loop 이 세 가지를 통해 이루어진다.
➡️ 이 세 가지의 도움으로 우리는 브라우저가 멀티 스레드로 작동하는 것처럼 느낄 수 있게 된다!
Web API는 자바스크립트 엔진이 아닌 브라우저에서 제공하는 API 로, JS 에서 처리할 수 없는 이벤트 핸들러, AJAX 요청, 비동기함수(SetTimout 등) 같은 요청을 전달받아서 대신 처리해주는 역할을 수행한다.
즉 자바스크립트는 코드를 실행하다가 조금이라도 시간이 오래 걸리는 요청을 만나면 Web API 로 보낸다.
Web API의 메서드들은 전부 비동기 메서드이기 때문에 콜백함수를 가지고 있다. 이 메서드들의 실행이 끝나면, 가지고 있던 콜백함수를 태스크 큐로 보낸다.
💡 Web API는 멀티 스레드 방식을 따르기 때문에, 백그라운드 내에서 여러 개가 동시에 실행될 수 있다.
Web API가 처리한 비동기 메서드의 콜백함수는 다음으로 태스크 큐에 들어온다. 자바스크립트의 함수는 콜 스택에 들어가야만 처리될 수 있다. 태스크 큐에서는 콜 스택이 빌 때까지 함수를 보관하고 있다가, 스택이 비면 보내서 함수가 실행될 수 있도록 한다.
실행되기 전까지 여기에 보관된 함수들은 순서대로 줄서서 기다리다가, 순서대로 빠져나가게 된다.
function callBack() {
console.log("2");
}
console.log("1");
setTimeout(callBack, 5000);
console.log("3");
💻 위의 예시 코드를 통해 본다면,
1) setTimeout(callBack, 5000) 줄이 실행되어 실행 컨텍스트에 담긴다.
2) 자바스크립트는 Web API로 콜백함수인 callBack을 보내며, 실행 컨텍스트에서 setTimeout 컨텍스트는 사라진다.
3) Web API는 5초의 시간차를 처리한 후, 함수 callBack을 태스크 큐로 보낸다.
addEventListener('click' , function() {...})를 예시 코드로 들어보면,
클릭 이벤트가 발생하면addEventListener가 Web API로 들어가고, 그 후 두번째 인자의 익명함수가 태스크 큐(Task queue)로 보내진다.
태스크 큐(Task Queue)에 담긴 콜백함수들이 적당한 타이밍에 콜 스택으로 이동되기 위해선 이벤트 루프가 필요하다!이벤트 루프는 자바스크립트 엔진 자체에 포함된 부분이다.
이벤트 루프는 실행할 콜백함수들을 관리한다. 콜 스택을 지켜보고 있다가, 스택이 빈 것을 확인하면 태스크 큐에서 대기 중인 함수를 꺼내서 스택에 넣는다.
현재 콜 스택에서 실행되고 있는 함수가 있다면 태스크 큐에 담긴 함수들은 콜 스택으로 이동되지 않는다!
위의 예시 코드에서 Web API가 5초의 시간차를 처리한 후 callBack을 태스크 큐로 보낸 이후에, 이를 최종적으로 콜 스택으로 보내는 것은 이벤트 루프의 역할이다.
💻 위의 코드를 다시 보면,
1) setTimeout(callBack, 5000) 줄이 실행되어 실행 컨텍스트에 담긴다.
2) 자바스크립트는 Web API로 콜백함수인 callBack을 보내며, 실행 컨텍스트에서 setTimeout 컨텍스트는 사라진다.
3) Web API는 5초의 시간차를 처리한 후, 함수 callBack을 태스크 큐로 보낸다.
4) 이벤트 루프가 콜 스택을 지켜보고 있다가, 콜 스택이 비면 callBack 실행 컨텍스트를 생성하며 함수를 실행한다.
이벤트 루프는 스택이 비었을 때 콜백 함수를 콜 스택에 넣는다. 그렇다면 setTimeout 으로 시간을 설정하고 콜백 함수가 태스크 큐에 들어가도, 콜 스택이 비지 않았다면 큐에서 계속 대기하게 된다.
콜스택에 아직 실행되지 않은 함수들이 한가득 들어있다면, 콜백함수는 몇초가 되든 일단 대기상태로 머무를 수 있다. 따라서 setTimeout에 입력한 시간은 정확하게 지켜지지 않을 수도 있다.
➡️ 여기서 알 수 있는 것은, 콜 스택을 너무 바쁘게 만들면 비동기 함수가 제때 들어가지를 못하고, 브라우저 프리징 현상이 발생할 수 있다는 점이다.
👿 브라우저 프리징 예시
1. 클릭 이벤트가 일어났는데, 해당 콜백함수가 큐에서 콜스택으로 들어가지 못하고 콜스택의 모든 함수들의 실행이 종료될 때까지 기다리고 있는 상황이다. 이때, 위의 이미지와 같은 "응답 대기 중"현상이 발생한다.
2. 마찬가지로, 버튼 하나에 이벤트 리스너를 100개씩 달아놓으면, 큐에 콜백함수가 100개 생기고 이에 따라 웹사이트가 버벅일 수 있다.
출처
[코딩만화] 비동기 프로그래밍이 뭔가요?
이벤트 루프 (setTimeout의 시간은 정확할까?)
호출 스택과 이벤트루프
[JavaScript] 비동기 작업의 원리 (JavaScript 엔진, Web API, Task Queue, Event Loop)
JS 비동기의 핵심 Event Loop
JS. WEBAPI와 단일 쓰레드
Javascript는 왜 싱글 스레드일까?
자바스크립트는 왜 싱글 스레드를 선택했을까? 프로세스, 스레드, 비동기, 동기, 자바스크립트 엔진, 이벤트루프