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