코딩쌀롱

[JS] 마이크로 태스크, 매크로 태스크, 비동기 실행 흐름 이해하기 본문

개발공부

[JS] 마이크로 태스크, 매크로 태스크, 비동기 실행 흐름 이해하기

이브✱ 2021. 1. 27. 15:39

 

마이크로 태스크, 매크로 태스크, PromiseJobs 얘네들이 도대체 뭔가..했는데 이제 좀 알 것 같다. 기쁘다!🤪

✱ 용어 정리

용어 정리를 하자면, 매크로 태스크 큐는 이벤트 루프를 설명할 때 나오는 태스크 큐(콜백 큐)와 같다. 그리고 PromiseJobs큐는 마이크로 태스크 큐와 같고, PromiseJobs큐는 ECMA, 마이크로 태스크 큐는 V8엔진에서 부르는 이름만 다른 것이다.

 

매크로 태스크 큐 = 태스크 큐 = 콜백 큐
마이크로 태스크 큐 (V8) = PromiseJobs 큐 (ECMA)

 

✱ 마이크로 태스크 큐, 매크로 태스크 큐

이벤트 루프의 동작 원리에 대해서 공부할 때 아래와 같은 자료들을 자주 보게 되는데,

이 때 콜백 큐(태스크 큐)는 하나의 큐처럼 보이지만 실제로는 여러 개의 큐로 이루어져 있다(Node.js 교과서 참고). 얼마나 여러 개 인지까지는 모르겠지만 일단 지금까지 아는 바로는 (매크로)태스크 큐와 마이크로 태스크 큐로 나뉜다는 것. 태스크 큐 부분을 다시 그려보면,

 

 

백그라운드에서 태스크 큐로 보낼 때 어떻게 나뉘어질까? 함수에 따라 나뉜다.

‣ (Macro)task : setTimeout, setInterval, setImmediate

Microtask : Promise callback, process.nextTick, queueMicrotask

 

함수에 따라 콜백함수들이 큐에 들어갔다면 이벤트 루프는 어떤 우선 순위로 콜 스택에 보낼까?

‣ 콜스택 > 마이크로 태스크 > 매크로 태스크

 

다음에 이어지는 예시들로 이해를 해보자!

 

✱ 비동기 실행 흐름 이해하기 (setTimeout-macrotask, Promise-microtask)

아래 코드의 실행 결과를 예상해 보자.

console.log('Start!')

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

실행결과는 아래와 같다.

Start 출력 → Promise의 콜백 백그라운드 거쳐 마이크로태스크 큐로 → End 출력 → 마이크로 태스크 큐에서 콜백 실행

 

Start!
End!
Promise!

 

아래 코드의 실행 결과를 예상해 보자.

console.log('Start!')

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

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

console.log들은 바로 콜스택에서 실행되고, 비동기 함수들(setTimeout, Promise)는 백그라운드를 거쳐 setTimeout은 태스크 큐에, Promise는 마이크로 태스크 큐에 들어가게 된다. 마이크로 태스크 큐가 우선 실행되기 때문에 Promise의 콜백함수를 실행하고, setTimeout의 콜백함수가 마지막으로 실행된다.

 

Start!
End!
Promise!
Timeout!

 

Task1 ~ Task6까지의 함수가 있다고 할 때 실행흐름은?

‣ Task1 : 콜스택에 바로 더해질 함수

Task2, Task3, Task4 : 마이크로 태스크 (Promise, queueMicrotask..)

Task5, Task6 : (매크로)태스크 (setTimeout, setImmediate..)

 

위 함수들의 실행 흐름은 아래와 같다.

이미지 출처(참고에 링크 있음): https://dev.to/lydiahallie/javascript-visualized-promises-async-await-5gke 

 

✱ 비동기 실행 흐름 이해하기 (async/await)

아래 코드의 실행 결과를 예상해 보자.

const 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!')  

1) console.log는 바로 콜스택에 더해지고 실행, 출력!

