사용자 입력에 빠르게 응답하는 웹사이트를 빌드하는 것은 웹 성능에서 가장 어려운 측면 중 하나였으며, Chrome팀은 웹 개발자가 이를 달성할 수 있도록 노력해 왔습니다. 올해 초 다음 페인트에 대한 상호작용 (INP) 측정항목이 실험용에서 대기 중으로 전환될 예정이라고 발표되었습니다. 이제 2024년 3월에 Core Web Vitals로서 최초 입력 지연 시간 (FID)을 대체할 예정입니다.
웹 개발자가 웹사이트를 최대한 빠르게 만드는 데 도움이 되는 새로운 API를 지속적으로 제공하기 위해 Chrome팀은 현재 Chrome 버전 115부터 scheduler.yield
의 오리진 트라이얼을 진행하고 있습니다. scheduler.yield
는 스케줄러 API에 제안된 새로운 추가 기능으로, 기존에 사용해 온 메서드보다 더 쉽고 효과적인 방법으로 제어를 기본 스레드로 다시 반환할 수 있습니다.
포기 시
JavaScript는 실행 완료 모델을 사용하여 작업을 처리합니다. 즉, 작업이 기본 스레드에서 실행되면 완료하는 데 필요한 시간만큼 실행됩니다. 작업이 완료되면 제어권이 기본 스레드로 다시 반환되므로 기본 스레드가 대기열의 다음 작업을 처리할 수 있습니다.
태스크가 완료되지 않는 극단적인 경우(예: 무한 루프)를 제외하고는, 생성은 JavaScript의 태스크 예약 로직에서 피할 수 없는 측면입니다. 실시될 것이며 언제의 문제일 뿐입니다. 늦어도 빨리 실행하는 것이 좋습니다. 태스크를 실행하는 데 너무 오래 걸리는 경우(정확히 50밀리초 초과) 장기 태스크로 간주됩니다.
긴 작업은 브라우저의 사용자 입력에 대한 응답 능력을 지연시키므로 페이지 응답성이 저하됩니다. 긴 작업이 자주 발생하고 실행 시간이 길어질수록 사용자는 페이지가 느리다는 인상을 받거나 심지어 페이지가 완전히 손상되었다고 느낄 수 있습니다.
하지만 코드가 브라우저에서 작업을 시작한다고 해서 제어를 기본 스레드로 다시 반환하기 전에 해당 작업이 완료될 때까지 기다려야 하는 것은 아닙니다. 작업에서 명시적으로 생성하여 페이지에서 사용자 입력에 대한 응답성을 개선할 수 있습니다. 이렇게 하면 작업이 다음에 사용 가능한 기회에 완료되도록 분할됩니다. 이렇게 하면 다른 작업이 긴 작업이 완료될 때까지 기다리지 않아도 되므로 기본 스레드에서 더 일찍 시간을 확보할 수 있습니다.
명시적으로 양보하면 브라우저에 '실행하려는 작업이 다소 시간이 걸릴 수 있다는 점을 알고 있습니다. 사용자 입력이나 다른 중요한 작업에 응답하기 전에 브라우저에서 모든 작업을 실행하지 않아도 됩니다.'라고 알리는 것입니다. Firebase는 개발자 도구 상자에서 사용자 환경을 개선하는 데 큰 도움이 되는 유용한 도구입니다.
현재 수익 창출 전략의 문제점
일반적인 생성 방법은 제한 시간 값이 0
인 setTimeout
를 사용합니다. setTimeout
에 전달된 콜백이 나머지 작업을 후속 실행을 위해 대기열에 추가될 별도의 태스크로 이동하기 때문에 이렇게 작동합니다. 브라우저가 자체적으로 생성될 때까지 기다리는 대신 '이 큰 작업을 더 작은 작업으로 나누자'라고 말하는 것입니다.
그러나 setTimeout
로 생성하면 바람직하지 않은 부작용이 발생할 수 있습니다. 생성 지점 뒤에 오는 작업이 작업 큐의 뒷부분으로 이동합니다. 사용자 상호작용으로 예약된 작업은 여전히 정상적으로 큐의 맨 앞으로 이동하지만, 명시적으로 생성된 후 수행하려는 나머지 작업은 그보다 먼저 큐에 추가된 경쟁 소스의 다른 작업으로 인해 더 지연될 수 있습니다.
이 기능을 사용해 보려면 이 Glitch 데모를 사용해 보거나 아래에 삽입된 버전에서 실험해 보세요. 데모는 클릭할 수 있는 몇 개의 버튼과 작업이 실행될 때 로깅되는 상자로 구성됩니다. 페이지에 도착하면 다음 작업을 실행합니다.
- 주기적으로 태스크 실행이라는 상단 버튼을 클릭하면 차단 태스크가 자주 실행되도록 예약됩니다. 이 버튼을 클릭하면 작업 로그에
setInterval
로 차단 작업 실행이라는 메시지가 여러 개 채워집니다. - 그런 다음 루프 실행, 각 반복에서
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
의 작동 방식을 보려면 다음 단계를 따르세요.
chrome://flags
로 이동합니다.- 실험용 웹 플랫폼 기능 실험을 사용 설정합니다. 이 작업을 실행한 후 Chrome을 다시 시작해야 할 수 있습니다.
- 데모 페이지로 이동하거나 이 목록 아래에 있는 삽입된 버전을 사용하세요.
- 상단의 주기적으로 작업 실행 버튼을 클릭합니다.
- 마지막으로 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부터 다음 두 가지 방법으로 사용해 볼 수 있습니다.
scheduler.yield
를 로컬에서 실험하려면 Chrome의 주소 표시줄에chrome://flags
를 입력하고 실험용 웹 플랫폼 기능 섹션의 드롭다운에서 사용 설정을 선택합니다. 이렇게 하면scheduler.yield
및 기타 실험용 기능을 내 Chrome 인스턴스에서만 사용할 수 있습니다.- 공개적으로 액세스할 수 있는 출처에서 실제 Chromium 사용자를 위해
scheduler.yield
를 사용 설정하려면scheduler.yield
오리진 트라이얼에 가입해야 합니다. 이렇게 하면 제안된 기능을 일정 기간 동안 안전하게 실험할 수 있으며 Chrome팀은 이러한 기능이 현장에서 어떻게 사용되는지에 관한 유용한 정보를 얻을 수 있습니다. 출처 무료 체험의 작동 방식에 대한 자세한 내용은 이 가이드를 참고하세요.
scheduler.yield
를 구현하지 않는 브라우저를 계속 지원하면서 scheduler.yield
를 사용하는 방법은 목표에 따라 다릅니다. 공식 폴리필을 사용할 수 있습니다. 폴리필은 다음과 같은 상황에 유용합니다.
- 이미 애플리케이션에서
scheduler.postTask
를 사용하여 태스크를 예약하고 있습니다. - 작업 및 산출 우선순위를 설정할 수 있어야 합니다.
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
를 더 개선할 수 있는 방법에 관한 의견을 제공해 주세요.