안녕하세요 웨비 여러분 곽지욱입니다. 2주차 과제로 자바스크립트 Scheduler API 를 소개하는 글을 작성해봤습니다.
웹 혹은 웹앱을 개발할 때 프로젝트의 복잡성이 높아질 수록 메인 스레드의 효율적인 관리는 JS를 사용한다면 성능 최적화의 핵심이 되는 부분입니다.
그 동안 보편적으로 setTimeout(0)
과 같은 기법으로 브라우저에게 작업을 잠시 양보해가며 작업을 쪼개서 실행해왔으나 이러한 방법은 우선순위나 세밀한 제어에 한계가 분명히 존재합니다.
이 한계를 극복하기 위해서 등장한 것이 바로 JS의 Scheduler API 입니다.
Scheduler API란?
Scheduler API는 작업의 우선순위를 명시하고, 메인 스레드에서 실행 타이밍을 보다 정밀하게 조절할 수 있도록 돕는 표준 API입니다.
기존 방식의 한계점
setTimeout(() => {
processNextBatch();
}, 0);
위와 같은 방식은 브라우저의 Event Loop 에 넘겨 비동기 방식으로 처리하는 것이 가능하지만
- 작업의 중요도 구분 불가
- 백그라운드에서 실행할 작업과 사용자 입력 사이의 구분 부족
- 체계적인 우선순위 불가능
과 같은 단점을 가지고 있습니다.
Scheduler API 의 핵심기능
1. scheduler.yield()
- 현재 작업 흐름을 일시적으로 중단하고, 브라우저에게 컨트롤을 양보할 수 있습니다. 일종의 비동기 함수의 일종이며, 현재 작업을 중단하고, 브라우저가 사용자 이벤트 처리나 렌더링 같은 작업을 먼저 하도록 잠깐 쉬어주는 역할을 합니다.
function heavyTask() {
doWorkChunk1();
setTimeout(() => {
doWorkChunk2();
}, 0); // 잠깐 쉬었다 다시 실행
}
async function processLargeData() {
for (let i = 0; i < 1000; i++) {
processItem(i);
if (i % 100 === 0) {
await scheduler.yield(); // 메인 스레드 양보
}
}
}
- 이렇듯 현재 실행을 중단하고, 다른 중요한 작업이 있으면 먼저 처리하게 한 뒤 다시 돌아옵니다.
2.scheduler.postTask()
- 작업을 명시적으로 등록하고 우선순위와 취소 신호 등을 함께 설정할 수 있습니다.
await scheduler.postTask(() => {
processNextBatch();
}, { priority: 'background' });
작업 우선순위 같은 경우는 Scheduler API 에서 세 가지 작업 우선순위를 지원합니다.
- user-blocking : 사용자 입력 처리 UI업데이트 등 즉시 처리해야 하는 작업
- user-visible : 사용자게에 곧 보여질 데이터 등 중요하지만 긴급하지 않은 작업
- background : 분석 프리페칭 등 사용자 경험에 영향 없는 작업
실제 활용 예시
async function processData(data) {
const controller = new TaskController({ priority: 'background' });
try {
await scheduler.postTask(() => heavyComputation(data), {
signal: controller.signal,
priority: 'background'
});
} catch (error) {
if (error.name !== 'AbortError') {
console.error('Processing failed:', error);
}
}
}
heavyComputation
작업 순위를 'background'로 설정하였기에 브라우저가 다른 중요한 작업(렌더링, 사용자 입력 등) 먼저 처리한 후 실행되도록 유도합니다.- 사용자 인터랙션을 막지 않으면서 무거운 작업을 진행하고 싶을 때, 혹은 중간에 작업을 취소할 가능성이 있을 때 사용할 수 있습니다.
작업 중 우선순위 변경
- 사용자 행동(이벤트)에 따라서 작업 중요도가 변하는 케이스도 존재할 수 있습니다. 이 때 TaskController 를 통해 동적으로 우선순위를 변경할 수 있습니다.
const controller = new TaskController({ priority: 'background' });
controller.signal.addEventListener('prioritychange', (e) => {
console.log(`Changed: ${e.previousPriority} -> ${e.target.priority}`);
});
scheduler.postTask(async () => {
await processInitialData();
controller.setPriority('user-visible');
await processCriticalData();
}, { signal: controller.signal });
- 작업 도중에
controller.setPriority()
로 우선순위를 background -> user-visible로 동적으로 변경하는 코드입니다. - 초기에는 우선순위가 background로 설정되었으나, 우선순위가 바뀌었을 때 감지하는 이벤트 리스너인
controller.signal.addEventListener
를 설정해두었기에 - processInitialData()가 실행되고 우선순위가 변경됩니다. 이 변경이 이루어지면서 브라우저가
controller
작업을 더 빠르게, 사용자에게 잘 보이는 작업으로 인식하게끔 만듭니다. - 예를 들면 초기에 리소스를 아끼고 나중에 중요한 데이터를 처리해야할 때 백그라운드로 작업을 시작하다가, 중간에 우선순위를 올리는 방법을 사용할 때 고려할 수 있습니다.
작업 중단(abort)
const controller = new TaskController();
scheduler.postTask(async () => {
const res = await fetch('/api/data');
return res.json();
}, { signal: controller.signal }).catch((e) => {
if (e.name === 'AbortError') {
console.log('작업이 중단되었습니다.');
}
});
controller.abort(); // 언제든 중단 가능
- 이 코드의 핵심은 예약된 작업을 중간에 중단할 수 있고, 중단된 경우에 AbortError 를 이용해서 적절한 에러처리를 수행할 수 있다는 점이에요
- 브라우저의 스케줄러 큐에 작업을 예약하고 내부에서 fetch를 진행합니다. 이때 signal : controller.signal 을 전달해서 작업이 controller.abort() 를 호출할때 중단될 수 있도록 세팅합니다.
- 만약 abort()에 의해 중단된다면 AbortError가 발생하고 catch 블록이 실행됩니다.
언제 Scheduler API를 사용해야 할까
- 긴 연산을 메인 스레드를
차단
하지 않고 나눠 처리하고 싶을 때 - 사용자의 상호작용에 영향을 주지 않도록 작업의 중요도를 조절하고 싶을 때
- 여러 작업 간의 우선순위 체계를 도입하고 싶을 때
Scheduler API는 아직 실험적인 단계이지만 주요 브라우저에서는 핵심 기능을 구현하거나 하는 등의 브라우저 지원이 꾸준히 증가하고 있는 추세입니다.
브라우저 지원이 더 확장된다면 이 API는 웹 개발 툴킷의 핵심이 될 가능성이 있으니 미리 개념을 알아둔다면 좋을 것 같습니다.
'2주차' 카테고리의 다른 글
자바스크립트 이벤트 루프 Event Loop (0) | 2025.04.25 |
---|---|
js 모듈화 (0) | 2025.04.25 |
Dialog 태그로 모달창 구현하기 ! (0) | 2025.04.25 |
자바스크립트 동기와 비동기 (0) | 2025.04.25 |
메모리 누수와 성능 관리 (0) | 2025.04.25 |