scheduler.yield()를 사용하여 긴 작업 분할

Brendan Kenny
Brendan Kenny

게시일: 2025년 3월 6일

Browser Support

  • Chrome: 129.
  • Edge: 129.
  • Firefox: 142.
  • Safari: not supported.

Source

긴 작업으로 인해 기본 스레드가 계속 사용 중이면 페이지가 느리고 응답하지 않는 것처럼 느껴집니다. 기본 스레드가 사용자 입력에 응답하는 것과 같은 다른 중요한 작업을 수행할 수 없기 때문입니다. 따라서 더 복잡한 맞춤 구성요소는 말할 것도 없고 내장된 양식 컨트롤조차 사용자에게 페이지가 멈춘 것처럼 깨진 것처럼 보일 수 있습니다.

scheduler.yield()는 기본 스레드에 양보하여 브라우저가 대기 중인 우선순위가 높은 작업을 실행하도록 허용한 다음 중단된 지점에서 실행을 계속하는 방법입니다. 이렇게 하면 페이지의 응답성이 높아지고 결과적으로 Interaction to Next Paint (INP)가 개선됩니다.

scheduler.yield는 명시된 대로 정확하게 작동하는 인체공학적 API를 제공합니다. 즉, 호출된 함수의 실행이 await scheduler.yield() 표현식에서 일시중지되고 기본 스레드로 양보되어 작업이 중단됩니다. 함수의 나머지 부분(함수의 연속이라고 함)의 실행은 새 이벤트 루프 작업에서 실행되도록 예약됩니다.

async function respondToUserClick() {
  giveImmediateFeedback();
  await scheduler.yield(); // Yield to the main thread.
  slowerComputation();
}

scheduler.yield의 구체적인 이점은 yield 후의 연속이 페이지에서 대기열에 추가된 다른 유사한 작업을 실행하기 전에 실행되도록 예약된다는 것입니다. 새 작업을 시작하는 것보다 기존 작업의 지속을 우선시합니다.

setTimeout 또는 scheduler.postTask와 같은 함수를 사용하여 작업을 분할할 수도 있지만 이러한 연속은 일반적으로 이미 대기열에 추가된 새 작업 후에 실행되므로 기본 스레드에 양보한 후 작업을 완료하는 데 긴 지연이 발생할 수 있습니다.

양보 후 우선순위가 지정된 연속

scheduler.yield우선순위가 지정된 작업 예약 API의 일부입니다. 웹 개발자는 일반적으로 이벤트 루프가 명시적 우선순위 측면에서 작업을 실행하는 순서에 관해 이야기하지 않지만 상대적 우선순위는 항상 존재합니다. 예를 들어 대기열에 추가된 setTimeout 콜백 후에 실행되는 requestIdleCallback 콜백이나 setTimeout(callback, 0)로 대기열에 추가된 작업 전에 실행되는 트리거된 입력 이벤트 리스너가 있습니다.

우선순위가 지정된 작업 예약은 이를 더 명시적으로 만들어 어떤 작업이 다른 작업보다 먼저 실행되는지 쉽게 파악할 수 있으며, 필요한 경우 우선순위를 조정하여 실행 순서를 변경할 수 있습니다.

언급한 바와 같이 scheduler.yield()로 양보한 후 함수의 계속 실행은 다른 작업을 시작하는 것보다 높은 우선순위를 갖습니다. 안내 개념은 다른 작업으로 이동하기 전에 작업의 연속이 먼저 실행되어야 한다는 것입니다. 작업이 브라우저가 사용자 입력에 응답하는 것과 같은 다른 중요한 작업을 할 수 있도록 주기적으로 양보하는 적절한 코드인 경우 다른 유사한 작업보다 우선순위가 지정되어 양보에 대한 처벌을 받아서는 안 됩니다.

다음은 setTimeout를 사용하여 서로 다른 작업에서 실행되도록 대기열에 추가된 두 함수의 예입니다.

setTimeout(myJob);
setTimeout(someoneElsesJob);

이 경우 두 setTimeout 호출이 바로 옆에 있지만 실제 페이지에서는 자체적으로 실행되도록 작업을 설정하는 서드 파티 스크립트와 퍼스트 파티 스크립트와 같이 완전히 다른 위치에서 호출될 수 있습니다. 또는 프레임워크의 스케줄러 깊숙이에서 트리거되는 별도의 구성요소의 두 작업일 수도 있습니다.

DevTools에서 이러한 작업은 다음과 같이 표시될 수 있습니다.

Chrome DevTools 성능 패널에 두 개의 작업이 표시됩니다. 둘 다 긴 작업으로 표시되며, 'myJob' 함수는 첫 번째 작업의 전체 실행을 차지하고 'someoneElsesJob'은 두 번째 작업의 전체를 차지합니다.

myJob는 긴 작업으로 플래그가 지정되어 실행되는 동안 브라우저가 다른 작업을 실행하지 못하도록 차단합니다. 퍼스트 파티 스크립트에서 가져온 것으로 가정하면 다음과 같이 나눌 수 있습니다.

function myJob() {
  // Run part 1.
  myJobPart1();
  // Yield with setTimeout() to break up long task, then run part2.
  setTimeout(myJobPart2, 0);
}

