Tech/JavaScript

자바스크립트가 웹에서 비동기적으로 작동하는 원리에 대하여

닝닝깅 2024. 2. 12. 17:58

코어 자바스크립트라는 책을 읽다가 자바스크립트의 작동 원리에 대해 정확히 알고 싶어서 정리하게 되었다.

 

자바스크립트는 단일 스레드 기반의 언어자 동기적인 언어이다. 즉 순차적으로 한번에 하나의 작업만 처리할 수 있다.

 

 

자바스크립트가 실행되는 환경을 자바스크립트 런타임이라고 한다. 자바스크립트에도 다양한 런타임이 있고 각각의 런타임은 환경과 규칙을 정의한다.

  • 브라우저 : 웹 브라우저에서 실행되는 자바스크립트 코드를 위한 환경이다.
  • Node.js : 브라우저 외부에서 자바스크립트가 실행 가능하도록 한다 ( ex 서버 작업 )
  • React Native : 모바일 앱 개발을 위한 환경이다.
  • Electron : 데스크톱 애플리케이션을 위한 환경이다.

 

웹 개발을 주로 하는 프론트엔드 개발자로서 웹에서의 동작 방식이 제일 궁금했기에 브라우저 환경에서의 자바스크립트가 비동기적으로 동작하는 원리에 대해 알아보고자 한다! 🧐

 

📌 브라우저에서의 자바스크립트

브라우저 환경에서 자바스크립트가 실행되기 위해서는 자바스크립트 엔진, Web APIs, Callback Queue ( Task Queue ), Event Loop 영역이 필요하다.

 

런타임 환경은 이 모든 영역을 포함한 것을 일컫는다.

 

결론부터 말하자면 자바스크립트 엔진이 싱글 스레드로 동작하고 런타임 환경이 멀티스레드를 제공한다!

 

✨ 자바스크립트 엔진

자바스크립트 엔진은 자바스크립트 코드를 실행하는 프로그램 또는 인터프리터(해석기)이다.

자바스크립트 엔진은 Memory Heap 과 Call Stack로 구성된다.

Memory Heap

메모리 할당이 일어나는 곳이다. 객체, 배열, 함수 등의 참조타입 데이터가 저장된다.

 

Call Stack ( 콜 스택 )

코드가 실행될 때마다 호출이 스택 형식으로 쌓이는 곳이다. 프로그램 실행 중 우리가 어디에 있는 지를 기록하고 제어할 수 있다.

 

자바스크립트는 단일 스레드 기반의 언어이기 때문에 하나의 콜 스택이 존재한다. 따라서 한번에 하나의 함수만 실행가능하다. 함수가 실행되면 그 함수는 스택의 가장 상단에 위치하게 되고 함수의 실행이 종료되면 그 함수는 스택에서 제거된다. 

 

Web APIs

Web APIs는 브라우저 환경에서 제공되는 자체 API로 비동기 처리 작업을 위한 인터페이스의 집합이다. 주로 네트워크 요청, 타이머 설정, Dom 조작 및 이벤트처리와 관련된 작업을 한다.

 

대표적인 Web API는 다음과 같다.

  • DOM: HTML 문서의 구조와 내용을 표현하고 조작할 수 있는 객체
  • XMLHttpRequest: 서버와 비동기적으로 데이터를 교환할 수 있는 객체
  • Timer API: 일정 시간을 간격으로 함수를 실행시키거나 지연시키는 메소드 제공
  • Console API: 개발자 도구에서 콘솔 기능 제공

 

각 API마다 스레드가 할당되어 있고, 이들이 모여 멀티 스레드를 이루고 있다. Web API가 멀티 스레드로 구현되어 있기 때문에 자바스크립트 비동기 처리를 할 수 있는 것이다. 

 

콜 스택에서 실행된 비동기 함수는 Web API를 통해 호출되고 Web API는 콜백 함수를 콜백 큐에 푸시한다.

 

 

✨ Callback Queue

