Menggunakan scheduler.yield() untuk membagi tugas yang panjang

Dipublikasikan: 6 Maret 2025

Browser Support

  • Chrome: 129.
  • Edge: 129.
  • Firefox: not supported.
  • Safari: not supported.

Source

Halaman terasa lambat dan tidak responsif saat tugas yang lama membuat thread utama sibuk, sehingga mencegahnya melakukan pekerjaan penting lainnya, seperti merespons input pengguna. Akibatnya, bahkan kontrol formulir bawaan dapat terlihat rusak bagi pengguna—seolah-olah halaman dibekukan—apalagi komponen kustom yang lebih kompleks.

scheduler.yield() adalah cara untuk menyerahkan thread utama—memungkinkan browser menjalankan tugas prioritas tinggi yang tertunda—lalu melanjutkan eksekusi dari tempat terakhir. Hal ini membuat halaman lebih responsif dan, pada akhirnya, membantu meningkatkan Interaction to Next Paint (INP).

scheduler.yield menawarkan API ergonomis yang melakukan persis seperti yang dikatakan: eksekusi fungsi yang dipanggil dijeda pada ekspresi await scheduler.yield() dan menghasilkan thread utama, yang membagi tugas. Eksekusi fungsi lainnya—disebut kelanjutan fungsi—akan dijadwalkan untuk berjalan dalam tugas loop peristiwa baru.

async function respondToUserClick() {
  giveImmediateFeedback();
  await scheduler.yield(); // Yield to the main thread.
  slowerComputation();
}

Manfaat spesifik scheduler.yield adalah bahwa kelanjutan setelah hasil dijadwalkan untuk dijalankan sebelum menjalankan tugas serupa lainnya yang telah diantrekan oleh halaman. Prioritasnya adalah melanjutkan tugas daripada memulai tugas baru.

Fungsi seperti setTimeout atau scheduler.postTask juga dapat digunakan untuk membagi tugas, tetapi kelanjutan tersebut biasanya berjalan setelah tugas baru yang sudah diantrekan, yang berpotensi menimbulkan penundaan yang lama antara menyerahkan ke thread utama dan menyelesaikan tugasnya.

Lanjutan yang diprioritaskan setelah menghasilkan

scheduler.yield adalah bagian dari Prioritized Task Scheduling API. Sebagai developer web, kita biasanya tidak membahas urutan event loop menjalankan tugas dalam hal prioritas eksplisit, tetapi prioritas relatif selalu ada, seperti callback requestIdleCallback yang berjalan setelah callback setTimeout yang diantrekan, atau pemroses peristiwa input yang dipicu biasanya berjalan sebelum tugas diantrekan dengan setTimeout(callback, 0).

Penjadwalan Tugas yang Diprioritaskan hanya membuat hal ini lebih eksplisit, sehingga mempermudah untuk mengetahui tugas apa yang akan berjalan sebelum tugas lainnya, dan memungkinkan penyesuaian prioritas untuk mengubah urutan eksekusi tersebut, jika diperlukan.

Seperti yang disebutkan, eksekusi fungsi yang berlanjut setelah menghasilkan dengan scheduler.yield() mendapatkan prioritas yang lebih tinggi daripada memulai tugas lain. Konsep panduannya adalah bahwa kelanjutan tugas harus dijalankan terlebih dahulu, sebelum melanjutkan ke tugas lain. Jika tugas adalah kode yang berperilaku baik yang menghasilkan secara berkala sehingga browser dapat melakukan hal penting lainnya (seperti merespons input pengguna), tugas tersebut tidak boleh dihukum karena menghasilkan dengan diprioritaskan setelah tugas serupa lainnya.

Berikut adalah contohnya: dua fungsi, yang diantrekan untuk dijalankan dalam tugas yang berbeda menggunakan setTimeout.

setTimeout(myJob);
setTimeout(someoneElsesJob);

Dalam hal ini, kedua panggilan setTimeout berada tepat di samping satu sama lain, tetapi di halaman sebenarnya, keduanya dapat dipanggil di tempat yang sama sekali berbeda, seperti skrip pihak pertama dan skrip pihak ketiga yang menyiapkan tugas secara independen untuk dijalankan, atau bisa jadi dua tugas dari komponen terpisah yang dipicu jauh di dalam penjadwal framework Anda.

Berikut adalah tampilan pekerjaan tersebut di DevTools:

Dua tugas yang ditampilkan di panel performa Chrome DevTools. Keduanya ditunjukkan sebagai tugas yang panjang, dengan fungsi 'myJob' menghabiskan seluruh eksekusi tugas pertama, dan 'someoneElsesJob' menghabiskan seluruh tugas kedua.

myJob ditandai sebagai tugas yang lama, sehingga memblokir browser untuk melakukan hal lain saat sedang berjalan. Dengan asumsi bahwa ini berasal dari skrip pihak pertama, kita dapat membaginya:

function myJob() {
  // Run part 1.
  myJobPart1();
  // Yield with setTimeout() to break up long task, then run part2.
  setTimeout(myJobPart2, 0);
}

Karena myJobPart2 dijadwalkan untuk berjalan dengan setTimeout dalam myJob, tetapi penjadwalan tersebut berjalan setelah someoneElsesJob telah dijadwalkan, berikut tampilan eksekusi:

