Memperkenalkan uji coba origin scheduler.results

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 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.

Penggambaran tentang bagaimana memecah tugas dapat memfasilitasi responsivitas input yang lebih baik. Di bagian atas, tugas yang panjang memblokir pengendali peristiwa agar tidak berjalan sampai tugas selesai. Di bagian bawah, tugas yang dipotong memungkinkan {i>event handler<i} untuk berjalan lebih cepat dari yang seharusnya.
Visualisasi yang menghasilkan kontrol kembali ke thread utama. Di bagian atas, menghasilkan hanya terjadi setelah tugas berjalan hingga selesai, yang berarti tugas dapat memerlukan waktu lebih lama untuk diselesaikan sebelum mengembalikan kontrol kembali ke thread utama. Di bagian bawah, menghasilkan dilakukan secara eksplisit, memecah tugas yang panjang menjadi beberapa tugas yang lebih kecil. Hal ini memungkinkan interaksi pengguna berjalan lebih cepat, yang meningkatkan responsivitas input dan INP.

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:

  1. 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.
  2. 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 juga. 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 menghasilkan 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:

  1. Buka chrome://flags
  2. Aktifkan eksperimen Fitur Web Platform Eksperimental. Anda mungkin harus memulai ulang Chrome setelah melakukannya.
  3. Buka halaman demo atau gunakan versi yang disematkan di bawah daftar ini.
  4. Klik tombol atas berlabel Run tugas secara berkala.
  5. 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 kedua hal tersebut: Anda dapat menghasilkan peningkatan responsivitas input di situs Anda, tetapi juga memastikan bahwa pekerjaan yang ingin Anda selesaikan setelah diserahkan tidak tertunda.

Cobalah!

Jika scheduler.yield terlihat menarik bagi Anda dan Anda ingin mencobanya, Anda dapat melakukannya dengan dua cara mulai Chrome versi 115:

  1. Jika Anda ingin bereksperimen dengan scheduler.yield secara lokal, ketik dan masukkan chrome://flags di kolom URL Chrome, lalu pilih Enable dari dropdown di bagian Experimental Web Platform Features. Tindakan ini akan membuat scheduler.yield (dan fitur eksperimental lainnya) hanya tersedia di instance Chrome Anda.
  2. Jika ingin mengaktifkan scheduler.yield untuk pengguna Chromium sungguhan di origin yang dapat diakses secara publik, Anda harus mendaftar uji coba origin scheduler.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:

  1. Anda sudah menggunakan scheduler.postTask di aplikasi untuk menjadwalkan tugas.
  2. Anda ingin dapat menetapkan tugas dan prioritas hasil.
  3. Anda ingin dapat membatalkan atau memprioritaskan ulang tugas melalui class TaskController yang ditawarkan API scheduler.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 sepertinya API yang 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.