본문 바로가기
기타 미분류

[JavaScript] 자바스크립트 동작 원리와 비동기 처리 방식

by domo7304 2021. 8. 3.

1. 브라우저가 자바스크립트를 실행하는 방식

자바스크립트는 단일 쓰레드 (single-thread) 방식으로 동작한다. 단일 쓰레드로 동작한다는 것은 하나의 작업만을 처리할 수 있다는 것을 의미한다. 하지만 우리가 웹 애플리케이션을 사용하다보면 동시에 여러 작업이 이뤄지는 것 같은 느낌을 받는데, 이렇게 자바스크립트의 동시성을 지원하는 것이 바로 이벤트 루프이다.

뭔가 아래 사진에서부터 처음보는 게 한가득이지만, 하나하나 어떤 역할인지 알아보고자 한다.

자바스크립트의 동작 환경 - 모던 자바스크립트 Deep Dive

위 이미지에서 자바스크립트 엔진은 살구색으로 칠해진 저 두 부분만을 의미한다. 구글의 V8엔진을 비롯한 대부분의 자바스크립트 엔진은 크게 2개의 영역으로 나뉜다. 

Call Stack (호출 스택) 
함수가 호출되면 호출된 함수는 call stack에 순차적으로 쌓이며, 자바스크립트는 단 하나의 call stack만을 갖기 때문에 현재 진행중인 작업이 완료되기 이전에는 다른 작업이 실행될 수 없다. 한마디로 해야할 일이 쌓이는 곳.

Heap
동적으로 생성되는 객체가 할당되는 영역. 메모리 할당이 일어난다. heap의 자세한 동작원리는 모르지만, MDN 문서에서도 '객체들이 heap에 할당된다'고 간단히 나와있다.

위 이미지에서 볼 수 있듯이 자바스크립트 엔진 자체는 Call Stack에 쌓이는 작업을 순차적으로 실행하기만 한다. 이 때 작업이 동시에 실행되는 것처럼 느끼는 이유는 바로 비동기 요청때문인데, 이 비동기 요청의 처리는 자바스크립트 엔진을 구동하는 환경인 브라우저가 담당한다.

Event Queue(Task Queue)
비동기 요청 작업들을 보관하는 장소, 비동기 처리 요청에는 콜백 함수, 비동기식 이벤트 핸들러, Timer함수 (setTimeout(), setInterval()) 등 Web API요청이 있다. 이 작업들은 수행이 끝난 후에 Event Queue로 가서 대기하게 된다. 이곳에 임시로 보관된 작업들은 Call Stack이 완전히 비워질 때 이벤트루프에 의해 Call Stack으로 이동되어 실행이 마무리 된다.

Web API : 어떤일을 하는지만 간단히 살펴보자면, 타이머를 사용하는일, HTTP요청을 보내는 일, 파일로부터 데이터를 읽어오는 일 등 시간을 소요하는 작업들을 말한다. (콜백함수, Timer 함수 등)

Event Loop
Call Stack 내에서 현재 실행 중인 작업이 있는지, Event Queue에 보관 중인 작업이 있는지 반복하여 확인한다. 만약 Call Stack이 비었고, Event Queue에 작업이 존재한다면 해당 작업을 Call Stack으로 옮겨 실행하도록 한다.

그렇다면 비동기 요청이란 무엇일까

 

2. 비동기(asynchronous) 요청, '비동기' 방식이란?

자바스크립트의 동작 - 모던 자바스크립트 Deep Dive

위 이미지를 보면 비동기식 요청 처리가 어떻게 진행되는지 볼 수 있다.

  1. 자바스크립트 코드를 Call Stack에서 실행.
  2. setTimeout() 등 Web API 비동기 요청이 들어오면 수행 후 Event Queue에서 차례를 기다리게 됨.
  3. Call Stack에 있던 작업이 모두 끝나고 나면 Event Loop에 의해 queue에 있던 작업이 Call Stack으로 이동되어 마무리.

간단히 코드로 살펴보자

비동기 요청을 모른다면 해당 코드를 보고 당연히

  1. 1 출력
  2. 2초 기다린 뒤 2 출력
  3. 3, hello 출력

이렇게 될 거라 생각할 수 있다. 하지만, 앞서 말한 것처럼 setTimeout() 과 같은 Web API 비동기 요청은 바로 실행되는 것이 아니라 브라우저에게 비동기 요청을 보내놓고 동기적으로 나머지 코드들을 실행한 다음 Call Stack의 작업이 모두 완료될 경우 Event Loop에 의해 비동기 작업이 완료된다. 이 때문에 숫자 2가 가장 나중에 출력되는 것이다.

해당 코드를 통해 조금 더 자세히 살펴볼 수 있다. 위 코드 9번째 line에서부터 실행 결과를 예측해보자

  1. 동기적으로 1 출력
  2. setTimeout()은 비동기 요청이므로 일단 브라우저에게 비동기 요청
  3. 동기적으로 3 출력
  4. 동기적으로 hello 출력
  5. 이 역시 비동기 요청이므로 일단 브라우저에게 비동기 요청
  6. 이제 동기적 요청이 더 이상 없어 Call Stack이 모두 비었을테니, 비동기 요청인 line 10, 13중 먼저 실행이 종료된 2 출력 후, 그 다음 비동기 요청인 async callback 출력 (동기적 요청이 끝나고 나서 비동기 요청들의 경우 line순서대로 실행되는 것이 아니라 그냥 일이 먼저 끝난 순서대로 나오게 된다!)

위와 같이 동작을 할 것이고, 결과는 우측 콘솔에서 확인할 수 있다.

요약하자면,

  1. 자바스크립트는 브라우저의 Event loop를 통해 비동기적으로 작업을 수행함으로써 멀티태스킹을 지원하는 것처럼 동작할 수 있다.
  2. 이 때 코드 순서대로 실행되는 것이 동기적 요청이고, 동기적 요청이 끝나고 나서야 실행이 종료되는 순서대로 처리되는 것이 비동기 요청이다.

비동기적 요청은 시간이 걸리는 작업들을 Web API를 통해 분리하여 처리함으로써 html, css 등 동기적으로 실행되는 코드들이 버벅이거나 실행이 늦어지는 일을 방지할 수 있다는 장점을 갖는다.

하지만 비동기적 요청 특히 콜백 함수를 몇 개씩 중첩하여 사용하는 등 적절하지 못하게 사용하게 되면 '콜백 지옥' 에 빠질 수 있는데, 이 다음으로 콜백함수의 중첩이 권장되지 않는 이유, 이러한 코드를 풀어주기 위해 등장한 문법인 promise, async/await에 대해 알아보고자 한다.

 

댓글