마이크로태스크
프라미스 핸들러(.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을 사용해 큐에 넣으면 됩니다.
![](https://blog.kakaocdn.net/dn/KaiFY/btrBxbRVb8a/NOkBkSNuHZPNg01o7vmvU1/img.png)
이제 의도한 대로 순서가 변경되었습니다.
처리되지 못한 거부
에러를 적절하게 모두 처리해주지 못한 경우, 즉 예상치 못한 곳에서 에러 핸들링을 못해줬을 경우, "처리되지 못한 거부" 라고 하며 이는 마이크로태스크 큐 끝에서 프로미스 에러가 처리되지 못할 때 발생합니다.
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 |