sbyeol3/articles

[번역] 자바스크립트 함수 실행 이해하기 - 콜스택, 이벤트 루프 등

Opened this issue · 0 comments

자바스크립트 함수 실행 이해하기

원문 : Understanding Javascript Function Executions — Call Stack, Event Loop , Tasks & more

요즘 웹 개발자나 프론트엔드 개발자는 브라우저 내에서 인터랙티브한 요소를 동작시키거나 컴퓨터 게임, 데스크탑 위젯, 크로스플랫폼 모바일 앱 제작 또는 데이터베이스와 연결하기 위해 서버사이드로 코드를 작성하는 것(node.js를 많이 사용)에 이르기까지 스크립트 언어로 다양한 것을 할 수 있습니다. 그렇기에 자바스크립트의 내부 동작을 이해하는 것과 자바스크립트를 더 효율적으로 사용하는 것은 아주 중요합니다. 그리고 이 아티클에서 이런 주제들에 대해 다룰 겁니다.

자바스크립트 생태계는 그 어느 때보다 복잡해졌고 앞으로도 계속 복잡해질 겁니다. Webpack, Babel, ESLint,Mocha, Karma, Grunt 등 모던 웹 어플리케이션을 제작하기 위해 필요한 도구들은 엄청납니다. 어떤 도구를 사용해야 하고 그 도구는 어떤 일을 할까요? 오늘날 웹 개발자들의 고군분투를 아주 잘 설명해주는 만화를 찾았습니다.

Javascript Fatigue — What it feels like to learn Javascript

모든 자바스크립트 개발자들은 어떤 프레임워크나 라이브러리에 대해 깊이 알아보기 전에 루트 레벨에서 내부적으로 어떻게 동작하는지에 대한 기초 지식들을 알아야 합니다. 대부분의 자바스크립트 개발자들은 아마 크롬의 런타임인 V8이라는 단어에 대해 들어보셨을 겁니다. 그렇지만 정확히 V8이 무엇을 하는지 알지 못하는 분들도 있겠죠. 저는 개발자로서의 커리어 첫 1년 동안 주어진 일을 마무리하는 것을 주로 했기에 이런 화려한 용어들에 대해서도 잘 몰랐습니다. 자바스크립트가 어떻게 이런 일들을 해내는지에 대한 저의 호기심을 만족시키지 못했죠. 저는 구글에 대해 더 깊이 파보기로 결정했고 Phili RobertsJS 컨퍼런스에서 진행한 이벤트 루프에 대한 멋진 강연과 같은 몇몇 좋은 블로그 포스팅을 발견했습니다. 그래서 배운 것들을 요약해서 공유하고자 했습니다. 알아야 하는 내용들이 너무 많아 아티클을 두 파트로 나누었습니다. 이번 파트는 일반적으로 사용되는 용어를 소개할 것이고 두 번째 파트에서는 각 용어들을 연결해보려고 합니다.

자바스크립트는 싱글 스레드의 싱글 동시성 언어입니다. 즉 한 번에 하나의 태스크, 코드 한 조각을 다룰 수 있다는 것입니다. 또한 자바스크립트는 (V8 내부에 구현된) 자바스크립트 동시성 모델을 구성하는 힙과 큐 그리고 하나의 콜스택을 가지고 있습니다. 이 용어들에게 대해 알아봅시다.

Visual Representation of JS Model

1. Call Stack, 콜 스택

함수 호출과 기본적으로 현재 프로그램 위치를 저장해두는 자료구조입니다. 우리가 함수를 실행하기 위해 호출할 때 스택에 무언가를 push해두고 함수로부터 리턴받을 때 스택의 top에서 해당 함수를 pop 합니다.

JS Stack Visualization (GIF) (Sorry Correct Output = 100)

위의 파일을 실행할 때 실행이 시작되는 메인 함수가 먼저 보입니다. console.log(bar(6))로부터 가서 스택에 push한 후 인수와 함께 bar 함수로 넘어갑니다. 다시 foo 함수를 호출하여 해당 함수를 스택에 push합니다. 그리고 foo 함수가 리턴하면 스택에서 pop되고 비슷하게 bar가 차례대로 pop됩니다. 마지막으로 console이 값을 출력하면서 pop됩니다. 이 모든 것들은 한번에 하나씩 실행됩니다.

여러분들은 브라우저 콘솔창에서 길고 빨간 줄들로 채워진 에러 스택이 추적된 것을 적어도 한번쯤은 본 적이 있으실 겁니다. 에러 스택은 기본적으로 현재 상태와 위에서 아래의 순서로 실패한 위치를 보여줍니다. (아래 이미지 참고)

때때로 여우리는 함수를 재귀적으로 호출하다가 무한 루프에 빠지기도 합니다. 또한 크롬 브라우저를 사용하면 16,000 프레임의 스택 크기의 제한을 넘게 되어 프로그램이 죽고 Max Stack Error Reached 에러를 throw합니다.

2. Heap, 힙

객체들은 (대부분 비구조적인 메모리 영역인) Heap에 할당됩니다. 변수나 객체들의 모든 메모리 할당이 힙에서 이루어집니다.

3. Queue, 큐

자바스크립트 런타임은 처리되는 메세지들의 목록과 관련되어 실행할 콜백 함수들을을 담는 메세지 큐를 갖고 있습니다. 스택의 용량이 충분하면 큐에서 메세지를 가져와 관련된 함수를 호출합니다. 스택이 다시 비워질 때까지 메세지가 처리됩니다. 이러한 메세지들은 콜백 함수가 주어진 (마우스 클릭 이벤트나 HTTP 요청에 대한 응답을 받은 경우와 같은) 외부 비동기 이벤트에 대한 응답으로 큐에 들어갑니다. 예를 들어 사용자가 버튼을 클릭했으나 콜백함수가 따로 없다면 큐에 들어가는 메세지는 없습니다.

