Membuat situs yang dapat merespons input pengguna dengan cepat telah menjadi salah satu aspek performa web yang paling menantang—yang telah bekerja keras bersama Tim Chrome untuk memenuhi kebutuhan developer web. Tahun ini saja, diumumkan bahwa metrik Interaction to Next Paint (INP) akan beralih dari status eksperimental ke status tertunda. API tersebut kini siap menggantikan First Input Penundaan (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
mulai Chrome versi 115. scheduler.yield
adalah usulan tambahan baru untuk API penjadwal yang memungkinkan cara yang lebih mudah dan lebih baik untuk mengembalikan kontrol ke thread utama daripada metode yang secara tradisional diandalkan.
Saat mengalah
JavaScript menggunakan model run-to-completion untuk menangani tugas. Ini berarti bahwa saat tugas berjalan di thread utama, tugas tersebut akan berjalan selama diperlukan untuk diselesaikan. Setelah tugas selesai, kontrol dihasilkan kembali ke thread utama, yang memungkinkan thread utama memproses tugas berikutnya dalam antrean.
Selain kasus ekstrem saat tugas tidak pernah selesai—seperti misalnya, loop tak terbatas—menghasilkan adalah aspek yang tak terelakkan dari logika penjadwalan tugas JavaScript. Hal ini akan terjadi, ini hanya masalah kapan, dan lebih cepat lebih baik daripada nanti. Jika tugas membutuhkan waktu terlalu lama untuk dijalankan—tepatnya lebih dari 50 milidetik—tugas tersebut dianggap sebagai tugas yang berjalan lama.
Tugas yang panjang adalah sumber respons halaman yang buruk, karena menunda kemampuan browser untuk merespons input pengguna. Semakin sering tugas yang lama terjadi—dan semakin lama dijalankan—semakin besar kemungkinan pengguna mendapatkan kesan bahwa halaman tersebut lambat, atau bahkan merasa bahwa halaman tersebut sudah rusak.
Namun, hanya karena kode Anda memulai tugas di browser bukan berarti Anda harus menunggu sampai tugas tersebut selesai sebelum kontrol dikembalikan ke thread utama. Anda dapat meningkatkan responsivitas terhadap input pengguna di halaman dengan menghasilkan tugas secara eksplisit, yang memecah tugas agar diselesaikan pada kesempatan yang tersedia berikutnya. Hal ini memungkinkan tugas lain mendapatkan waktu di thread utama lebih cepat daripada jika tugas tersebut harus menunggu tugas yang lama selesai.
Ketika Anda secara eksplisit menyerah, Anda memberi tahu browser "halo, saya mengerti bahwa pekerjaan yang akan saya lakukan bisa memakan waktu cukup lama, dan saya tidak ingin Anda harus melakukan semua pekerjaan itu sebelum merespons input pengguna atau tugas lain yang mungkin juga penting". Ini adalah alat berharga dalam kotak peralatan developer yang bisa membantu meningkatkan pengalaman pengguna.
Masalah dengan strategi hasil saat ini
Metode umum untuk menghasilkan menggunakan setTimeout
dengan nilai waktu tunggu 0
. Cara ini berfungsi karena callback yang diteruskan ke setTimeout
akan memindahkan pekerjaan yang tersisa ke tugas terpisah yang akan diantrekan untuk eksekusi berikutnya. Alih-alih menunggu browser berhenti berfungsi, Anda mengatakan "mari kita pisahkan pekerjaan besar ini menjadi bagian-bagian yang lebih kecil".
Namun, menghasilkan dengan setTimeout
membawa efek samping yang berpotensi tidak diinginkan: pekerjaan yang terjadi setelah titik hasil akan berada di bagian belakang task queue. Tugas yang dijadwalkan oleh interaksi pengguna akan tetap berada di antrean paling depan sebagaimana mestinya—tetapi pekerjaan tersisa yang ingin Anda lakukan setelah diberikan secara eksplisit dapat lebih tertunda karena tugas lain dari sumber yang bersaing, yang ada dalam antrean di depannya.
Untuk melihat penerapannya, coba demo Glitch ini—atau bereksperimenlah dalam versi yang disematkan di bawah ini. Demo ini terdiri dari beberapa tombol yang dapat Anda klik, dan kotak di bawahnya yang mencatat saat tugas dijalankan. Saat Anda membuka halaman, lakukan tindakan berikut:
- Klik tombol atas berlabel Jalankan tugas secara berkala, yang akan menjadwalkan tugas pemblokiran untuk dijalankan sesekali. Saat Anda mengklik tombol ini, log tugas akan diisi dengan beberapa pesan yang bertuliskan Menjalankan tugas pemblokiran dengan
setInterval
. - Selanjutnya, klik tombol berlabel Run loop, menghasilkan
setTimeout
di setiap iterasi.
Anda akan melihat bahwa kotak di bagian bawah demo akan bertuliskan 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 "end of task queue" perilaku yang terjadi saat menghasilkan setTimeout
. Loop yang menjalankan memproses lima item, dan menghasilkan setTimeout
setelah setiap item diproses.
Hal ini mengilustrasikan masalah umum di web: hal ini wajar untuk suatu skrip—terutama skrip pihak ketiga—untuk mendaftarkan fungsi timer yang berjalan pada interval tertentu. "Akhir task queue" yang disertakan dengan menghasilkan setTimeout
berarti pekerjaan dari sumber tugas lain mungkin diantrekan sebelum pekerjaan tersisa yang harus dilakukan loop setelah menghasilkan.
Bergantung pada aplikasi Anda, ini mungkin atau mungkin bukan hasil yang diinginkan—tetapi dalam banyak kasus, perilaku ini adalah alasan developer mungkin merasa enggan untuk menyerahkan kontrol thread utama dengan begitu mudah. Hasil itu bagus karena interaksi pengguna memiliki peluang untuk berjalan lebih cepat, tetapi juga memungkinkan pekerjaan interaksi non-pengguna lainnya untuk mendapatkan waktu di thread utama. Ini merupakan masalah yang nyata—tetapi scheduler.yield
dapat membantu menyelesaikannya.
Masuk ke scheduler.yield
scheduler.yield
telah tersedia di belakang tanda sebagai fitur platform web eksperimental sejak Chrome versi 115. Satu pertanyaan yang mungkin Anda miliki adalah "mengapa saya memerlukan fungsi khusus untuk menghasilkan ketika setTimeout
sudah melakukannya?"
Perlu diperhatikan bahwa hasil bukan sasaran desain setTimeout
, melainkan efek samping yang bagus dalam menjadwalkan callback untuk dijalankan pada waktu mendatang di masa mendatang—meskipun dengan nilai waktu tunggu 0
yang ditentukan. Namun, yang lebih penting untuk diingat adalah bahwa menghasilkan dengan setTimeout
akan mengirimkan pekerjaan yang tersisa ke kembali task queue. Secara default, scheduler.yield
mengirimkan pekerjaan yang tersisa ke depan antrean. Ini berarti bahwa pekerjaan yang ingin Anda lanjutkan segera setelah menyerahkannya tidak akan mengganggu tugas dari sumber lain (dengan pengecualian interaksi pengguna yang signifikan).
scheduler.yield
adalah fungsi yang menghasilkan thread utama dan menampilkan Promise
saat dipanggil. Ini berarti Anda dapat melakukan await
dalam fungsi async
:
async function yieldy () {
// Do some work...
// ...
// Yield!
await scheduler.yield();
// Do some more work...
// ...
}
Untuk melihat cara kerja scheduler.yield
, lakukan hal berikut:
- Buka
chrome://flags
- Aktifkan eksperimen Eksperimental fitur Platform Web. Anda mungkin harus memulai ulang Chrome setelah melakukannya.
- Buka halaman demo atau gunakan versi yang disematkan di bawah daftar ini.
- Klik tombol atas berlabel Run tugas secara berkala.
- Terakhir, klik tombol berlabel Run loop, menghasilkan
scheduler.yield
di setiap iterasi.
Output dalam 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
Berbeda dengan demo yang menggunakan setTimeout
, Anda dapat melihat bahwa loop—meskipun menghasilkan setelah setiap iterasi—tidak mengirimkan tugas yang tersisa ke bagian belakang antrean, melainkan ke bagian depannya. Hal ini akan memberikan yang terbaik dari keduanya: Anda dapat menghasilkan untuk meningkatkan responsivitas input di situs Anda, 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.yield
secara lokal, ketik dan masukkanchrome://flags
di kolom URL Chrome, lalu pilih Enable dari dropdown di bagian Experimental Web Platform Features. Tindakan ini akan membuatscheduler.yield
(dan fitur eksperimental lainnya) hanya tersedia di instance Chrome Anda. - Jika ingin mengaktifkan
scheduler.yield
untuk pengguna Chromium sungguhan di origin yang dapat diakses secara publik, Anda harus mendaftar uji coba originscheduler.yield
. Hal ini memungkinkan Anda bereksperimen secara aman dengan fitur yang diusulkan dalam jangka waktu tertentu, dan memberikan wawasan 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
—sekaligus tetap mendukung browser yang tidak menerapkannya—bergantung pada sasaran Anda. Anda dapat menggunakan polyfill resmi. Polyfill berguna jika hal berikut berlaku pada situasi Anda:
- Anda sudah menggunakan
scheduler.postTask
di aplikasi untuk menjadwalkan tugas. - Anda ingin dapat menetapkan tugas dan prioritas hasil.
- Anda ingin dapat membatalkan atau memprioritaskan ulang tugas melalui class
TaskController
yang ditawarkan APIscheduler.postTask
.
Jika tidak sesuai dengan situasi Anda, polyfill mungkin tidak tepat untuk Anda. Dalam hal ini, Anda dapat melakukan roll fallback sendiri dengan beberapa cara. Pendekatan pertama menggunakan scheduler.yield
jika tersedia, tetapi kembali ke setTimeout
jika tidak tersedia:
// 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:
// ...
}
Ini dapat berfungsi, tetapi seperti yang Anda duga, browser yang tidak mendukung scheduler.yield
akan muncul tanpa "bagian depan antrean" perilaku model. Jika itu berarti Anda lebih memilih 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 API penjadwal—yang diharapkan akan memudahkan developer meningkatkan responsivitas dibandingkan strategi yang menghasilkan saat ini. Jika scheduler.yield
tampaknya berguna bagi Anda, berpartisipasilah dalam riset kami untuk membantu meningkatkannya, dan berikan masukan tentang cara untuk meningkatkannya lebih lanjut.
Banner besar dari Unsplash, oleh Jonathan Allison.