Tiga tugas yang ditampilkan di panel performa Chrome DevTools. Yang pertama menjalankan fungsi 'myJobPart1', yang kedua adalah tugas panjang yang menjalankan 'someoneElsesJob', dan terakhir tugas ketiga menjalankan 'myJobPart2'.

Kita telah membagi tugas dengan setTimeout sehingga browser dapat responsif selama pertengahan myJob, tetapi sekarang bagian kedua myJob hanya berjalan setelah someoneElsesJob selesai.

Dalam beberapa kasus, hal ini mungkin tidak masalah, tetapi biasanya tidak optimal. myJob memberikan hasil ke thread utama untuk memastikan halaman tetap responsif terhadap input pengguna, bukan untuk melepaskan thread utama sepenuhnya. Jika someoneElsesJob sangat lambat, atau banyak tugas lain selain someoneElsesJob juga telah dijadwalkan, mungkin perlu waktu lama sebelum paruh kedua myJob dijalankan. Mungkin itu bukan niat developer saat menambahkan setTimeout tersebut ke myJob.

Masukkan scheduler.yield(), yang menempatkan kelanjutan fungsi apa pun yang memanggilnya dalam antrean prioritas yang sedikit lebih tinggi daripada memulai tugas serupa lainnya. Jika myJob diubah untuk menggunakannya:

async function myJob() {
  // Run part 1.
  myJobPart1();
  // Yield with scheduler.yield() to break up long task, then run part2.
  await scheduler.yield();
  myJobPart2();
}

Sekarang eksekusi akan terlihat seperti:

Dua tugas yang ditampilkan di panel performa Chrome DevTools. Keduanya ditunjukkan sebagai tugas yang panjang, dengan fungsi 'myJob' menghabiskan seluruh eksekusi tugas pertama, dan 'someoneElsesJob' menghabiskan seluruh tugas kedua.

Browser masih memiliki peluang untuk menjadi responsif, tetapi sekarang kelanjutan tugas myJob diprioritaskan daripada memulai tugas baru someoneElsesJob, sehingga myJob selesai sebelum someoneElsesJob dimulai. Hal ini jauh lebih mendekati ekspektasi untuk memberikan thread utama untuk mempertahankan responsivitas, bukan melepaskan thread utama sepenuhnya.

Pewarisan prioritas

Sebagai bagian dari Prioritized Task Scheduling API yang lebih besar, scheduler.yield() disusun dengan baik dengan prioritas eksplisit yang tersedia di scheduler.postTask(). Tanpa prioritas yang ditetapkan secara eksplisit, scheduler.yield() dalam callback scheduler.postTask() pada dasarnya akan bertindak sama seperti contoh sebelumnya.

Namun, jika prioritas ditetapkan, seperti menggunakan prioritas 'background' rendah:

async function lowPriorityJob() {
  part1();
  await scheduler.yield();
  part2();
}

scheduler.postTask(lowPriorityJob, {priority: 'background'});

Lanjutan akan dijadwalkan dengan prioritas yang lebih tinggi daripada tugas 'background' lainnya—mendapatkan lanjutan prioritas yang diharapkan sebelum tugas 'background' yang tertunda—tetapi tetap memiliki prioritas yang lebih rendah daripada tugas default atau prioritas tinggi lainnya; tugas ini tetap merupakan tugas 'background'.

Artinya, jika Anda menjadwalkan pekerjaan prioritas rendah dengan 'background' scheduler.postTask() (atau dengan requestIdleCallback), kelanjutan setelah scheduler.yield() di dalamnya juga akan menunggu hingga sebagian besar tugas lainnya selesai dan thread utama tidak ada aktivitas untuk dijalankan, yang persis seperti yang Anda inginkan dari menghasilkan tugas prioritas rendah.

Cara menggunakan API

Untuk saat ini, scheduler.yield() hanya tersedia di browser berbasis Chromium. Jadi, untuk menggunakannya, Anda harus mendeteksi fitur dan kembali ke cara sekunder untuk menghasilkan browser lain.

scheduler-polyfill adalah polyfill kecil untuk scheduler.postTask dan scheduler.yield yang secara internal menggunakan kombinasi metode untuk mengemulasi banyak kemampuan API penjadwalan di browser lain (meskipun pewarisan prioritas scheduler.yield() tidak didukung).

Bagi mereka yang ingin menghindari polyfill, salah satu metodenya adalah menghasilkan menggunakan setTimeout() dan menerima hilangnya kelanjutan yang diprioritaskan, atau bahkan tidak menghasilkan di browser yang tidak didukung jika hal itu tidak dapat diterima. Lihat dokumentasi scheduler.yield() di Mengoptimalkan tugas yang lama untuk mengetahui informasi selengkapnya.

Jenis wicg-task-scheduling juga dapat digunakan untuk mendapatkan pemeriksaan jenis dan dukungan IDE jika Anda mendeteksi fitur scheduler.yield() dan menambahkan penggantian sendiri.

Pelajari lebih lanjut

Untuk mengetahui informasi selengkapnya tentang API dan cara API berinteraksi dengan prioritas tugas dan scheduler.postTask(), lihat dokumen scheduler.yield() dan Penjadwalan Tugas yang Diprioritaskan di MDN.

Untuk mempelajari lebih lanjut tugas yang lama, pengaruhnya terhadap pengalaman pengguna, dan tindakan yang harus dilakukan, baca artikel tentang mengoptimalkan tugas yang lama.