queueMicrotask() 사용하여 스케쥴링하기
microtask queue & macrotask queue
https://developer.mozilla.org/ko/docs/Web/API/HTML_DOM_API/Microtask_guide
- 마이크로태스크가
queueMicrotask()
를 통해 더 많은 마이크로태스크를 큐에 추가하는 경우, 이렇게 새롭게 추가된 마이크로태스크 또한 다음 태스크 실행 전에 모두 실행된다. - 이벤트 루프는 대기열이 텅 빌때 까지 계속해서 마이크로태스크를 호출한다
마이크로태스크를 사용해야할 때
- 이벤트 처리기, 타임아웃과 인터벌, 기타 콜백이 호출되기 전에 결과를 포착 또는 검증하거나 정리 작업등을 수행하는 상황
- 마이크로태스크를 사용하는 이유는, 결과나 데이터가 동기적으로 사용가능하더라도 태스크 순서의 일관성을 유지하고, 동시에 사용자가 인지할 수 있는 수준의 연산 지연을 줄이는 것에 있다.
조건적 프로미스 사용에 있어서 실행 유지 😲
if-else
선언문 중에 한 절에서만 프로미스를 사용하고, 다른 절에서는 사용하지 않는 것
customElement.prototype.getData = (url) => {
if (this.cache[url]) {
this.data = this.cache[url];
this.dispatchEvent(new Event("load"));
} else {
fetch(url)
.then((result) => result.arrayBuffer())
.then((data) => {
this.cache[url] = data;
this.data = data;
this.dispatchEvent(new Event("load"));
});
}
};
- 위 코드의 문제는
if..else
선언문의 한 분기에서는 태스크를 사용하고, else 절에서는 프로미스를 사용하여 마이크로태스크를 사용하므로 실행 순서가 달라진다.
위 함수를 아래와 같은 환경에서 두번 실행해보면
element.addEventListener("load", () => console.log("Loaded data"));
console.log("Fetching data...");
element.getData();
console.log("Data fetched");
캐시 없을 때 (else 절로 넘어감)
Fetching data...
Data fetched
Loaded data
캐시 있을 때 (if 절로 넘어감)
Fetching data...
Loaded data
Data fetched
- 이 코드의 생성이 끝난 후, 어떤 때에는 요소의 data 속성이 설정되고, 다른 때에는 아니라는 점.
- 이를 개선해보자
customElement.prototype.getData = (url) => {
if (this.cache[url]) {
queueMicrotask(() => {
this.data = this.cache[url];
this.dispatchEvent(new Event("load"));
});
} else {
fetch(url)
.then((result) => result.arrayBuffer())
.then((data) => {
this.cache[url] = data;
this.data = data;
this.dispatchEvent(new Event("load"));
});
}
};
- 위에서 두 분기 모두 data 속성 설정과, load 이벤트 발생을 마이크로태스크 안에서 처리하여 서로 균형을 맞춤!
- if 에서는
queueMicrotask
로, else에서는 fetch Promise 로
- if 에서는
계산 배칭 😳👍
- 마이크로태스크를 사용하면 다양한 출처로부터 다수의 요청을 하나의 배치로 묶어서 같은 유형의 작업을 위한 반복 호출로 인해 발생하는 부하를 피할 수 있다.
const messageQueue = [];
let sendMessage = (message) => {
messageQueue.push(message);
if (messageQueue.length === 1) {
queueMicrotask(() => {
const json = JSON.stringify(messageQueue);
messageQueue.length = 0;
fetch("url-of-receiver", json);
});
}
};
- sendMessage()를 호출하면 주어진 메시지는 우선 messageQueue 배열에 들어간다.
- 방금 배열에 추가한 메시지가 첫번째 메시지라면 sendMessage 는 메시지 전송 배치를 예약한다. (아직 콜스택 내의 실행할게 남아있으므로 바로 보내진 않음)
- 그리고 sendMessage 가 계속 호출되고 여러 메시지들을 messsageQueue 배열에 넣겠지만 마이크로태스크큐로 배치를 예약하진 않는다 (길이 검사로인해)
- 그리고 이렇게 실행컨텍스트가 끝난 후, 마이크로태스크 실행 시점이 오면..! 마이크로태스크는 우선
JSON.stringify
로 메시지를 JSON으로 바꾸고, 배열을 비운다. - 이를 통해 이벤트 루프의 같은 주기에서 수행한
sendMessage()
다수의 호출은 하나의fetch
에 모이게 되고, 타임아웃이나 통신 지연을 피할 수 있음.