2) myFunc()를 호출하고 함수 body 실행. 첫 번째 줄의 console.log 메서드 콜스택에 더해져 출력!

3) myFunc 함수의 두 번째 줄에서 one함수 실행, 'One!' 문자열 리턴

4) 엔진이 await 키워드를 보고 async 함수가 중단!!!✋🏼🤚🏼

      async 함수의 나머지는 마이크로 태스크 큐로 들어감

5) async 함수에서 나와서(jumps out) async함수를 호출한 실행 컨텍스트로 돌아가서 마저 실행

      위의 예시에서는 전역 컨텍스트로 돌아가서 함수 실행

6) console.log 콜스택에 더해져 실행, 출력!

7) 실행 컨텍스트에 task가 더 이상 없는 것을 확인하고 이벤트루프가 마이크로 태스크 큐를 확인

8) async 함수인 myFunc함수가 콜스택으로 옮겨지고, 이전에 중단된 곳에서 이어서 실행

9) 내부의 console.log 실행, 출력!

 

이러한 과정으로 출력 결과는 아래와 같다. (과정을 더 이해하고 싶다면 참고 링크 블로그에서 gif로 확인 가능)

Before function!
In function!
After function!
One!

 

위의 과정으로 Promise.then과 async 함수의 차이점을 알 수 있다

async 함수에서는 await를 만나면 함수가 중단된다.

 Promise의 body는 계속 실행중인 상태다.

 

 

아래 코드의 실행 결과를 예상해 보자. 

DD님이 공유해주신 이 코드를 시작으로 이 공부를 시작했다. 덕분에 많이 배웠습니다🙏🏼

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

function b() {
    console.log('b1');
    c();
    console.log('b2');
}
  
async function c() {
    console.log('c1');
    await d();
    console.log('c2');
}
  
function d() {
    return new Promise(resolve => {
      console.log('d1');
      resolve();
      console.log('d2');
    })
    .then(() => console.log('then!'));
}

a();

1) a 함수 호출, console.log 실행, 출력 → a1

2) b 함수 호출, console.log 실행, 출력 → b1

3) c 함수 호출, console.log 실행, 출력 → c1

4) d 함수 호출, 첫 번째 console.log 실행, 출력 → d1 (비동기X)

5) 두 번째 console.log 실행, 출력 → d2 (비동기X)

6) .then 콜백은 백그라운드를 거쳐 마이크로 태스크 큐에 쌓임

7) d 함수 호출 완료 후 await를 만나고 async 함수 c는 중단!!!✋🏼🤚🏼

      async 함수의 나머지는 마이크로 태스크 큐에 쌓임

8) c 함수를 호출한 실행 컨텍스트(b함수)로 돌아가서 console.log 실행, 출력 → b2

9) b 함수를 호출한 실행 컨텍스트(a함수)로 돌아가서 console.log 실행, 출력 → a2

10) 콜스택이 모두 비워지고, 이벤트 루프가 마이크로 태스크 큐를 확인

       then 콜백, async 함수가 쌓여있음

11) .then 콜백 실행, console.log 출력 → then!

12) async 함수 중단된 곳부터 이후로 실행,  console.log 출력 → c2

 

이러한 과정으로 출력 결과는 아래와 같다. 

a1
b1
c1
d1
d2
b2
a2
then!
c2

 

 

 

이상 끝!🥳🎶🎶

 

 


참고📚

gif로 비동기 실행 흐름을 보여주는 블로그 (완전 추천👍🏼)

책 - 조현영 〔Node.js 교과서〕

 

학습 단계로 잘못된 정보가 있을 수 있습니다. 잘못된 부분에 대해 알려주시면 곧바로 정정하도록 하겠습니다🙂

 

 

'개발공부' 카테고리의 다른 글

[JS] input type="file" 파일 읽기  (0) 2021.02.07
[HTML¦CSS] input type='file'로 파일 입력 받기  (0) 2021.02.07
[JS] Promise → async/await로 바꾸기  (0) 2021.01.26
[JS] Promise  (0) 2021.01.26
Comments