isInputPending()을 사용한 JS 예약 개선

로드 성능과 입력 응답성 간의 절충을 피하는 데 도움이 되는 새로운 JavaScript API입니다.

Nate Schloss
Nate Schloss
Andrew Comminos
Andrew Comminos

빠르게 로드하기가 어렵습니다. JS를 활용하여 콘텐츠를 렌더링하는 사이트는 현재 로드 성능과 입력 응답성 간에 절충해야 합니다. 디스플레이에 필요한 모든 작업을 한 번에 실행하거나 (로드 성능은 향상되고 입력 응답성은 저하됨) 입력 및 페인트에 계속 응답하기 위해 작업을 더 작은 태스크로 분할합니다 (로드 성능은 저하되고 입력 응답성은 향상됨).

이러한 균형을 유지할 필요가 없도록 Facebook은 수익 창출 없이 응답성을 개선하기 위해 Chromium에서 isInputPending() API를 제안하고 구현했습니다. 오리진 트라이얼 의견을 바탕으로 API가 여러 번 업데이트되었으며 이제 API가 Chromium 87에서 기본적으로 제공된다는 소식을 전해드립니다.

브라우저 호환성

브라우저 지원

  • Chrome: 87
  • Edge: 87.
  • Firefox: 지원되지 않음
  • Safari: 지원되지 않음

소스

isInputPending()는 버전 87부터 Chromium 기반 브라우저에 제공되었습니다. 다른 브라우저는 API를 제공할 의도를 알리지 않았습니다.

배경

오늘날 JS 생태계의 대부분의 작업은 단일 스레드인 기본 스레드에서 실행됩니다. 이렇게 하면 개발자에게 강력한 실행 모델이 제공되지만 스크립트가 장시간 실행되면 사용자 환경(특히 응답성)이 크게 저하될 수 있습니다. 예를 들어 입력 이벤트가 실행되는 동안 페이지에서 많은 작업을 실행하는 경우 페이지는 해당 작업이 완료될 때까지 클릭 입력 이벤트를 처리하지 않습니다.

현재 권장사항은 JavaScript를 더 작은 블록으로 나누어 이 문제를 해결하는 것입니다. 페이지가 로드되는 동안 페이지는 약간의 JavaScript를 실행한 다음 제어권을 브라우저에 양보하고 다시 전달할 수 있습니다. 그러면 브라우저가 입력 이벤트 큐를 확인하고 페이지에 알려야 할 내용이 있는지 확인할 수 있습니다. 그러면 브라우저는 JavaScript 블록이 추가될 때마다 JavaScript 블록을 실행할 수 있습니다. 이렇게 하면 도움이 되지만 다른 문제가 발생할 수 있습니다.

페이지가 브라우저에 제어 권한을 다시 반환할 때마다 브라우저가 입력 이벤트 큐를 확인하고 이벤트를 처리하고 다음 JavaScript 블록을 선택하는 데 시간이 걸립니다. 브라우저가 이벤트에 더 빠르게 응답하지만 페이지의 전체 로드 시간이 느려집니다. 포기 횟수가 너무 많으면 페이지가 너무 느리게 로드됩니다. 실행 빈도가 낮으면 브라우저가 사용자 이벤트에 응답하는 데 더 오래 걸리고 사용자가 좌절하게 됩니다. 재미가 없습니다.

긴 JS 작업을 실행하면 브라우저에서 이벤트를 전달할 시간이 줄어든다는 것을 보여주는 다이어그램

Facebook에서는 이러한 불편한 절충점을 없애는 새로운 로드 접근 방식을 고안하면 어떤 결과가 나올지 확인하고자 했습니다. 이에 대해 Chrome의 친구에게 문의한 결과 isInputPending() 제안이 나왔습니다. isInputPending() API는 웹에서 사용자 입력에 인터럽트라는 개념을 처음으로 사용하는 API로, 이 API를 사용하면 JavaScript가 브라우저에 의존하지 않고도 입력을 확인할 수 있습니다.

isInputPending()를 사용하면 JS가 실행을 브라우저에 완전히 반환하지 않고도 대기 중인 사용자 입력이 있는지 확인할 수 있음을 보여주는 다이어그램입니다.

API에 대한 관심이 있었기 때문에 Chrome의 동료들과 협력하여 Chromium에서 이 기능을 구현하고 출시했습니다. Chrome 엔지니어의 도움으로 오리진 트라이얼을 통해 패치를 제공했습니다. 오리진 트라이얼을 통해 Chrome이 API를 완전히 출시하기 전에 변경사항을 테스트하고 개발자의 의견을 받을 수 있습니다.

이제 원본 체험판 및 W3C 웹 성능 작업 그룹의 다른 회원들의 의견을 수렴하여 API에 변경사항을 구현했습니다.

예: yieldier 스케줄러

페이지를 로드하기 위해 구성요소에서 마크업을 생성하거나, Prime을 제거하거나, 멋진 로드 스피너를 그리는 등 여러 가지 디스플레이 차단 작업이 있다고 가정해 보겠습니다. 각각은 개별 작업 항목으로 나뉩니다. 스케줄러 패턴을 사용하여 가상의 processWorkQueue() 함수에서 작업을 처리하는 방법을 스케치해 보겠습니다.

