자바스크립트의 비밀스러운 삶: Promise (Microtasks)
Source: Dev.to
VIP 라인 이해하기: 마이크로태스크 vs. 매크로태스크
Timothy는 작은 도서관 테이블에 앉아 두 장의 종이를 뒤섞고 있었다. Margaret가 다가오자 그는 이마에 찡그린 표정을 지었다.
“이해가 안 돼요,” 그가 조용히 말했다. “코드에 0밀리초를 기다리라고 했어요. 0이요. 그럼 즉시 실행돼야 하지 않나요?”
그는 테이블 위에 코드 조각을 밀어넣었다.
console.log("1. Start");
setTimeout(() => {
console.log("2. Timeout");
}, 0);
Promise.resolve().then(() => {
console.log("3. Promise");
});
console.log("4. End");
“1, 2, 3, 4가 나올 거라고 기대했어요,” Timothy가 설명했다. “아니면 1, 4, 2, 3일 수도 있겠고. 그런데 실제로는 이렇게 나왔어요.”
1. Start
4. End
3. Promise
2. Timeout
“볼륨 6에서 이벤트 루프에 대해 얘기했을 때 기억나요? 우리는 웨이터가 큐를 확인한다고 했었죠.”
“맞아요,” Timothy가 고개를 끄덕였다.
“그런데,” Margaret가 속삭였다, “전체 이야기를 다 말해주지는 않았어요. 큐가 하나만 있는 게 아니에요. 두 개가 있거든요.”
그녀는 칠판에 큰 원을 그리고 The Event Loop라고 라벨을 붙인 뒤, 두 개의 별도 상자를 추가했다:
- 매크로태스크 큐 – 표준 라인.
setTimeout,setInterval, 사용자 상호작용 등을 담는다. - 마이크로태스크 큐 – VIP 라인. Promise,
queueMicrotask,MutationObserver등을 담는다.
“엔진에는 엄격한 규칙이 있어요,” 그녀가 설명했다. “현재 작업(예: 메인 스크립트)이 끝나면, 웨이터가 바로 다음 매크로태스크를 잡는 게 아니라 먼저 VIP 라인을 확인합니다.”
큐가 처리되는 방식
- 마이크로태스크 큐에 있는 모든 마이크로태스크를 실행한다.
- 그 다음에 매크로태스크 큐에서 다음 매크로태스크를 선택한다.
이 규칙 때문에 마이크로태스크는 언제나 대기 중인 매크로태스크보다 먼저 실행된다. 설령 매크로태스크의 타이머가 0 ms로 설정돼 있더라도 말이다.
실행 순서 설명
- **
console.log("1. Start")**와 **console.log("4. End")**는 호출 스택(현재 작업)에서 즉시 실행된다. - **
setTimeout(..., 0)**은 매크로태스크 큐에 배치된다. - **
Promise.resolve().then(...)**은 마이크로태스크 큐에 배치된다.
메인 스크립트가 끝나면 이벤트 루프는 먼저 마이크로태스크 큐를 처리해 “3. Promise”를 출력하고, 그 다음 매크로태스크 큐로 넘어가 “2. Timeout”을 출력한다.
중첩 마이크로태스크와 기아 현상
Margaret는 다음과 같은 중첩 예시로 잠재적인 함정을 보여주었다:
Promise.resolve().then(() => {
console.log("VIP 1");
Promise.resolve().then(() => {
console.log("VIP 2 (Nested)");
// 여기서 계속 Promise를 추가한다면...
});
});
setTimeout(() => console.log("Standard Line"), 0);
이 경우:
- “VIP 1”이 로그된다.
- 중첩된
Promise.resolve().then이 마이크로태스크 큐 앞에 또 다른 마이크로태스크(“VIP 2”)를 추가한다. - 이벤트 루프는 큐가 비워질 때까지 마이크로태스크 처리를 계속한다.
코드가 계속해서 새로운 마이크로태스크를 큐에 넣는다면, 마이크로태스크 큐는 절대 비워지지 않으며 이벤트 루프는 매크로태스크 큐에 도달하지 못한다. 이 상황을 기아(starvation) 라고 부른다—표준 라인(매크로태스크)이 실행 시간을 빼앗겨 브라우저가 멈출 수도 있다.
모범 사례
- 빠르고 긴급한 업데이트가 필요하고 다음 렌더링이나 I/O 작업 전에 반드시 실행돼야 할 경우에 Promise(마이크로태스크)를 사용한다.
- 무한히 많은 마이크로태스크를 큐에 쌓는 일을 피한다; 그렇지 않으면 매크로태스크가 기아 상태에 빠져 성능이 저하될 수 있다.
“시간 문제는 아니에요,” Timothy가 결론지었다. “상태 문제죠.”
“정확히 그렇죠,” Margaret가 답했다. “JavaScript에서 Promise는 엄숙한 약속이에요. 단순 타임아웃보다 우선순위를 갖습니다.”