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

게시일: 2024년 3월 6일

Browser Support

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

Source

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

scheduler.yield()는 기본 스레드에 양보하는 방법으로, 브라우저가 대기 중인 우선순위가 높은 작업을 실행할 수 있도록 한 다음 중단된 지점에서 실행을 계속합니다. 이렇게 하면 페이지의 응답성이 향상되고 다음 페인트까지의 상호작용 (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'를 실행합니다.

myJob가 실행되는 동안 브라우저가 응답할 수 있도록 setTimeout로 태스크를 분할했지만 이제 myJob의 두 번째 부분은 someoneElsesJob가 완료된 후에만 실행됩니다.

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

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가 완료됩니다. 이는 기본 스레드를 완전히 포기하는 것이 아니라 응답성을 유지하기 위해 기본 스레드에 양보한다는 기대에 훨씬 더 가깝습니다.

우선순위 상속

더 큰 Prioritized Task Scheduling 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() 문서를 참고하세요.

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

자세히 알아보기

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

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