myJobPart2myJob 내에서 setTimeout와 함께 실행되도록 예약되었지만 이 예약은 someoneElsesJob가 이미 예약된 에 실행되므로 실행은 다음과 같이 표시됩니다.

Chrome DevTools 성능 패널에 표시된 세 가지 작업 첫 번째는 'myJobPart1' 함수를 실행하고 두 번째는 'someoneElsesJob'을 실행하는 긴 작업이며 마지막 세 번째 작업은 'myJobPart2'를 실행합니다.

setTimeout를 사용하여 작업을 분할했으므로 브라우저가 myJob 중간에 응답할 수 있지만 이제 myJob의 두 번째 부분은 someoneElsesJob가 완료된 후에만 실행됩니다.

경우에 따라 괜찮을 수도 있지만 일반적으로는 최적의 방법이 아닙니다. myJob는 페이지가 사용자 입력에 계속 응답할 수 있도록 기본 스레드에 양보하는 것이지 기본 스레드를 완전히 포기하는 것이 아닙니다. someoneElsesJob가 특히 느리거나 someoneElsesJob 외에 다른 작업도 많이 예약된 경우 myJob의 후반부가 실행되기까지 오랜 시간이 걸릴 수 있습니다. 개발자가 myJobsetTimeout를 추가할 때 의도한 것은 아니었을 것입니다.

scheduler.yield()를 입력합니다. 그러면 이를 호출하는 함수의 연속이 다른 유사한 작업을 시작하는 것보다 약간 높은 우선순위 큐에 배치됩니다. myJob가 사용하도록 변경된 경우:

async function myJob() {
  // Run part 1.
  myJobPart1();
  // Yield with scheduler.yield() to break up long task, then run part2.
  await scheduler.yield();
  myJobPart2();
}

이제 실행은 다음과 같습니다.

Chrome DevTools 성능 패널에 두 개의 작업이 표시됩니다. 둘 다 긴 작업으로 표시되며, 'myJob' 함수는 첫 번째 작업의 전체 실행을 차지하고 'someoneElsesJob'은 두 번째 작업의 전체를 차지합니다.

브라우저는 여전히 응답할 수 있지만 이제 myJob 작업의 연속이 새 작업 someoneElsesJob 시작보다 우선순위가 높으므로 someoneElsesJob가 시작되기 전에 myJob가 완료됩니다. 이는 기본 스레드를 완전히 포기하는 것이 아니라 응답성을 유지하기 위해 기본 스레드에 양보해야 한다는 기대치에 훨씬 더 가깝습니다.

우선순위 상속

더 큰 우선순위가 지정된 작업 일정 API의 일부인 scheduler.yield()scheduler.postTask()에서 사용할 수 있는 명시적 우선순위와 잘 호환됩니다. 명시적으로 설정된 우선순위가 없으면 scheduler.postTask() 콜백 내의 scheduler.yield()는 기본적으로 이전 예와 동일하게 작동합니다.

하지만 낮은 'background' 우선순위를 사용하는 등 우선순위가 설정된 경우:

async function lowPriorityJob() {
  part1();
  await scheduler.yield();
  part2();
}

scheduler.postTask(lowPriorityJob, {priority: 'background'});

연속은 다른 'background' 작업보다 우선순위가 높게 예약되므로 대기 중인 'background' 작업보다 우선순위가 지정된 연속이 먼저 실행되지만 다른 기본 또는 우선순위가 높은 작업보다는 우선순위가 낮습니다. 'background' 작업으로 유지됩니다.

즉, 'background' scheduler.postTask() (또는 requestIdleCallback)로 우선순위가 낮은 작업을 예약하면 scheduler.yield() 내의 연속도 대부분의 다른 작업이 완료되고 기본 스레드가 유휴 상태가 될 때까지 기다린 후 실행됩니다. 이는 우선순위가 낮은 작업에서 원하는 동작입니다.

API 사용 방법

현재 scheduler.yield()는 Chromium 기반 브라우저에서만 사용할 수 있으므로 이를 사용하려면 기능 감지를 수행하고 다른 브라우저의 경우 양보하는 보조 방식으로 대체해야 합니다.

scheduler-polyfillscheduler.postTaskscheduler.yield의 작은 폴리필로, 내부적으로 메서드 조합을 사용하여 다른 브라우저의 일정 API의 많은 기능을 에뮬레이션합니다 (scheduler.yield() 우선순위 상속은 지원되지 않음).

폴리필을 피하려는 경우 한 가지 방법은 setTimeout()를 사용하여 양보하고 우선순위가 지정된 연속의 손실을 수용하거나, 허용되지 않는 경우 지원되지 않는 브라우저에서 양보하지 않는 것입니다. 자세한 내용은 긴 작업 최적화의 scheduler.yield() 문서를 참고하세요.

wicg-task-scheduling 유형을 사용하여 scheduler.yield()를 기능 감지하고 직접 대체 항목을 추가하는 경우 유형 검사와 IDE 지원을 받을 수도 있습니다.

자세히 알아보기

API 및 API가 작업 우선순위 및 scheduler.postTask()와 상호작용하는 방식에 관한 자세한 내용은 MDN의 scheduler.yield()우선순위가 지정된 작업 예약 문서를 참고하세요.

긴 작업, 긴 작업이 사용자 환경에 미치는 영향, 긴 작업에 대한 조치에 대해 자세히 알아보려면 긴 작업 최적화를 참고하세요.