싱글스레드 자바스크립트가 비동기 처리를 하는 방법

작성 날짜

JS 동기와 비동기

이벤트 루프에 대해 잘 이해하고 있다 생각했는데 머릿속에서 제대로 정리되지 않고,

"분명 자바스크립트는 싱글스레드인데 비동기 처리가 어떻게 되는거지..?"

라는 생각이 들어 다시 한 번 제대로 정리해보고자 한다.

⭕️ 자바스크립트는 싱글스레드 기반으로 동작하는 언어가 맞다.

비동기 동작 때문에 흔히 asynchronous(비동기)하다고 많이 얘기하지만, 자바스크립트 자체는 하나의 흐름을 주욱 타고 흘러가는 synchronous(동기) 한 언어다. '싱글스레드'라는 표현이 함축하고 있는 것이 여러 가지가 있을 텐데 이와 관련된 자바스크립트의 특징을 간단히 꼽아보자면 이렇다.

1. 코드 실행 중 하나의 코드에서 멈추거나 걸리면 다음으로 진행할 수 없다.

예를 들어, alert등의 코드가 실행되면 경고창에서 확인을 누르기 전까진 UI 조작이 불가하다. 혹은 그 이후 코드도 실행되지 않는 걸 알 수 있다. 또 다른 예시로는, 아래의 경우다.

setTimeout(() => {
  console.log("hello");
}, 0);

while (true) {
  console.log("world");
}

console.log("!");

이 코드에서 “!”는 물론이고, “hello”도 영원히 출력되지 않는다. setTimeout같은 비동기 함수는 멀티스레드처럼 별도로 동작해서 출력될 거 같지만, 그러지 않다. 이에 대한 이야기는 밑에서 좀 더 자세하게 알아보자.

여기서 중요한 건 'JavaScript' 자체로는 싱글스레드가 맞다는 점이다. 하지만 웹 개발을 하거나, 노드로 개발을 하는 과정에서 순수하게 자바스크립트만을 실행시키는 경우가 과연 많을까? 특히 프론트엔드 입장이라면 자바스크립트 코드는 반드시 브라우저를 통해 실행되게 되어있다. 그리고 이 브라우저 때문에 싱글스레드 언어인 자바스크립트가 우리 눈으로 보기엔 동시성을 갖는 작업을 해내는 것으로 보이게 된다.

이벤트 루프

위에서 얘기한 눈의 착시를 이해하기 위해선 브라우저의 '이벤트 루프'를 이해할 필요가 있다. 이와 관련해서는 굉장히 유명한 영상이 있는데, 이벤트 루프에 대해 처음 들어보거나 상세한 설명이 필요하다면 꼭 참고하길 바란다.

자바스크립트의 코드는 이벤트 루프 기반으로 작동한다.

즉, 실행되는 함수를 싱글스레드 형식으로 처리하는 메인 "콜 스택"이 있고, 이 콜 스택이 비었을 때마다 "콜백 큐"에서 대기 중인 함수를 콜 스택으로 불러와 실행시킨다.(이벤트 루프 동작 조건 2가지)

JS 이벤트루프

  • setTimeout 같은 비동기 함수가 호출되면,해당 작업은 브라우저에 내장된 WebAPI에게 넘겨진다.
  • 그 안에서 타이머가 동작을 하거나 네트워크 통신을 진행하고, 코드가 실행될 준비가 되면 콜백 큐에 해당 작업을 넣는다. 그래서 사실상 비동기 함수가 동기적인 함수들과 '동시'에 동작하는 것처럼 보이지만, 이는 아주 재빠르게 콜 스택에 넘어와 실행되었기 때문이다.
  • 즉, 모든 건 순차적으로 하나의 스레드(콜 스택) 안에서 돌아가고 우리는 그저 이 시간 차가 너무 작아서 동시에 돌아간다고 착각을 하게 되는 것이다.

위에서 설명한 이벤트루프와 같은 내용이지만 추가로 보면 동작 원리에 대해 명확히 이해하기에 좋다.

비동기 함수 동작 원리

  1. 인터프리터가 비동기 함수를 만나면, 즉시 Call Stack에서 지워버린다.
  2. 이 비동기 함수는 Web API로 넘어간다.
  3. 비동기 함수는 Web API에 담겨있다가, 타이머나 로드 등이 완료되면 Callback Queue로 보내진다.
  4. Event Loop는 Call Stack과 Callback Queue 사이에서 Call Stack이 비어있는지 주시한다.
  5. 모든 함수의 실행이 완료되고 Call Stack이 비워지면, Event Loop는 Callback Queue에 담겨있는 함수들을 먼저 들어온 순서대로 Call Stack으로 넘겨준다.