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

로드 성능과 입력 반응성 간의 절충을 방지하는 데 도움이 될 수 있는 새로운 JavaScript API입니다.

Nate Schloss
Nate Schloss
Andrew Comminos
Andrew Comminos

빠른 로드는 어렵습니다. JS를 활용하여 콘텐츠를 렌더링하는 사이트는 현재 로드 성능과 입력 반응성 간에 절충해야 합니다. 즉, 디스플레이에 필요한 모든 작업을 한꺼번에 실행하거나 (로드 성능 향상, 입력 응답성 저하) 작업을 작은 작업으로 분할하여 입력 및 페인트 응답성을 유지합니다 (부하 성능 저하, 입력 응답성 향상)

이러한 장단점을 없애기 위해 Facebook은 수익을 내지 않고 응답성을 개선하기 위해 Chromium에 isInputPending() API를 제안하고 구현했습니다. 오리진 트라이얼 의견에 따라 API를 여러 차례 업데이트했으며 이제 Chromium 87에서 API가 기본적으로 출시된다는 소식을 전해드립니다.

브라우저 호환성

브라우저 지원

  • 87
  • 87
  • x
  • x

isInputPending()은 버전 87부터 Chromium 기반 브라우저에 제공됩니다. API를 제공하겠다는 신호를 보낸 다른 브라우저는 없습니다.

배경

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

현재 권장사항은 자바스크립트를 더 작은 블록으로 나눠 이 문제를 처리하는 것입니다. 페이지가 로드되는 동안 페이지에서 JavaScript를 약간 실행한 다음 컨트롤을 양보하여 브라우저에 다시 전달할 수 있습니다. 그러면 브라우저가 입력 이벤트 큐를 확인하여 페이지에 알릴 사항이 있는지 확인할 수 있습니다. 그러면 브라우저가 자바스크립트 블록이 추가되면 다시 실행할 수 있습니다. 이렇게 하면 도움이 되지만 다른 문제가 발생할 수도 있습니다.

페이지가 브라우저에 제어권을 다시 반환할 때마다 브라우저가 입력 이벤트 대기열을 확인하고 이벤트를 처리하고 다음 자바스크립트 블록을 선택하는 데 다소 시간이 걸립니다. 브라우저가 이벤트에 더 빠르게 반응하지만 페이지의 전체 로드 시간이 느려집니다. 양적 요소가 너무 많으면 페이지 로드 속도가 너무 느립니다. 수익률이 감소하면 브라우저가 사용자 이벤트에 응답하는 데 시간이 더 오래 걸리고 사용자는 불만을 갖게 됩니다. 재미없어.

긴 JS 작업을 실행할 때 브라우저에서 이벤트를 전달하는 데 걸리는 시간이 단축되는 것을 보여주는 다이어그램

Facebook에서는 이러한 좌절감을 피할 수 있는 새로운 로드 방식을 고안할 경우 어떤 결과가 나올지 보고 싶었습니다. 이에 대해 Chrome 친구들에게 연락하여 isInputPending()에 대한 제안을 제시했습니다. isInputPending() API는 웹에서 사용자 입력에 인터럽트 개념을 처음으로 사용하며 JavaScript가 브라우저에 의존하지 않고 입력을 확인할 수 있도록 합니다.

isInputPending()을 통해 JS가 실행 중인 사용자 입력을 브라우저로 완전히 전환하지 않고도 대기 중인 사용자 입력이 있는지 확인할 수 있는 다이어그램.

API에 관심이 생겨 Chrome에 이 기능을 구현하고 출시하기 위해 Chrome의 동료들과 협력하고 있습니다. Google은 Chrome 엔지니어의 도움을 받아 오리진 트라이얼(API를 완전히 출시하기 전에 Chrome에서 변경사항을 테스트하고 개발자로부터 의견을 얻는 방법)을 개발했습니다.

Google은 이제 오리진 트라이얼과 W3C Web Performance Working Group의 다른 구성원으로부터 의견을 받아 API 변경사항을 구현했습니다.

예: 더 성숙한 스케줄러

구성요소에서 마크업을 생성하거나, 소수를 제외하거나, 로드 스피너를 멋지게 그리는 등 페이지를 로드하기 위해 여러 가지 디스플레이 차단 작업을 해야 한다고 가정해 보겠습니다. 이들은 각각 별개의 작업 항목으로 나뉩니다. 스케줄러 패턴을 사용하여 가상의 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()의 이점을 누릴 수 있습니다.

수익률이 항상 나쁘지는 않음

생성형 AI를 줄이는 것이 모든 사용 사례에 적합한 솔루션은 아니라는 점에 유의해야 합니다. 입력 이벤트를 처리하는 것 외에도 브라우저에 제어권을 반환하는 이유는 많습니다(예: 렌더링을 수행하고 페이지에서 다른 스크립트를 실행하는 경우).

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

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

재량에 따라 isInputPending()를 사용하시기 바랍니다. 실행할 사용자 차단 작업이 없다면 더 자주 수익을 창출하여 이벤트 루프의 다른 사용자에게 친절하게 대해 주세요. 장기 작업은 유해할 수 있습니다.

의견

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

결론

isInputPending()가 출시되어 이제 개발자가 이를 사용할 수 있게 되어 기쁩니다. 이 API는 Facebook에서 처음으로 새로운 웹 API를 빌드했으며, 아이디어 구상 단계에서 표준 제안서로, 그리고 브라우저에서 실제로 배송하는 단계로 진행되었습니다. 여기까지 도움을 주신 모든 분께 감사드리며, 이 아이디어를 구체화하고 제품을 출시할 수 있도록 도와주신 Chrome의 모든 분께 특별한 감사 인사를 전합니다.

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