Penjadwalan JS yang lebih baik dengan isInputMenunggu()

JavaScript API baru yang dapat membantu Anda menghindari kompromi antara performa pemuatan dan responsivitas input.

Nate Schloss
Nate Schloss
Andrew Comminos
Andrew Comminos

Pemuatan cepat itu sulit. Situs yang memanfaatkan JS untuk merender kontennya saat ini harus melakukan kompromi antara performa pemuatan dan respons input: melakukan semua pekerjaan yang diperlukan untuk menampilkan semuanya sekaligus (performa pemuatan yang lebih baik, responsivitas input yang lebih buruk), atau memotong tugas menjadi tugas yang lebih kecil agar tetap responsif terhadap input dan paint (performa pemuatan yang lebih buruk, responsivitas input yang lebih baik).

Untuk meniadakan keharusan melakukan kompromi ini, Facebook mengusulkan dan menerapkan isInputPending() API di Chromium untuk meningkatkan responsivitas tanpa menguraikan. Berdasarkan masukan uji coba origin, kami telah melakukan sejumlah update pada API dan dengan senang hati mengumumkan bahwa API kini dikirimkan secara default di Chromium 87.

Kompatibilitas browser

Dukungan Browser

  • 87
  • 87
  • x
  • x

isInputPending() dikirimkan dalam browser berbasis Chromium mulai versi 87. Tidak ada browser lain yang menandakan intent untuk mengirimkan API.

Latar belakang

Sebagian besar pekerjaan di ekosistem JS saat ini diselesaikan di satu thread: thread utama. Hal ini memberikan model eksekusi yang andal bagi developer, tetapi pengalaman pengguna (khususnya, responsivitas) dapat sangat terpengaruh jika skrip dieksekusi dalam waktu yang lama. Misalnya, jika halaman melakukan banyak pekerjaan saat peristiwa input diaktifkan, halaman tidak akan menangani peristiwa input klik sampai pekerjaan tersebut selesai.

Praktik terbaik saat ini adalah menangani masalah ini dengan membagi JavaScript menjadi blok yang lebih kecil. Saat halaman dimuat, halaman tersebut dapat menjalankan sedikit JavaScript, lalu menghasilkan dan meneruskan kontrol kembali ke browser. Selanjutnya, browser dapat memeriksa antrean peristiwa inputnya dan melihat apakah ada sesuatu yang perlu diberitahukan kepada halaman tentang hal tersebut. Kemudian browser dapat kembali menjalankan blok JavaScript ketika mereka ditambahkan. Ini membantu, tetapi dapat menyebabkan masalah lain.

Setiap kali halaman mengembalikan kontrol ke browser, browser memerlukan waktu untuk memeriksa antrean peristiwa inputnya, memproses peristiwa, dan mengambil blok JavaScript berikutnya. Meskipun browser merespons peristiwa lebih cepat, waktu pemuatan halaman secara keseluruhan melambat. Dan jika kami menyerah terlalu sering, pemuatan halaman akan terlalu lambat. Jika kami tidak terlalu sering memberikan hasil, browser akan memerlukan waktu lebih lama untuk merespons peristiwa pengguna, dan orang-orang akan merasa frustrasi. Tidak seru.

Diagram yang menunjukkan bahwa saat Anda menjalankan tugas JS yang panjang, browser memiliki lebih sedikit waktu untuk mengirim peristiwa.

Di Facebook, kami ingin mengetahui seperti apa tampilannya jika kami mendapatkan pendekatan baru untuk pemuatan yang menghilangkan kompromi yang menjengkelkan ini. Kami menghubungi teman-teman kami di Chrome mengenai hal ini, dan mengajukan proposal untuk isInputPending(). isInputPending() API adalah yang pertama menggunakan konsep interupsi untuk input pengguna di web, dan memungkinkan JavaScript dapat memeriksa input tanpa bergantung pada browser.

Diagram yang menunjukkan bahwa isInputPending() memungkinkan JS Anda memeriksa apakah ada input pengguna yang tertunda, tanpa sepenuhnya menghasilkan eksekusi kembali ke browser.

Karena ada minat terhadap API ini, kami bermitra dengan kolega kami di Chrome untuk menerapkan dan mengirimkan fitur ini di Chromium. Dengan bantuan dari engineer Chrome, kami mendapatkan patch yang ditempatkan di balik uji coba origin (yang merupakan cara Chrome menguji perubahan dan mendapatkan masukan dari developer sebelum merilis API sepenuhnya).

Kami kini telah menerima masukan dari uji coba origin dan dari anggota lain W3C Web Performance Working Group serta menerapkan perubahan pada API.

Contoh: penjadwal hasil

Misalkan Anda punya banyak tugas pemblokiran tampilan yang harus dilakukan untuk memuat halaman, misalnya membuat markup dari komponen, memperhitungkan bilangan prima, atau hanya menggambar indikator lingkaran berputar pemuatan yang keren. Masing-masing dipecah menjadi item kerja yang terpisah. Dengan menggunakan pola penjadwal, mari kita buat sketsa bagaimana kita dapat memproses pekerjaan kita dalam fungsi processWorkQueue() hipotetis:

