Eventloop 와 async/await

Chang-__-·2023년 3월 13일
8

Call Stack

기본적으로 자바스크립트 엔진에는 Call Stack 이라는 기념이 있다.
다른 언어와 마찬가지로 함수를 호출하면 Call Stack 에 쌓이는데 물론 Stack 이니 LIFO 구조로 되어있다.
함수가 값을 return 하게 되면 Call Stack 에서 제거된다.

그림을 보면서 이해를 하면 더 쉬울듯 하다.

greet 함수가 먼저 stack 에 쌓인 뒤에 hello 를 return 하고 제거가 되었다.
respond 함수 같은경우에는 setTimeout이 걸려있다. setTimeout 은 WebAPI 에 추가가 되고, call stack 에도 respond 와 setTimeout이 추가가 된다.

Stack 에서 setTimeout => responsd 순으로 호출을 한다. setTimeout 이 호출되는 시기엔 Web API 에서 timer가 1초동안 돌고 Task Queue로 들어간다.

이렇게 Task Queue 로 들어가게 되면 Event Loop 에서 Task Queue에 있는 함수를 Call Stack으로 옮길 것이다. 하지만 만약에 Call Stack에서 실행중인 코드가 있다면 Event Loop는 Task Queue 에 있는 함수를 Call Stack으로 옮기지 않는다.

이벤트 루프에서 queue에 있는 함수를 Call Stack 으로 옮기고 Call Stack 에서 호출이 된다.

그렇다면 아래코드는 어떻게 실행이 될까?

const foo = () => console.log('First');
const bar = () => setTimeout(() => console.log('Second'), 500);
const baz = () => console.log('Third');

bar();
foo();
baz();

결론은 First => Third => Second 순으로 출력이 된다.

1. bar 함수가 Call Stack에 들어간다.
2. setTimeout이 있어서 bar 함수는 Call Stack에서 제거되고 Web API 로 들어간다.
3. Web API 가 실행되는 동안 foo 가 호출되어서 Call Stack에 쌓인다. => First 호출
4. baz가 Call Stack 에 쌓인다. => Third 호출
5. Web API 에서는 Queue 에 setTimeout의 콜백을 넣는다.
6. Event Loop 에서는 Call Stack이 비어있음을 확인하고, Queue 에 있던 콜백을 Call Stack에 넣는다.

MicroTask Queue

console.log('Start!');

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

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

console.log('End!');

위 코드를 실행하면 어떻게 될까?
Promise! 가 먼저 찍힐까? 아니면 Timeout! 이 먼저 찍힐까?
정담은 Promise 가 먼저 찍힌다. 왜냐하면 Promise 는 MicroTask Queue 에 들어가는데 일반 Task Queue 보다 더 높은 우선순위를 가진다. setTimeout은 Task Queue에 들어가므로 MicroTask Queue 보다 우선순위가 낮다. 따라서 Promise가 먼저 찍히는데 시각화하면 아래와 같이 동작한다.

console.log 가 Call Stack 에 맨 처음 들어와서 Start 가 로그에 찍히고 Call Stack 에서 사라진다.

setTimeout 함수가 Call Stack에 호출되서 이를 Web API 에 넘긴다.

setTimeout 에서 호출된 함수는 Task Queue 로 옮겨진다.
Promise.resolve 와 Promise.then 의 콜백이 Call Stack으로 들어온다. Promise.then 의 콜백은 MicroTask Queue 로 옮겨지고 Promise는 Call Stack 에서 반환된다.

Call Stack 에 console.log 가 들어와서 로그를 찍고 Call Stack 에서 반환 된다.

Event Loop 는 Call Stack 이 비어있는것을 확인하고 MicroTask Queue 부터 확인한다.
MicroTask Queue 에 있는 콜백을 Event Loop 가 Call Stack으로 옮긴다.
Promise! 가 로그에 찍힌다.


마찬가지로 Event Loop 가 Call Stack이 비는것을 확인하고 Task Queue 에 있는 콜백을 Call Stack으로 옮겨서 코드를 실행한다.

Async/Await

async/await 문법은 ES8에서 적용 되었고, Promise를 더 쉽게 다룰 수 있다.

Promise.resolve('Hello!');

// 위 코드는 아래 코드와 같다.

async function greet() {
 return 'Hello!'
}

그렇다면 아래의 코드는 어떻게 실행이 될까?

onst one = () => Promise.resolve('One!');

async function myFunc() {
  console.log('In function!');
  const res = await one();
  console.log(res);
}

console.log('Before function!');
myFunc();
console.log('After function!');

먼저 Before function! 이 Call Stack 에 들어가서 먼저 찍힐 것이다.

위에 있는 사진 처럼 myFunc 가 실행이 되는데, myFunc 첫번째 줄의 In function! 이 찍힌다.

myFunc가 콜스택에 담기고, await one() 을 만나서 MicroTask Queue 로 이동한다.

동기 함수인 console.log('After function!')이 찍힌다.

Call Stack 이 비어있는 것을 확인 한 Event Loop는 myFunc 를 실행한다.

그럼 아래의 코드는 어떻게 실행이 될까?

function a() {
    console.log('a1');
    b();
    console.log('a2');
}

function b() {
    console.log('b1');
    c();
    console.log('b2');
}
  
async function c() {
    console.log('c1');
    setTimeout(() => console.log('setTimeout'), 0);
    await d();
    console.log('c2');
}

function d() {
    return new Promise(resolve => {
      console.log('d1');
      resolve();
      console.log('d2');
    })
    .then(() => console.log('then!'));
}

a();

출력 결과부터 먼저 보면

a1
b1
c1
d1
d2
b2
a2
then!
c2
setTimeout

이렇게 나온다.

이 코드는 다음과 같이 실행 된다.

1. a 호출 => a1 출력
2. b 호출 => b1 출력
3. c 호출 => c1 출력
4. setTimeout 이 Task Queue에 쌓임
5. d 호출 => d1 출력 (이 때 new Promise 안에 있는 콜백은 비동기가 아님)
6. d2 출력 (마찬가지로 Promise 안에 있는 콜백은 비동기가 아님)
7. then 함수는 MicroTask Queue에 쌓임
8. d 함수 호출 완료 후 await 을 만나 c 함수는 MicroTask Queue에 쌓임
9. c 함수를 호출한 컨텍스트(b 함수)로 돌아가서 b2 출력
10. b 함수를 호출한 컨텍스트(a 함수)로 돌아가서 a2 출력
11. Call Stack 이 모두 비워지면 .then, async 함수가 쌓여 있음 
12. 이 때 이벤트 루프는 MicroTask Queue 에 먼저 들어간 .then 함수를 실행 then! 출력
13. 그 다음 MicroTask Queue에 있는 c 함수 실행(중단된 지점부터) c2 출력
14. Event Loop 가 Task Queue를 확인 setTimeout 의 콜백이 쌓여 있어서 setTimeout을 Call Stack 으로 이동시켜 setTimeout을 출력

0개의 댓글