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

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

Nate Schloss
Nate Schloss
Andrew Comminos
Andrew Comminos

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

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

브라우저 호환성

브라우저 지원

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

소스

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

배경

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

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

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

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

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

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

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

이제 오리진 트라이얼과 W3C 웹 성능 실무 그룹의 다른 구성원으로부터 의견을 받고 API에 변경사항을 구현했습니다.

예: yieldier 스케줄러

페이지를 로드하기 위해 구성요소에서 마크업을 생성하거나, 소수를 분해하거나, 멋진 로드 스피너를 그리는 등 디스플레이를 차단하는 작업을 많이 해야 한다고 가정해 보겠습니다. 이러한 각 작업은 개별 작업 항목으로 분류됩니다. 스케줄러 패턴을 사용하여 가상의 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 저장소에서 사양에 관한 의견을 남깁니다.
  • Twitter에서 @acomminos (사양 작성자 중 한 명)에게 문의하세요.

결론

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

히어로 사진: 윌 H 맥마한, Unsplash