const DEADLINE = performance.now() + QUANTUM;
while (workQueue.length > 0) {
  if (performance.now() >= DEADLINE) {
    // Yield the event loop if we're out of time.
    setTimeout(processWorkQueue);
    return;
  }
  let job = workQueue.shift();
  job.execute();
}

Dengan memanggil processWorkQueue() nanti dalam tugas makro baru melalui setTimeout(), kami memberi browser kemampuan untuk tetap responsif terhadap input (browser dapat menjalankan pengendali peristiwa sebelum pekerjaan dilanjutkan) sambil tetap mengelola agar berjalan relatif tanpa gangguan. Meskipun demikian, kita mungkin dijadwalkan untuk waktu yang lama oleh pekerjaan lain yang menginginkan kontrol loop peristiwa, atau mendapatkan hingga QUANTUM milidetik latensi peristiwa tambahan.

Tidak apa-apa, tetapi bisakah kita melakukan yang lebih baik? Tentu saja.

const DEADLINE = performance.now() + QUANTUM;
while (workQueue.length > 0) {
  if (navigator.scheduling.isInputPending() || performance.now() >= DEADLINE) {
    // Yield if we have to handle an input event, or we're out of time.
    setTimeout(processWorkQueue);
    return;
  }
  let job = workQueue.shift();
  job.execute();
}

Dengan memperkenalkan panggilan ke navigator.scheduling.isInputPending(), kita dapat merespons input lebih cepat sambil tetap memastikan bahwa pekerjaan pemblokiran tampilan kita akan dijalankan tanpa gangguan. Jika kita tidak tertarik untuk menangani hal selain input (misalnya mengecat) hingga pekerjaan selesai, kita juga dapat dengan mudah meningkatkan panjang QUANTUM.

Secara default, peristiwa "berkelanjutan" tidak ditampilkan dari isInputPending(). Alat tersebut meliputi mousemove, pointermove, dan lainnya. Jika Anda juga tertarik untuk menghasilkan game ini, tidak masalah. Dengan memberikan objek ke isInputPending() dengan includeContinuous yang ditetapkan ke true, kita siap memulai:

const DEADLINE = performance.now() + QUANTUM;
const options = { includeContinuous: true };
while (workQueue.length > 0) {
  if (navigator.scheduling.isInputPending(options) || performance.now() >= DEADLINE) {
    // Yield if we have to handle an input event (any of them!), or we're out of time.
    setTimeout(processWorkQueue);
    return;
  }
  let job = workQueue.shift();
  job.execute();
}

Selesai. Framework seperti React mem-build dukungan isInputPending() ke dalam library penjadwalan inti menggunakan logika yang serupa. Semoga hal ini akan mengarahkan developer yang menggunakan framework ini untuk dapat memanfaatkan isInputPending() di balik layar tanpa penulisan ulang yang signifikan.

Hasil tidak selalu buruk

Perlu diperhatikan bahwa menghasilkan lebih sedikit bukanlah solusi yang tepat untuk setiap kasus penggunaan. Ada banyak alasan untuk menampilkan kontrol ke browser selain untuk memproses peristiwa input, seperti untuk melakukan rendering dan menjalankan skrip lain di halaman.

Terdapat kasus saat browser tidak dapat mengatribusikan dengan benar peristiwa input yang tertunda. Secara khusus, menyetel klip dan mask yang kompleks untuk iframe lintas origin dapat melaporkan negatif palsu (yaitu isInputPending() dapat secara tidak terduga menampilkan salah saat menargetkan frame ini). Pastikan Anda cukup sering memberikan hasil jika situs Anda memerlukan interaksi dengan subframe bergaya.

Perhatikan juga halaman lain yang berbagi loop peristiwa. Pada platform seperti Chrome untuk Android, cukup umum bagi beberapa origin untuk berbagi loop peristiwa. isInputPending() tidak akan pernah menampilkan true jika input dikirim ke frame lintas origin, sehingga halaman yang berjalan di latar belakang dapat mengganggu responsivitas halaman latar depan. Anda dapat mengurangi, menunda, atau lebih sering menghasilkan saat melakukan pekerjaan di latar belakang menggunakan Page Visibility API.

Sebaiknya gunakan isInputPending() dengan bijaksana. Jika tidak ada tugas yang memblokir pengguna, bersikaplah baik terhadap orang lain di loop peristiwa dengan membuat lebih sering. Tugas yang panjang dapat membahayakan.

Masukan

  • Berikan masukan tentang spesifikasinya di repositori is-input-pending.
  • Hubungi @acomminos (salah satu penulis spesifikasi) di Twitter.

Kesimpulan

Kami senang isInputPending() diluncurkan, dan developer dapat mulai menggunakannya sekarang. API ini adalah pertama kalinya Facebook membuat API web baru dan membawanya dari inkubasi ide ke proposal standar, lalu benar-benar mengirimkannya di browser. Kami ingin berterima kasih kepada semua orang yang telah membantu kami mencapai titik ini. Kami juga ingin mengucapkan terima kasih khusus kepada semua orang di Chrome yang telah membantu kami mewujudkan ide ini dan mewujudkannya.

Foto utama oleh will H McMahan di Unsplash.