Membuat situs yang merespons input pengguna dengan cepat adalah salah satu aspek performa web yang paling menantang—yang telah diupayakan oleh Tim Chrome untuk membantu developer web memenuhinya. Baru tahun ini, diumumkan bahwa metrik Interaction to Next Paint (INP) akan lulus dari status eksperimental ke status tertunda. Metrik ini sekarang siap menggantikan First Input Delay (FID) sebagai Core Web Vital pada Maret 2024.
Dalam upaya berkelanjutan untuk menghadirkan API baru yang membantu developer web membuat situs mereka secepat mungkin, Tim Chrome saat ini menjalankan uji coba origin untuk scheduler.yield yang dimulai di Chrome versi 115. scheduler.yield adalah tambahan baru yang diusulkan ke scheduler API yang memungkinkan cara yang lebih mudah dan lebih baik untuk mengembalikan kontrol ke thread utama daripada metode yang biasanya diandalkan.
Tentang hasil
JavaScript menggunakan model run-to-completion untuk menangani tugas. Artinya, saat tugas berjalan di thread utama, tugas tersebut akan berjalan selama diperlukan untuk diselesaikan. Setelah tugas selesai, kontrol akan dihasilkan kembali ke thread utama, yang memungkinkan thread utama memproses tugas berikutnya dalam antrean.
Selain kasus ekstrem saat tugas tidak pernah selesai—seperti loop tak terbatas, misalnya—hasil adalah aspek yang tak terhindarkan dari logika penjadwalan tugas JavaScript. Hal ini akan terjadi, hanya masalah kapan, dan lebih cepat lebih baik daripada nanti. Jika tugas memerlukan waktu terlalu lama untuk dijalankan—lebih dari 50 milidetik, tepatnya—tugas tersebut dianggap sebagai tugas yang panjang.
Tugas yang panjang adalah sumber responsivitas halaman yang buruk, karena tugas tersebut menunda kemampuan browser untuk merespons input pengguna. Semakin sering tugas yang panjang terjadi—dan semakin lama tugas tersebut berjalan—semakin besar kemungkinan pengguna akan mendapatkan kesan bahwa halaman tersebut lambat, atau bahkan merasa bahwa halaman tersebut benar-benar rusak.
Namun, hanya karena kode Anda memulai tugas di browser, bukan berarti Anda harus menunggu hingga tugas tersebut selesai sebelum kontrol dikembalikan ke thread utama. Anda dapat meningkatkan responsivitas terhadap input pengguna di halaman dengan menghasilkan secara eksplisit dalam tugas, yang membagi tugas untuk diselesaikan pada kesempatan berikutnya. Hal ini memungkinkan tugas lain mendapatkan waktu di thread utama lebih cepat daripada jika mereka harus menunggu tugas yang panjang selesai.
Saat Anda menghasilkan secara eksplisit, Anda memberi tahu browser "hei, saya mengerti bahwa pekerjaan yang akan saya lakukan mungkin memerlukan waktu beberapa saat, dan saya tidak ingin Anda harus melakukan semua pekerjaan tersebut sebelum merespons input pengguna atau tugas lain yang mungkin juga penting". Ini adalah alat yang berharga dalam toolbox developer yang dapat membantu meningkatkan pengalaman pengguna.
Masalah dengan strategi hasil saat ini
Metode hasil umum menggunakan menggunakan setTimeout dengan nilai waktu tunggu 0. Hal ini berfungsi karena callback yang diteruskan ke setTimeout akan memindahkan pekerjaan yang tersisa ke tugas terpisah yang akan diantrekan untuk eksekusi berikutnya. Daripada menunggu browser menghasilkan sendiri, Anda mengatakan "pecah bagian pekerjaan besar ini menjadi bagian yang lebih kecil".
Namun, hasil dengan setTimeout memiliki efek samping yang berpotensi tidak diinginkan: pekerjaan yang muncul setelah titik hasil akan masuk ke bagian belakang antrean tugas. Tugas yang dijadwalkan oleh interaksi pengguna akan tetap masuk ke bagian depan antrean seperti yang seharusnya—tetapi pekerjaan yang tersisa yang ingin Anda lakukan setelah menghasilkan secara eksplisit dapat tertunda lebih lanjut oleh tugas lain dari sumber yang bersaing yang diantrekan di depannya.
Untuk melihat cara kerjanya, coba demo Codepen ini—atau bereksperimenlah dengan versi sematan berikut. Demo ini terdiri dari beberapa tombol yang dapat Anda klik, dan kotak di bawahnya yang mencatat waktu tugas dijalankan. Saat Anda membuka halaman, lakukan tindakan berikut:
- Klik tombol atas berlabel Run tasks periodically, yang akan menjadwalkan tugas pemblokiran untuk dijalankan secara berkala. Saat Anda mengklik tombol ini, log tugas akan diisi dengan beberapa pesan yang berbunyi Ran blocking task with
setInterval. - Selanjutnya, klik tombol berlabel Run loop, yielding with
setTimeouton each iteration.
Anda akan melihat bahwa kotak di bagian bawah demo akan menampilkan sesuatu seperti ini:
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
Output ini menunjukkan perilaku "akhir task queue" yang terjadi saat menghasilkan dengan setTimeout. Loop yang menjalankan proses lima item, dan menghasilkan dengan setTimeout setelah setiap item diproses.
Hal ini menggambarkan masalah umum di web: tidak jarang skrip—terutama skrip pihak ketiga—mendaftarkan fungsi timer yang menjalankan pekerjaan pada interval tertentu. Perilaku "akhir antrean tugas" yang muncul dengan hasil setTimeout berarti bahwa pekerjaan dari sumber tugas lain dapat diantrekan di depan pekerjaan yang tersisa yang harus dilakukan loop setelah menghasilkan.
Bergantung pada aplikasi Anda, hal ini mungkin atau mungkin tidak menjadi hasil yang diinginkan—tetapi dalam banyak kasus, perilaku ini adalah alasan mengapa developer mungkin merasa enggan untuk menyerahkan kontrol thread utama begitu saja. Hasilnya bagus karena interaksi pengguna memiliki peluang untuk berjalan lebih cepat, tetapi juga memungkinkan pekerjaan interaksi non-pengguna lainnya mendapatkan waktu di thread utama. Ini adalah masalah yang nyata—tetapi scheduler.yield dapat membantu menyelesaikannya.
Memasukkan scheduler.yield
scheduler.yield telah tersedia di balik flag sebagai fitur platform web eksperimental sejak Chrome versi 115. Satu pertanyaan yang mungkin Anda miliki adalah "mengapa saya memerlukan fungsi khusus untuk menghasilkan saat setTimeout sudah melakukannya?"
Perlu diperhatikan bahwa hasil bukanlah tujuan desain setTimeout, melainkan efek samping yang bagus dalam menjadwalkan callback untuk dijalankan di masa mendatang—bahkan dengan nilai waktu tunggu 0 yang ditentukan. Namun, yang lebih penting untuk diingat adalah bahwa hasil dengan setTimeout mengirimkan pekerjaan yang tersisa ke belakang antrean tugas. Secara default, scheduler.yield mengirimkan pekerjaan yang tersisa ke depan antrean. Artinya, pekerjaan yang ingin Anda lanjutkan segera setelah menghasilkan tidak akan kalah dengan tugas dari sumber lain (dengan pengecualian interaksi pengguna).
scheduler.yield adalah fungsi yang menghasilkan thread utama dan menampilkan Promise saat dipanggil. Artinya, Anda dapat await di fungsi async:
async function yieldy () {
// Do some work...
// ...
// Yield!
await scheduler.yield();
// Do some more work...
// ...
}
Untuk melihat scheduler.yield beraksi, lakukan hal berikut:
- Buka
chrome://flags. - Aktifkan eksperimen Experimental Web Platform features. Anda mungkin harus memulai ulang Chrome setelah melakukannya.
- Buka halaman demo atau gunakan versi sematan berikut setelah daftar ini.
- Klik tombol atas berlabel Run tasks periodically.
- Terakhir, klik tombol berlabel Run loop, yielding with
scheduler.yieldon each iteration.
Output di kotak di bagian bawah halaman akan terlihat seperti ini:
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
Tidak seperti demo yang menghasilkan menggunakan setTimeout, Anda dapat melihat bahwa loop—meskipun menghasilkan setelah setiap iterasi—tidak mengirimkan pekerjaan yang tersisa ke bagian belakang antrean, melainkan ke bagian depannya. Hal ini memberi Anda yang terbaik dari kedua hal tersebut: Anda dapat menghasilkan untuk meningkatkan responsivitas input di situs, tetapi juga memastikan bahwa pekerjaan yang ingin Anda selesaikan setelah menghasilkan tidak tertunda.
Cobalah!
Jika scheduler.yield terlihat menarik bagi Anda dan Anda ingin mencobanya, Anda dapat melakukannya dengan dua cara mulai Chrome versi 115:
- Jika Anda ingin bereksperimen dengan
scheduler.yieldsecara lokal, ketik dan masukkanchrome://flagsdi kolom URL Chrome, lalu pilih Enable dari menu drop-down di bagian Experimental Web Platform Features. Tindakan ini akan membuatscheduler.yield(dan fitur eksperimental lainnya) hanya tersedia di instance Chrome Anda. - Jika Anda ingin mengaktifkan
scheduler.yielduntuk pengguna Chromium yang sebenarnya di origin yang dapat diakses secara publik, Anda harus mendaftar kescheduler.yielduji coba origin. Hal ini memungkinkan Anda bereksperimen dengan aman menggunakan fitur yang diusulkan untuk jangka waktu tertentu, dan memberikan insight berharga kepada Tim Chrome tentang cara fitur tersebut digunakan di lapangan. Untuk mengetahui informasi selengkapnya tentang cara kerja uji coba origin, baca panduan ini.
Cara Anda menggunakan scheduler.yield—sambil tetap mendukung browser yang tidak menerapkannya—bergantung pada tujuan Anda. Anda dapat menggunakan polyfill resmi. Polyfill berguna jika hal berikut berlaku untuk situasi Anda:
- Anda sudah menggunakan
scheduler.postTaskdi aplikasi untuk menjadwalkan tugas. - Anda ingin dapat menetapkan prioritas tugas dan hasil.
- Anda ingin dapat membatalkan atau memprioritaskan ulang tugas menggunakan class
TaskControlleryang ditawarkanscheduler.postTaskAPI.
Jika hal ini tidak menjelaskan situasi Anda, polyfill mungkin tidak cocok untuk Anda. Dalam hal ini, Anda dapat membuat penggantian sendiri dengan beberapa cara. Pendekatan pertama menggunakan scheduler.yield jika tersedia, tetapi kembali ke setTimeout jika tidak:
// 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:
// ...
}
Hal ini dapat berfungsi, tetapi seperti yang mungkin Anda duga, browser yang tidak mendukung scheduler.yield akan menghasilkan tanpa perilaku "depan antrean". Jika itu berarti Anda lebih memilih untuk tidak menghasilkan sama sekali, Anda dapat mencoba pendekatan lain yang menggunakan scheduler.yield jika tersedia, tetapi tidak akan menghasilkan sama sekali jika tidak:
// 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 adalah tambahan yang menarik untuk scheduler API—yang diharapkan akan memudahkan developer meningkatkan responsivitas daripada strategi hasil saat ini. Jika scheduler.yield tampak seperti API yang berguna bagi Anda, harap berpartisipasilah dalam riset kami untuk membantu meningkatkannya, dan berikan masukan tentang cara API tersebut dapat ditingkatkan lebih lanjut.
Banner besar dari Unsplash, oleh Jonathan Allison.