Scheduler.yield 오리진 트라이얼 소개

사용자 입력에 빠르게 응답하는 웹사이트를 빌드하는 것은 웹 성능에서 가장 어려운 측면 중 하나였으며, Chrome팀은 웹 개발자가 이를 달성할 수 있도록 노력해 왔습니다. 올해 초 다음 페인트에 대한 상호작용 (INP) 측정항목이 실험용에서 대기 중으로 전환될 예정이라고 발표되었습니다. 이제 2024년 3월에 Core Web Vitals로서 최초 입력 지연 시간 (FID)을 대체할 예정입니다.

웹 개발자가 웹사이트를 최대한 빠르게 만드는 데 도움이 되는 새로운 API를 지속적으로 제공하기 위해 Chrome팀은 현재 Chrome 버전 115부터 scheduler.yield의 오리진 트라이얼을 진행하고 있습니다. scheduler.yield는 스케줄러 API에 제안된 새로운 추가 기능으로, 기존에 사용해 온 메서드보다 더 쉽고 효과적인 방법으로 제어를 기본 스레드로 다시 반환할 수 있습니다.

포기 시

JavaScript는 실행 완료 모델을 사용하여 작업을 처리합니다. 즉, 작업이 기본 스레드에서 실행되면 완료하는 데 필요한 시간만큼 실행됩니다. 작업이 완료되면 제어권이 기본 스레드로 다시 반환되므로 기본 스레드가 대기열의 다음 작업을 처리할 수 있습니다.

태스크가 완료되지 않는 극단적인 경우(예: 무한 루프)를 제외하고는, 생성은 JavaScript의 태스크 예약 로직에서 피할 수 없는 측면입니다. 실시될 것이며 언제의 문제일 뿐입니다. 늦어도 빨리 실행하는 것이 좋습니다. 태스크를 실행하는 데 너무 오래 걸리는 경우(정확히 50밀리초 초과) 장기 태스크로 간주됩니다.

긴 작업은 브라우저의 사용자 입력에 대한 응답 능력을 지연시키므로 페이지 응답성이 저하됩니다. 긴 작업이 자주 발생하고 실행 시간이 길어질수록 사용자는 페이지가 느리다는 인상을 받거나 심지어 페이지가 완전히 손상되었다고 느낄 수 있습니다.

하지만 코드가 브라우저에서 작업을 시작한다고 해서 제어를 기본 스레드로 다시 반환하기 전에 해당 작업이 완료될 때까지 기다려야 하는 것은 아닙니다. 작업에서 명시적으로 생성하여 페이지에서 사용자 입력에 대한 응답성을 개선할 수 있습니다. 이렇게 하면 작업이 다음에 사용 가능한 기회에 완료되도록 분할됩니다. 이렇게 하면 다른 작업이 긴 작업이 완료될 때까지 기다리지 않아도 되므로 기본 스레드에서 더 일찍 시간을 확보할 수 있습니다.

태스크를 분할하면 입력 응답성을 개선할 수 있는 방법을 보여주는 그림 상단에서 긴 태스크는 태스크가 완료될 때까지 이벤트 핸들러가 실행되지 않도록 차단합니다. 하단에서 청크 처리된 작업을 사용하면 이벤트 핸들러가 더 빨리 실행될 수 있습니다.
제어를 기본 스레드로 다시 반환하는 시각화입니다. 위쪽에서는 태스크가 완료될 때만 실행이 중단됩니다. 즉, 태스크가 완료되어 제어를 기본 스레드로 다시 반환하는 데 시간이 더 걸릴 수 있습니다. 하단에서 산출은 명시적으로 실행되어 긴 작업을 여러 개의 작은 작업으로 분할합니다. 이렇게 하면 사용자 상호작용이 더 빨리 실행되어 입력 응답성과 INP가 개선됩니다.

명시적으로 양보하면 브라우저에 '실행하려는 작업이 다소 시간이 걸릴 수 있다는 점을 알고 있습니다. 사용자 입력이나 다른 중요한 작업에 응답하기 전에 브라우저에서 모든 작업을 실행하지 않아도 됩니다.'라고 알리는 것입니다. Firebase는 개발자 도구 상자에서 사용자 환경을 개선하는 데 큰 도움이 되는 유용한 도구입니다.

현재 수익 창출 전략의 문제점

일반적인 생성 방법은 제한 시간 값이 0setTimeout를 사용합니다. setTimeout에 전달된 콜백이 나머지 작업을 후속 실행을 위해 대기열에 추가될 별도의 태스크로 이동하기 때문에 이렇게 작동합니다. 브라우저가 자체적으로 생성될 때까지 기다리는 대신 '이 큰 작업을 더 작은 작업으로 나누자'라고 말하는 것입니다.