const DEADLINE = performance.now() + QUANTUM;
while (workQueue.length > 0) {
 
if (performance.now() >= DEADLINE) {
   
// Yield the event loop if we're out of time.
    setTimeout
(processWorkQueue);
   
return;
 
}
  let job
= workQueue.shift();
  job
.execute();
}

나중에 setTimeout()를 통해 새 매크로 작업에서 processWorkQueue()를 호출하면 브라우저가 비교적 중단되지 않고 실행되는 동시에 입력에 어느 정도 반응 (작업이 재개되기 전에 이벤트 핸들러를 실행할 수 있음)할 수 있습니다. 하지만 이벤트 루프를 제어하려는 다른 작업에 의해 장시간 일정 변경되거나 이벤트 지연 시간이 최대 QUANTUM밀리초가 추가로 발생할 수 있습니다.

괜찮지만 더 나은 방법이 있을까요? 물론입니다.

const DEADLINE = performance.now() + QUANTUM;
while (workQueue.length > 0) {
 
if (navigator.scheduling.isInputPending() || performance.now() >= DEADLINE) {
   
// Yield if we have to handle an input event, or we're out of time.
    setTimeout
(processWorkQueue);
   
return;
 
}
  let job
= workQueue.shift();
  job
.execute();
}

navigator.scheduling.isInputPending() 호출을 도입하면 디스플레이 차단 작업이 중단되지 않고 실행되는 동시에 입력에 더 빠르게 응답할 수 있습니다. 작업이 완료될 때까지 입력 (예: 페인팅) 이외의 다른 작업을 처리하는 데 관심이 없다면 QUANTUM의 길이도 쉽게 늘릴 수 있습니다.

기본적으로 '연속' 이벤트는 isInputPending()에서 반환되지 않습니다. 여기에는 mousemove, pointermove 등이 포함됩니다. 이러한 항목에도 양보하는 데 관심이 있다면 언제든지 문의해 주세요. includeContinuoustrue로 설정하여 isInputPending()에 객체를 제공하면 됩니다.

const DEADLINE = performance.now() + QUANTUM;
const options = { includeContinuous: true };
while (workQueue.length > 0) {
 
if (navigator.scheduling.isInputPending(options) || performance.now() >= DEADLINE) {
   
// Yield if we have to handle an input event (any of them!), or we're out of time.
    setTimeout
(processWorkQueue);
   
return;
 
}
  let job
= workQueue.shift();
  job
.execute();
}

작업이 끝났습니다. React와 같은 프레임워크는 유사한 로직을 사용하여 핵심 예약 라이브러리에 isInputPending() 지원을 빌드하고 있습니다. 이를 통해 이러한 프레임워크를 사용하는 개발자가 상당한 재작성 없이 백그라운드에서 isInputPending()의 이점을 누릴 수 있기를 바랍니다.

양보가 항상 나쁜 것은 아닙니다.

더 적은 결과물을 생성하는 것이 모든 사용 사례에 적합한 솔루션은 아니라는 점에 주목해야 합니다. 입력 이벤트를 처리하는 것 외에 렌더링을 실행하고 페이지에서 다른 스크립트를 실행하는 등 여러 가지 이유로 브라우저에 제어 권한을 반환할 수 있습니다.

브라우저가 대기 중인 입력 이벤트의 기여도를 올바르게 부여하지 못하는 경우가 있습니다. 특히 교차 출처 iframe에 복잡한 클립과 마스크를 설정하면 거짓 음성이 보고될 수 있습니다 (즉, 이러한 프레임을 타겟팅할 때 isInputPending()가 예기치 않게 false를 반환할 수 있음). 사이트에서 스타일이 지정된 하위 프레임과의 상호작용이 필요한 경우 충분히 자주 생성해야 합니다.

이벤트 루프를 공유하는 다른 페이지도 주의하세요. Android용 Chrome과 같은 플랫폼에서는 여러 출처가 하나의 이벤트 루프를 공유하는 것이 일반적입니다. 입력이 교차 출처 프레임으로 전달되는 경우 isInputPending()true를 반환하지 않으므로 백그라운드 페이지가 포그라운드 페이지의 응답성을 방해할 수 있습니다. Page Visibility API를 사용하여 백그라운드에서 작업할 때는 작업을 줄이거나, 연기하거나, 더 자주 양보하는 것이 좋습니다.

isInputPending()는 신중하게 사용하는 것이 좋습니다. 사용자 차단 작업이 없으면 더 자주 생성하여 이벤트 루프의 다른 작업자에게 친절하게 대합니다. 장기 작업은 유해할 수 있습니다.

의견

  • is-input-pending 저장소에서 사양에 관한 의견을 남깁니다.
  • 트위터에서 @acomminos (사양 작성자 중 한 명)에게 문의하세요.

결론

isInputPending()가 출시되고 개발자가 지금 바로 이를 사용할 수 있게 되어 기쁩니다. 이 API는 Facebook이 새로운 웹 API를 빌드하고 아이디어 발굴 단계에서 표준 제안서 단계를 거쳐 브라우저에 실제로 제공하기까지의 과정을 거친 최초의 API입니다. 이 시점에 도달할 수 있도록 도와주신 모든 분께 감사드리며, 이 아이디어를 구체화하고 출시하는 데 도움을 주신 Chrome의 모든 직원에게 특별히 감사의 인사를 전합니다.

Unsplash윌 H 맥마한님 제공 히어로 사진