호출 스택

호출 스택은 함수의 호출을 스택 형식으로 나타낸 것이다. (LIFO 구조라서 스택으로 불린다)

function first() {
  second();
  console.log('첫 번째');
}
function second() {
  third();
  console.log('두 번째');
}
function third() {
  console.log('세 번째');
}

위 코드에 대한 실행 결과는

세 번째
두 번째
첫 번째

이다. 이는 호출 스택을 통해 나타낼 수 있다.

-----------------
|    third()    |
-----------------
-----------------
|    second()   |
-----------------
-----------------
|    first()    |
-----------------
-----------------
|    anonymous  |
-----------------

여기서 Anonymous는 가상의 전역 컨텍스트이다.

비동기 함수 호출

그렇다면 비동기 호출 방식 코드를 보겠다.

function run() {
  console.log('3초 후 실행');
}
console.log('시작');
setTimeout(run, 3000);
console.log('끝');

해당 코드는

시작
끝
3초 후 실행

으로 실행된다.

이는 호출 스택 + 이벤트 루프로 설명할 수 있다.

setTimeout(run, 3000); 이 실행된다면 호출 스택에 들어간 후 실행되는데, 비동기함수이기 때문에 백그라운드로 넘어가서 동시에 실행이 된다.

호출 스택                  백그라운드
---------------------     -----------------------
|                   |     |                     |
|                   |     |  ---------------    |
|                   |     | | 타이머(run, 3초) | |
|  ---------------  |     |  ---------------    |
|  |   anonymous |  |     -----------------------
|  ---------------  |
---------------------
 
태스크 큐
------------------------------------------
|                                        |
|                                        |
|                                        |
------------------------------------------

백그라운드에서 작업이 끝난다면 태스크 큐로 이동하게 된다.

호출 스택이 비게 된다면, 태스크 큐에 있는 함수가 호출 스택으로 넘어가서 실행이 된다.

호출 스택                  백그라운드
---------------------     -----------------------
|                   |     |                     |
|                   |     |                     |
|                   |     |                     |
|                   |     |                     |
|                   |     -----------------------
|                   |
---------------------
 
태스크 큐
------------------------------
|  -------                   |
|  | run |                   |
|  -------                   |
------------------------------

백그라운드로 가는 대표적인 비동기 함수는 아래와 같다.

  • 타이머
  • 파일 시스템 작업(I/O)
  • 네트워크 요청
  • 이벤트 리스너
  • 데이터베이스 작업
  • 무거운 연산(암호화, 압축)

백그라운드에서는 동기적으로 실행되지 않기 때문에 어떤 함수가 먼저 끝날지는 모른다.

Promise

추가적으로 아래와 같은 코드의 실행 결과는 다음과 같다.

console.log('시작');
 
setTimeout(() => {
  console.log('setTimeout 실행 (매크로태스크)');
}, 0);
 
Promise.resolve().then(() => {
  console.log('Promise 실행 (마이크로태스크)');
});
 
console.log('끝');
시작
끝
Promise 실행 (마이크로태스크)
setTimeout 실행 (매크로태스크)

Promise는 우선순위가 높기 때문에 태스크 큐에 먼저 도착하지 않았더라도 가장 먼저 실행이 된다.