그러나 setTimeout로 생성하면 바람직하지 않은 부작용이 발생할 수 있습니다. 생성 지점 뒤에 오는 작업이 작업 큐의 뒷부분으로 이동합니다. 사용자 상호작용으로 예약된 작업은 여전히 정상적으로 큐의 맨 앞으로 이동하지만, 명시적으로 생성된 후 수행하려는 나머지 작업은 그보다 먼저 큐에 추가된 경쟁 소스의 다른 작업으로 인해 더 지연될 수 있습니다.

이 기능을 사용해 보려면 이 Glitch 데모를 사용해 보거나 아래에 삽입된 버전에서 실험해 보세요. 데모는 클릭할 수 있는 몇 개의 버튼과 작업이 실행될 때 로깅되는 상자로 구성됩니다. 페이지에 도착하면 다음 작업을 실행합니다.

  1. 주기적으로 태스크 실행이라는 상단 버튼을 클릭하면 차단 태스크가 자주 실행되도록 예약됩니다. 이 버튼을 클릭하면 작업 로그에 setInterval로 차단 작업 실행이라는 메시지가 여러 개 채워집니다.
  2. 그런 다음 루프 실행, 각 반복에서 setTimeout로 산출이라는 라벨이 지정된 버튼을 클릭합니다.

데모 하단의 상자에 다음과 같은 내용이 표시됩니다.

Processing loop item 1
Processing loop item 2
Ran blocking task via setInterval
Processing loop item 3
Ran blocking task via setInterval
Processing loop item 4
Ran blocking task via setInterval
Processing loop item 5
Ran blocking task via setInterval
Ran blocking task via setInterval

이 출력은 setTimeout로 생성할 때 발생하는 '작업 큐 종료' 동작을 보여줍니다. 실행되는 루프는 5개의 항목을 처리하고 각 항목이 처리된 후 setTimeout로 생성합니다.

이는 웹에서 흔히 발생하는 문제를 보여줍니다. 스크립트, 특히 서드 파티 스크립트가 특정 간격으로 작업을 실행하는 타이머 함수를 등록하는 것은 드문 일이 아닙니다. setTimeout로 포기할 때 발생하는 '작업 큐 종료' 동작은 루프가 포기한 후 실행해야 하는 나머지 작업보다 다른 작업 소스의 작업이 먼저 큐에 추가될 수 있음을 의미합니다.

애플리케이션에 따라 이러한 결과가 바람직할 수도 있고 그렇지 않을 수도 있습니다. 하지만 대부분의 경우 이러한 동작으로 인해 개발자가 기본 스레드의 제어를 쉽게 포기하고 싶어 하지 않을 수 있습니다. 사용자 상호작용이 더 빨리 실행될 수 있으므로 양보하는 것이 좋지만, 사용자 상호작용이 아닌 다른 작업도 기본 스레드에서 시간을 가질 수 있습니다. 실제로 문제가 있지만 scheduler.yield를 사용하면 해결할 수 있습니다.

scheduler.yield 입력

scheduler.yield는 Chrome 버전 115부터 플래그 뒤에서 실험용 웹 플랫폼 기능으로 사용할 수 있었습니다. setTimeout가 이미 산출물을 생성하는 경우 산출물을 생성하는 특수 함수가 필요한 이유는 무엇인가요?'라는 질문이 있을 수 있습니다.

양보는 setTimeout의 설계 목표가 아니라 0의 시간 제한 값이 지정된 경우에도 나중에 실행되도록 콜백을 예약할 때 발생하는 좋은 부작용이라는 점에 유의해야 합니다. 하지만 더 중요한 것은 setTimeout로 생성된 작업이 남은 작업을 작업 큐의 뒷부분으로 전송한다는 점입니다. 기본적으로 scheduler.yield는 나머지 작업을 큐의 앞부분으로 전송합니다. 즉, 양보한 직후 다시 시작하려는 작업이 다른 소스의 작업보다 우선하지 않습니다 (사용자 상호작용은 예외).

scheduler.yield는 기본 스레드에 양보하고 호출 시 Promise를 반환하는 함수입니다. 즉, async 함수에서 await할 수 있습니다.

async function yieldy () {
  // Do some work...
  // ...

  // Yield!
  await scheduler.yield();

  // Do some more work...
  // ...
}

scheduler.yield의 작동 방식을 보려면 다음 단계를 따르세요.

  1. chrome://flags로 이동합니다.
  2. 실험용 웹 플랫폼 기능 실험을 사용 설정합니다. 이 작업을 실행한 후 Chrome을 다시 시작해야 할 수 있습니다.
  3. 데모 페이지로 이동하거나 이 목록 아래에 있는 삽입된 버전을 사용하세요.
  4. 상단의 주기적으로 작업 실행 버튼을 클릭합니다.
  5. 마지막으로 Run loop, yielding with scheduler.yield on each iteration(반복마다 scheduler.yield로 반환하는 루프 실행) 버튼을 클릭합니다.

