HomeAboutMeBlogGuest
© 2025 Sejin Cha. All rights reserved.
Built with Next.js, deployed on Vercel
👻
개발 기록
/
📚
CS 스터디
/
📚
Event Loop, blocking (JS 비동기 통신)
📚

Event Loop, blocking (JS 비동기 통신)

JS가 비동기적 통신을 할 수 있는 이유에 대해 알아보자.
또한 event loop, blocking, callstack, heap 등 자주 사용하는 용어에 대해 바르게 정의해보자!
 

✉️ JS 런타임을 단순화해보자!

  • 구글 V8 엔진을 예시로 들어보자.
    • V8은 웹 브라우저를 만드는 데 기반을 제공하는 오픈 소스 자바스크립트 엔진임.
notion image
  • 엔진은 두 개의 주요 컴포넌트로 구성되어 있음.
    • Memory Heap : 메모리 할당이 일어나는 곳.
    • Call Stack : 코드의 stack frames 단위로 실행이 일어나는 곳.
  • 개발에 필요한 대부분의 JS API가 있음.
    • 없는 것도 있음. 이건 뒷 부분에 알게 됨.
 

📥 The Call Stack

  • JS는 싱글 스레드 언어임.
    • 하나의 Call Stack을 가지고 있다는 의미이자, 한번에 하나의 코드만을 실행할 수 있다는 뜻.
    • Call Stack은 자료구조가 기록되는 곳임.
  • 함수를 실행하면, 해당 함수를 stack 최상단에 놓고, 만약 함수로부터 return 값을 받으면, stack에서 해당 함수를 pop함.
function multiply(x, y) { return x * y; } function printSquare(x) { var s = multiply(x, x); console.log(s); } printSquare(5);
Call Stack에 들어온 네모 박스를 Stack Frame이라 함.
Call Stack에 들어온 네모 박스를 Stack Frame이라 함.
 

이 예시는 어떻게 될까?

function foo() { foo(); } foo();
  • 엔진이 이 코드를 실행하면, 먼저 함수 foo를 실행하게 됨.
  • 하지만 foo는 어떤 종료 조건 없이 자기 자신을 호출하고 있음.
  • 함수가 실행될 때마다 자기 자신을 Call Stack에 추가하고 또 추가함.
notion image
  • 브라우저는 Call Stack이 실제로 추가할 수 있는 stack frame을 초과하면 다음과 같은 에러를 던지며 동작을 멈춤.
notion image
 

이렇게 싱글 스레드는 간단하다! 근데.. 한계는 없을까?🤔

 

⛔ Blocking

  • 엄청 난 시간을 필요로 하는 call stack이 올 경우 문제가 됨.
  • 예를 들어 API 요청이나 이미지 프로세싱은 느림.
var response1 = ajax('https://example.com/api_1'); var response2 = ajax('https://example.com/api_2'); var response3 = ajax('https://example.com/api_3'); console.log(response1); console.log(response2); console.log(response3);
response 123을 다 처리하고 나서야 콘솔값이 출력됨.
  • 해당 코드는 브라우저에서 실행되고 있기 때문에 문제가 되는 것임.
  • 이 동작을 동기적으로 처리한다면 다음 함수를 실행할 수 없고. 그동안 브라우저에 모든 동작을 할 수 없게 됨.
  • 만약 이를 모르는 사용자가 다른 버튼을 여러 번 클릭한다면 요청이 끝난 후 버튼 이벤트를 순차적으로 실행하며 웹이 정상적으로 동작할 수 있는 상태가 되지 못함.
    • notion image
  • 멋지고 유동적인 UI, UX를 생각한다면 stack에 필요없는 느린 코드를 쌓아서 브라우저가 할 일을 못하게 만들지 말자.
 

❓ 실제론 비동기적으로 동작하던데..

console.log('Hi'); setTimeout(function cb1() { console.log('cb1'); }, 5000); console.log('Bye'); /* Hi Bye cb1 */
 

Concurrency & the Event Loop가 가능하게 함.

 
V8 런타임과 브라우저가 제공하는 웹 API
V8 런타임과 브라우저가 제공하는 웹 API
  • 브라우저에서 제공하는 Web APIs가 있음!
    • JS에서 호출할 수 있는 스레드를 효과적으로 지원함.
  • 앞에서 JS는 대부분의 API를 제공하지만 DOM, AJAX, setTimeout는 해당 되지 않음. 대신 Web API가 제공해주기에 사용할 수 있는 것임(처음 의문에 대한 답).
 

💡 이벤트 루프가 뭐지?

  • Event loop는 Call Stack과 Callback Queue를 지켜보는 일을 함.
  • Call Stack이 비어있다면, Event Loop는 Call Stack에 이벤트를 넣음.
  • 각 이벤트는 콜백함수가 됨.
 

방금 다뤘던 예시를 적용해보자!

 
notion image
  1. state, browser console, call stack이 비워져 있음.
  1. call stack에 console.log('Hi') 추가.
  1. console.log('Hi') 실행.
  1. call stack의 console.log('Hi') 삭제.
  1. call stack에 setTimeout(function cb1() { ... }) 추가.
  1. setTimeout(function cb1() { ... }) 실행. 브라우저는 web api로부터 타이머를 만들어 카운트 다운을 시작함.
  1. call stack에 setTimeout(function cb1() { ... }) 삭제(완료된 것으로 간주).
  1. call stack에 console.log('Bye') 추가.
  1. console.log('Bye') 실행.
  1. call stack에 console.log('Bye')제거.
  1. 5초 후, 타이머 종료 후 Callback Queue에 콜백함수인 ch1 추가.
  1. Event Loop는 Callback Queue로부터 cb1을 받아 call stack에 추가.
  1. cb1이 실행되고 call stack에 console.log('cb1')추가.
  1. console.log('cb1') 실행.
  1. call stack에 console.log('cb1') 삭제.
  1. call stackd에 console.log('cb1') 삭제.
 

setTimeout 동작에서 주의할 점!

  • setTimeout은 자동으로 event loop가 call stack에 넣어주지 않음.
  • callback queue에 넣어짐에 따라 생기는 현상을 살펴보자.
console.log('Hi'); setTimeout(function() { console.log('callback'); }, 0); console.log('Bye'); /* Hi Bye callback */
  • 설정한 시간 후에 callback queue에 넣어지지만, call stack이 비워져 있지 않거나 앞전에 다른 이벤트가 있다면 해당 콜백함수는 기다려야 함.
💡
스크롤 이벤트도 스크롤 시 callback queue에 많은 이벤트를 쌓음. 유저가 스크롤을 멈출 때까지 작업량을 줄이는 방법도 있음.
 
 
참고 자료 :
What the heck is the event loop anyway?
How JavaScript works: an overview of the engine, the runtime, and the call stack
How JavaScript works: inside the V8 engine + 5 tips on how to write optimized code
How JavaScript works: Event loop and the rise of Async programming + 5 ways to better coding with async/await
https://nodejs.org/ko/docs/guides/event-loop-timers-and-nexttick