🤹🏻‍♀️ Javascript

[JS] Microtask

ji-hyun 2022. 5. 8. 21:23

마이크로태스크

프라미스 핸들러(.then/catch/finally) 는 항상 비동기적으로 실행됩니다.

프라미스가 즉시 이행되더라도 .then/catch/finally  아래에 있는 코드는 이 핸들러들이 실행되기 전에 실행됩니다.

 

const promise = Promise.resolve();
promise.then(() => console.log("프라미스 성공"));
console.log("코드 종료");

 

예시를 실행하면 '코드 종료’가 먼저, '프라미스 성공!'이 나중에 출력되는 것을 볼 수 있습니다.

프라미스는 즉시 이행상태가 되었는데도 말이죠. 뭔가 이상하네요.

 .then이 나중에 트리거 되었을까요? 그 이유에 대해 알아봅시다.

 

 

 

 

 

 

 

마이크로태스크 큐

비동기 작업을 처리하려면 적절한 관리가 필요합니다.

이를 위해 ECMA 에선 PromiseJobs 라는 내부 큐 를 명시합니다.

V8 엔진에선 이를 '마이크로태스크 큐(microtask queue)' 라고 부르기 때문에 이 용어가 좀 더 선호됩니다

 

 

 

 

명세서의 설명을 살펴봅시다.

 

  • 마이크로태스크 큐는 먼저 들어온 작업을 먼저 실행합니다 (FIFO, first-in-first-out).
  • 실행할 것이 아무것도 남아있지 않을 때만 마이크로태스크 큐 에 있는 작업이 실행되기 시작합니다.

 

 

 

요약하자면, 어떤 프라미스가 준비되었을 때 이 프라미스의 .then/catch/finally 핸들러가 큐에 들어간다고 생각하시면 됩니다.

이때 핸들러들은 여전히 실행되지 않습니다.

현재 코드에서 자유로운 상태가 되었을 때에서야 자바스크립트 엔진은 큐에서 작업을 꺼내 실행합니다.

 

 

위 예시에서 '코드 종료’ 가 먼저 출력되는 이유가 여기에 있습니다.

 

 

프라미스 핸들러는 항상 내부 큐를 통과하게 됩니다.

 

여러 개의 .then/catch/finally를 사용해 만든 체인의 경우, 각 핸들러는 비동기적으로 실행됩니다. 큐에 들어간 핸들러 각각은 현재 코드가 완료되고, 큐에 적체된 이전 핸들러의 실행이 완료되었을 때 실행됩니다.

 

 

 

 

 

 

그렇다면 '프라미스 성공!'을 먼저, '코드 종료’를 나중에 출력되게 하려면 어떻게 해야 할까요? 실행 순서가 중요한 경우엔 이런 요구사항이 충족되도록 코드를 작성해야 합니다.

 

방법은 아주 쉽습니다. .then을 사용해 큐에 넣으면 됩니다.

 
 
 
 
 

 

이제 의도한 대로 순서가 변경되었습니다.

 

 

 

 

 

처리되지 못한 거부

에러를 적절하게 모두 처리해주지 못한 경우, 즉 예상치 못한 곳에서 에러 핸들링을 못해줬을 경우, "처리되지 못한 거부" 라고 하며 이는 마이크로태스크 큐 끝에서 프로미스 에러가 처리되지 못할 때 발생합니다.

 

let promise = Promise.reject(new Error("프라미스 실패!"));
promise.catch(err => alert('잡았다!'));

// 에러가 잘 처리되었으므로 실행되지 않습니다.
window.addEventListener('unhandledrejection', event => alert(event.reason));

 

 

정상적인 개발자라면 에러가 생길 것을 대비하여 catch 를 추가해 에러를 처리하겠지만 에러를 처리하는 것을 잊은 경우 엔진은 마이크로 태스크 큐가 빈 이후에 unhandledrejection 이벤트를 트리거 합니다.

 
 
let promise = Promise.reject(new Error("프라미스 실패!"));

// 프라미스 실패!
window.addEventListener('unhandledrejection', event => alert(event.reason));​

 

 

그런데 만약 아래와 같이 setTimeout 을 이용해 에러를 나중에 처리하면 어떤 일이 생길까요?

 

let promise = Promise.reject(new Error("프라미스 실패!"));
setTimeout(() => promise.catch(err => alert('잡았다!')), 1000);

// Error: 프라미스 실패!
window.addEventListener('unhandledrejection', event => alert(event.reason));

 

예시를 실행하면 "프라미스 실패!" 가 먼저, "잡았다!" 가 나중에 출력되는 걸 확인할 수 있습니다.

 

마이크로태스크 큐에 대해 몰랐다면 "에러를 잡았는데도 왜 unhandledrejection 핸들러가 실행되는 거지?"라는 의문을 가졌을 겁니다.

 

 

★ unhandledrejection은 마이크로태스크 큐에 있는 작업 모두가 완료되었을 때 생성됩니다.

엔진은 프라미스들을 검사하고 이 중 하나라도 ‘거부(rejected)’ 상태이면 unhandledrejection 핸들러를 트리거 하죠. 이로써 앞선 의문이 자연스레 해결되었습니다.

 

위 예시를 실행하면 setTimeout을 사용해 추가한 .catch 역시 트리거 됩니다. 다만 .catch는 unhandledrejection 이 발생한 이후에 트리거 되므로 프라미스 실패! 가 출력됩니다.

 

 

 

 

 

 

요약

모든 프라미스 동작은 ‘마이크로태스크 큐’(ES8 용어)라 불리는 내부 ‘프라미스 잡(promise job)’ 큐에 들어가서 처리되기 때문에 프라미스 핸들링은 항상 비동기로 처리됩니다.

 

따라서 .then/catch/finally 핸들러는 항상 현재 코드가 종료되고 난 후에 호출됩니다. (= 마이크로태스크 큐에 있는 것들을 호출)

 

 

 

 

 

 

https://ko.javascript.info/microtask-queue#ref-365

 

마이크로태스크

 

ko.javascript.info

 

 

 

 

'🤹🏻‍♀️ Javascript' 카테고리의 다른 글

lodash compact  (0) 2022.05.09
[JS] 검색 엔터키  (0) 2022.05.09
async, await, 즉시 실행 함수  (0) 2022.04.26
클로저의 활용 사례  (0) 2022.04.24
async, await  (0) 2022.04.23