페이지 하단의 상자에 표시되는 출력은 다음과 같습니다.

Processing loop item 1
Processing loop item 2
Processing loop item 3
Processing loop item 4
Processing loop item 5
Ran blocking task via setInterval
Ran blocking task via setInterval
Ran blocking task via setInterval
Ran blocking task via setInterval
Ran blocking task via setInterval

setTimeout를 사용하여 생성하는 데모와 달리 루프는 모든 반복 후에 생성되더라도 나머지 작업을 대기열의 뒷부분이 아닌 앞부분으로 전송합니다. 이렇게 하면 두 가지 장점을 모두 누릴 수 있습니다. 즉, 양보하여 웹사이트의 입력 응답성을 개선할 수 있지만 양보 완료하려는 작업이 지연되지 않도록 할 수 있습니다.

한번 사용해 보세요.

scheduler.yield가 흥미로워서 사용해 보고 싶다면 Chrome 버전 115부터 다음 두 가지 방법으로 사용해 볼 수 있습니다.

  1. scheduler.yield를 로컬에서 실험하려면 Chrome의 주소 표시줄에 chrome://flags를 입력하고 실험용 웹 플랫폼 기능 섹션의 드롭다운에서 사용 설정을 선택합니다. 이렇게 하면 scheduler.yield 및 기타 실험용 기능을 내 Chrome 인스턴스에서만 사용할 수 있습니다.
  2. 공개적으로 액세스할 수 있는 출처에서 실제 Chromium 사용자를 위해 scheduler.yield를 사용 설정하려면 scheduler.yield 오리진 트라이얼에 가입해야 합니다. 이렇게 하면 제안된 기능을 일정 기간 동안 안전하게 실험할 수 있으며 Chrome팀은 이러한 기능이 현장에서 어떻게 사용되는지에 관한 유용한 정보를 얻을 수 있습니다. 출처 무료 체험의 작동 방식에 대한 자세한 내용은 이 가이드를 참고하세요.

scheduler.yield를 구현하지 않는 브라우저를 계속 지원하면서 scheduler.yield를 사용하는 방법은 목표에 따라 다릅니다. 공식 폴리필을 사용할 수 있습니다. 폴리필은 다음과 같은 상황에 유용합니다.

  1. 이미 애플리케이션에서 scheduler.postTask를 사용하여 태스크를 예약하고 있습니다.
  2. 작업 및 산출 우선순위를 설정할 수 있어야 합니다.
  3. scheduler.postTask API에서 제공하는 TaskController 클래스를 통해 작업을 취소하거나 우선순위를 변경하려고 합니다.

이 설명이 내 상황에 해당하지 않는다면 폴리필이 적합하지 않을 수 있습니다. 이 경우 몇 가지 방법으로 자체 대체 솔루션을 롤아웃할 수 있습니다. 첫 번째 접근 방식은 사용 가능한 경우 scheduler.yield를 사용하고 사용 불가능한 경우 setTimeout로 대체합니다.

// A function for shimming scheduler.yield and setTimeout:
function yieldToMain () {
  // Use scheduler.yield if it exists:
  if ('scheduler' in window && 'yield' in scheduler) {
    return scheduler.yield();
  }

  // Fall back to setTimeout:
  return new Promise(resolve => {
    setTimeout(resolve, 0);
  });
}

// Example usage:
async function doWork () {
  // Do some work:
  // ...

  await yieldToMain();

  // Do some other work:
  // ...
}

이렇게 하면 작동할 수 있지만 짐작할 수 있듯이 scheduler.yield를 지원하지 않는 브라우저는 'front of queue' 동작 없이 실행됩니다. 전혀 양보하고 싶지 않다면 scheduler.yield를 사용할 수 있는 경우 이를 사용하고 사용할 수 없는 경우에는 전혀 양보하지 않는 다른 접근 방식을 시도해 볼 수 있습니다.

// A function for shimming scheduler.yield with no fallback:
function yieldToMain () {
  // Use scheduler.yield if it exists:
  if ('scheduler' in window && 'yield' in scheduler) {
    return scheduler.yield();
  }

  // Fall back to nothing:
  return;
}

// Example usage:
async function doWork () {
  // Do some work:
  // ...

  await yieldToMain();

  // Do some other work:
  // ...
}

scheduler.yield는 개발자가 현재의 생성 전략보다 더 쉽게 응답성을 개선할 수 있도록 하는 scheduler API의 흥미로운 추가 기능입니다. scheduler.yield가 유용한 API라고 생각되면 개선을 위한 Google의 연구에 참여하고 scheduler.yield를 더 개선할 수 있는 방법에 관한 의견을 제공해 주세요.

Unsplash의 히어로 이미지, 조너선 앨리슨 제공.