콜백 큐는 Web API의 콜백 함수를 쌓아두는 큐다. 예를 들어, setTimeout( a, 500 )를 호출하게 되면 Web API는 타이머를 동작시켜서 5초후에 a를 콜백 큐에 쌓는다.

 

콜백 큐에는 두 가지 큐가 있다. Task QueueMicroTask Queue이다.

  • Task Queue ( MacroTask Queue ): 일반적으로 비동기로 처리되는 함수들의 콜백 함수가 쌓이는 큐
    • setTimeout, setInterval, fetch, addEventListener 등
  • MicroTask Queue: 우선적으로 비동기로 처리되는 함수들의 콜백함수가 쌓이는 큐
    • promise.then, process.nextTick, MutationObserver 등

출처: https://dev.to/lydiahallie/javascript-visualized-promises-async-await-5gke

콜백 큐에 쌓인 함수들은 콜스택이 비는 순간 이벤트루프에 의해 콜스택에 순서대로 푸시된다.

이때 종류에 따라 이벤트 루프가 콜스택으로 옮기는 순서가 달라진다. 일반적으로 MicroTask Queue의 콜백을 먼저 옮긴 뒤 Task Queue를 처리한다.

 

 

✨ Event Loop

이벤트 루프는 콜 스택, 콜백 큐, Web APIs 등의 브라우저 요소를 감시하며 비동기 처리가 원활하게 이루어지도록 한다. 

콜 스택에 현재 실행 중인 작업이 있는 지 확인하고, 콜백 큐에 대기 중인 작업이 있는 지 반복적으로 확인하여 대기 작업을 콜 스택으로 옮겨 실행 가능하게 만들어주는 형태로 동작한다. 즉, 브라우저의 동작 타이밍을 관리하는 감시자 역할을 한다.

 

 

📌 비동기 처리과정 예시 

브라우저 내에서 비동기가 처리되는 과정 전체를 보면 다음과 같다. 원활한 이해를 위해 Lydia Hellie님의 gif 자료를 참고하여 작성하게 되었다.

 

아래의 코드를 실행시켜 보자.

 

console.log('Start!');

setTimeout(() => {
	console.log('Timeout!');
}, 0);

Promise.resolve('Promise!').then(res => console.log(res));

console.log('End!');

 

1. 콜 스택에 console.log('Start!') 코드 부분이 쌓인 뒤 실행되어 콘솔창에 "Start!"가 출력된다.

 

2. setTimeout 코드가 콜 스택에 쌓이고 실행되면 그 안의 콜백함수가 Web API로 옮겨지고 타이머가 작동하게 된다.

 

3. 타이머가 종료됨에 따라 setTImeout 의 콜백함수는 Task Queue에 쌓이게 된다.

4. Promise 코드가 콜 스택에 쌓여 실행되고 then 핸들러의 콜백함수가 MicroTask Queue에 쌓이게 된다.

 

5. console.log('End!') 코드가 실행되어 "End!" 텍스트가 콘솔 창에 출력된다  

 

6. 콜 스택에 더이상 실행할 함수가 없어 비워지게 되면 콜백 큐에 남아있는 콜백 함수들을 옮겨와 콜 스택에 쌓인 뒤 실행된다. 이때, 우선순위에 따라 MicroTask Queue에 있는 콜백함수가 먼저 처리된다.

 

7. MicroTask Queue가 비워지면 Task Queue에 있는 콜백 함수를 콜 스택에 옮겨와 쌓여 실행된다.


출처

https://dev.to/lydiahallie/javascript-visualized-promises-async-await-5gke

https://inpa.tistory.com/entry/%F0%9F%94%84-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%A3%A8%ED%94%84-%EA%B5%AC%EC%A1%B0-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC#%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8_%EC%9D%B4%EB%B2%A4%ED%8A%B8_%EB%A3%A8%ED%94%84_%EB%8F%99%EC%9E%91_%EA%B3%BC%EC%A0%95