이벤트 루프

기본적으로 자바스크립트 코드의 성능을 평가할 때 스택의 함수를 느리게 또는 빠르게 조잘합니다. console.log()와 같은 함수는 빠르긴 하지만 forwhile문 안에서 수천 번, 수백만 번 반복하게 되면 느려지고, 스택을 그만큼 많이 차지하고 때로는 스택을 블락해버릴 겁니다. 이런 것을 blocking script라 부르며, Webpage Speed Insights에서 아마 보거나 들으셨을 것입니다.

네트워크 요청이나 이미지 요청은 느릴 수 있지만 다행히도 서버에 보내는 요청들은 비동기 함수인 AJAX를 통해 이루어집니다. 만약 네트워크 요청들이 동기 함수를 통해 만들어졌다면 어떤 일이 생길까요? 네트워크 요청은 보통 다른 컴퓨터나 어딘가에 있는 기계에 있는 어떤 서버에게 보내집니다. 그렇기에 다시 응답을 받을 때까지 시간이 꽤 걸릴 수 있습니다. 그 사이에 CTA 버튼을 클릭하거나 다른 것들을 렌더링할 게 있더라도 스택이 블락되어 아무런 일도 일어나지 않습니다. Ruby와 같은 멀티 스레드 언어에서는 이런 일들이 처리 가능하지만 자바스크립트와 같은 싱글 스레드 언어에서는 스택의 함수가 값을 반환할 때까지 가능하지 않습니다. 브라우저가 아무 것도 할 수 없어 웹페이지는 망가져 버립니다. 사용자들이 유동적인 UI를 원한다면 이런 상황은 좋지 않습니다. 이 일을 어떻게 처리해야 할까요?

"JS에서 동시성은 비동기 콜백을 제외하고 한번에 하나씩을 의미한다."

가장 쉬운 방법은 비동기 콜백을 사용하는 것입니다. 즉 코드의 일부를 실행하고 나중에 실행할 콜백 함수를 주는 것이죠. 우리는 모두 $.get(),setTimeout(),setInterval(), Promises 등과 함께 AJAX 요청과 같은 비동기 콜백을 경험했을 겁니다. Node는 비동기 함수 실행의 전부입니다. 이런 비동기 콜백들은 즉시 실행되지 않고 나중에 실행됩니다. 그래서 console.log(), 수학적 연산과 같은 동기 함수들처럼 스택에 바로 push되지 않습니다. 그럼 도대체 비동기 함수들은 어디에 가서 어떻게 처리되는 걸까요?

위 코드에서 자바스크립트 코드에서 네트워크 요청이 실행되는 경우

  1. 요청 함수가 실행된 경우, onreadystatechange 이벤트에 미래에 언젠가 응답이 도착했을 때 실행할 콜백함수로 익명함수를 전달합니다.
  2. "Script call done!"이 콘솔에 바로 출력됩니다.
  3. 응답이 도착했을 때 콜백이 실행되어 본문이 콘솔에 출력됩니다.

caller를 응답에서 분리되면 비동기 작업이 완료되고 호출을 기다리는 동안 자바스크립트 런타임이 다른 작업을 수행할 수 있습니다. 기본적으로 DOM 이벤트나 HTTP 요청, setTimeout과 같이 비동기 이벤트를 다루기 위해 C++로 구현된 브라우저에 의해 생성된 스레드인 브라우저 API가 시작하고 API를 호출됩니다.

브라우저 웹 API 스레드는 DOM 이벤트, HTTP 요청, setTimeout과 같은 비동기 이벤트를 다루기 위해 C++로 구현된 브라우저에 의해 생성됩니다.

WebAPI는 스스로 스택의 실행 코드로 들어갈 수 없습니다. 만약 가능했다면 코드 중간에 API가 무작위로 나타났을 겁니다. 위에서 다룬 메세지 콜백 큐는 그 방법을 보여줍니다. WebAPI는 실행이 완료되면 콜백함수를 큐로 push합니다. 이벤트 루프는 큐에 있는 이 콜백함수들의 실행을 책임지고, 스택이 빈 경우에 push하는 역할을 합니다. 이벤트 루프의 기본적인 일은 스택과 태스크 큐를 보다가 스택이 비었을 때 큐의 첫 번째 태스크를 스택에 push하는 것입니다. 각 메세지나 콜백은 다른 메세지가 처리되기 전에 완전히 처리됩니다.

while (queue.waitForMessage()) {
  queue.processNextMessage();
}

웹 브라우저에서 메세지들은 이벤트가 발생한 시간에 추가되고, 이벤트 리스너가 이벤트에 장착됩니다. 리스너가 없으면 이벤트는 사라집니다. 그래서 클릭 이벤트 핸들러가 있는 엘리먼트를 클릭했을 때 메세지가 더해집니다. 이 콜백 함수의 호출은 콜스택의 초기 프레임 역할을 하며 자바스크립트는 단일스레드이므로 모든 호출이 반환될 때까지 새로운 메세지를 폴링하거나 처리하는 것이 중단됩니다. 그 다음의 (동기) 함수가 스택의 새로운 콜프레임에 추가됩니다.

다음 파트에서 위 절차대로 코드가 실행되는 시각적인 애니메이션과 더불어 큐에서 우선순위과 높은 비동기 함수의 유형 등 비동기 함수의 여러 유형을 설명하겠습니다. 또한 일부 함수를 수행하는 데 사용되는 제로 딜레이와 같은 hacks에 대해 말씀드리겠습니다.