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

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

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

항복 시

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

작업이 완료되지 않는 극단적인 사례(예: 무한 루프)를 제외하고, 수익 창출은 JavaScript 작업 예약 로직의 불가피한 측면입니다. 그럴 가능성언제 문제지만, 빨리 하는 것이 바람직합니다. 작업을 실행하는 데 너무 오래 걸리는 경우(정확히 50밀리초 이상) 장기 작업으로 간주됩니다.

장기 작업은 사용자 입력에 반응하는 브라우저의 기능을 지연시키기 때문에 페이지 응답성을 저하시킵니다. 장기 작업이 자주 발생하고 실행 시간이 길어질수록 사용자가 페이지가 느려지거나 완전히 망가졌다고 느낄 가능성이 높습니다.

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

작업을 분리하여 입력 응답성을 향상하는 방법을 묘사. 맨 위에 있는 긴 작업은 작업이 완료될 때까지 이벤트 핸들러의 실행을 차단합니다. 하단에 있는 분할 작업으로 인해 이벤트 핸들러가 다른 경우보다 더 빨리 실행될 수 있습니다.
기본 스레드에 제어권을 다시 양도하는 과정을 시각화합니다. 무엇보다 작업이 완료될 때까지 양보가 발생합니다. 즉, 작업을 완료하는 데 더 오래 걸릴 수 있으며 그 후에 기본 스레드로 제어가 반환됩니다. 맨 아래에는 양보가 명시적으로 수행되어, 긴 작업을 여러 개의 작은 작업으로 나눕니다. 이를 통해 사용자 상호작용을 더 빨리 실행할 수 있으므로 입력 응답성과 INP가 개선됩니다.

명시적으로 양보하는 것은 브라우저에 "지금 하려는 작업에 시간이 걸릴 수 있다는 것을 알고 있으며, 사용자 입력 또는 중요할 수 있는 다른 작업에 응답하기 전에 그 작업을 모두 하는 것을 원하지 않습니다."라고 말하는 것입니다. 개발자 도구 모음에서 사용자 환경을 개선하는 데 큰 도움이 되는 중요한 도구입니다.

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

생성하는 일반적인 메서드는 제한 시간 값이 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. 마지막으로 실행 루프, 반복 시 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을(를) 입력하고 입력한 다음 실험용 웹 플랫폼 기능 섹션의 드롭다운에서 사용 설정을 선택합니다. 이렇게 하면 Chrome 인스턴스에서만 scheduler.yield 및 기타 실험용 기능을 사용할 수 있습니다.
  2. 공개적으로 액세스할 수 있는 오리진의 실제 Chromium 사용자가 scheduler.yield를 사용할 수 있도록 하려면 scheduler.yield 오리진 트라이얼에 가입해야 합니다. 이를 통해 사용자는 정해진 기간 동안 제안된 기능을 안전하게 실험할 수 있으며, Chrome팀은 이러한 기능이 현장에서 어떻게 사용되는지에 관한 유용한 정보를 얻을 수 있습니다. 오리진 트라이얼 작동 방식에 관한 자세한 내용은 이 가이드를 참고하세요.

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를 지원하지 않는 브라우저는 '대기열 앞' 동작 없이 수익을 창출합니다. 이 경우 전혀 수익을 내지 않으려면 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는 스케줄러 API에 추가된 흥미로운 기능입니다. 개발자는 이를 통해 현재 수익 창출 전략보다 응답성을 더 쉽게 개선할 수 있습니다. scheduler.yield이(가) 유용한 API라고 생각되시면 연구에 참여하여 API 개선을 위한 의견을 보내주세요.

Unsplash의 히어로 이미지(조나단 앨리슨 제작)