Memperkenalkan uji coba origin scheduler.results

Membuat situs yang merespons input pengguna dengan cepat adalah salah satu aspek performa web yang paling menantang—aspek yang telah diupayakan Tim Chrome untuk membantu developer web memenuhinya. Baru tahun ini, diumumkan bahwa metrik Interaction to Next Paint (INP) akan berstatus dari eksperimental menjadi tertunda. INP kini siap menggantikan First Input Delay (FID) sebagai Core Web Vitals 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 dari Chrome versi 115. scheduler.yield adalah penambahan baru yang diusulkan ke API penjadwal yang memungkinkan cara yang lebih mudah dan lebih baik untuk memberikan kontrol kembali ke thread utama daripada metode yang telah diandalkan secara tradisional.

Saat memberikan

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 diserahkan kembali ke thread utama, yang memungkinkan thread utama memproses tugas berikutnya dalam antrean.

Selain kasus ekstrem saat tugas tidak pernah selesai—misalnya, loop tak terbatas, misalnya—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 memerlukan waktu terlalu lama untuk dijalankan—lebih dari 50 milidetik, tepatnya—tugas tersebut dianggap sebagai tugas yang berjalan lama.

Tugas yang lama adalah sumber responsivitas halaman yang buruk, karena tugas tersebut menunda kemampuan browser untuk merespons input pengguna. Makin sering tugas yang lama terjadi—dan makin lama tugas tersebut berjalan—makin besar kemungkinan pengguna mendapatkan kesan bahwa halaman 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 respons terhadap input pengguna di halaman dengan memberikan tugas secara eksplisit, yang membagi tugas untuk diselesaikan pada kesempatan berikutnya yang tersedia. 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 membagi tugas dapat memfasilitasi responsivitas input yang lebih baik. Di bagian atas, tugas yang lama akan memblokir pengendali peristiwa agar tidak berjalan hingga tugas selesai. Di bagian bawah, tugas yang dikelompokkan memungkinkan pengendali peristiwa berjalan lebih cepat dari biasanya.
Visualisasi yang menghasilkan kontrol kembali ke thread utama. Di bagian atas, pengembalian 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, penghasil dilakukan secara eksplisit, yang membagi tugas panjang menjadi beberapa tugas yang lebih kecil. Hal ini memungkinkan interaksi pengguna berjalan lebih cepat, sehingga meningkatkan responsivitas input dan INP.

Saat secara eksplisit memberikan hasil, Anda memberi tahu browser "hey, 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 di kotak alat developer yang dapat sangat 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 memiliki efek samping yang berpotensi tidak diinginkan: pekerjaan yang datang setelah titik hasil akan masuk ke bagian belakang antrean tugas. Tugas yang dijadwalkan oleh interaksi pengguna akan tetap berada di antrean paling depan sebagaimana mestinya—tetapi pekerjaan lain 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 cara kerjanya, coba demo Glitch ini—atau bereksperimenlah dengan versi tersemat di bawah. Demo ini terdiri dari beberapa tombol yang dapat Anda klik, dan kotak di bawahnya yang mencatat log 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 setiap saat. 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, yielding with setTimeout on each iteration.

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 perilaku "akhir antrean tugas" yang terjadi saat menghasilkan dengan 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. Perilaku "akhir antrean tugas" yang disertakan dengan menghasilkan setTimeout berarti bahwa pekerjaan dari sumber tugas lain dapat diantrekan sebelum pekerjaan yang tersisa yang harus dilakukan loop setelah menghasilkan.

Bergantung pada aplikasi Anda, hal ini mungkin atau mungkin tidak merupakan hasil yang diinginkan—tetapi dalam banyak kasus, perilaku ini adalah alasan developer mungkin merasa enggan untuk melepaskan kontrol thread utama dengan begitu mudah. Memberikan hasil yang baik karena interaksi pengguna memiliki kesempatan 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 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 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 mengirim pekerjaan yang tersisa ke belakang antrean tugas. Secara default, scheduler.yield mengirim pekerjaan yang tersisa ke depan antrean. Artinya, pekerjaan yang ingin Anda lanjutkan segera setelah menghasilkan tidak akan diprioritaskan lebih rendah daripada tugas dari sumber lain (dengan pengecualian penting pada interaksi pengguna).

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 Platform Web 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 Jalankan tugas secara berkala.
  5. Terakhir, klik tombol berlabel Run loop, yielding with scheduler.yield on each iteration.

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

Tidak seperti demo yang menghasilkan menggunakan setTimeout, Anda dapat melihat bahwa loop—meskipun menghasilkan setelah setiap iterasi—tidak mengirim pekerjaan yang tersisa ke bagian belakang antrean, tetapi ke bagian depannya. Hal ini memberikan yang terbaik dari kedua sisi: 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 dan Anda ingin mencobanya, Anda dapat melakukannya dengan dua cara mulai dari Chrome versi 115:

  1. Jika Anda ingin bereksperimen dengan scheduler.yield secara lokal, ketik dan masukkan chrome://flags di kolom URL Chrome, lalu pilih Aktifkan dari dropdown di bagian Fitur Platform Web Eksperimental. 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 sebenarnya di origin yang dapat diakses secara publik, Anda harus mendaftar ke 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 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 sasaran Anda. Anda dapat menggunakan polyfill resmi. Polyfill berguna jika hal berikut berlaku untuk situasi Anda:

  1. Anda sudah menggunakan scheduler.postTask di aplikasi untuk menjadwalkan tugas.
  2. Anda ingin dapat menetapkan prioritas tugas dan hasil.
  3. Anda ingin dapat membatalkan atau memprioritaskan ulang tugas melalui class TaskController yang ditawarkan scheduler.postTask API.

Jika hal ini tidak menjelaskan situasi Anda, polyfill mungkin tidak cocok 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:
  // ...
}

Cara ini dapat berfungsi, tetapi seperti yang dapat Anda duga, browser yang tidak mendukung scheduler.yield akan menghasilkan tanpa perilaku "di awal antrean". Jika Anda tidak ingin menghasilkan sama sekali, Anda dapat mencoba pendekatan lain yang menggunakan scheduler.yield jika tersedia, tetapi tidak akan menghasilkan sama sekali jika tidak tersedia:

// 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 untuk meningkatkan responsivitas daripada strategi penghasil saat ini. Jika scheduler.yield tampak seperti API yang berguna bagi Anda, harap berpartisipasi dalam riset kami untuk membantu meningkatkannya, dan berikan masukan tentang cara meningkatkannya lebih lanjut.

Gambar hero dari Unsplash, oleh Jonathan Allison.