Memahami Interaction to Next Paint (INP)

1. Pengantar

Demo interaktif dan codelab untuk mempelajari Interaction to Next Paint (INP).

Diagram yang menggambarkan interaksi pada thread utama. Pengguna membuat input saat memblokir tugas yang berjalan. Input ditunda sampai tugas tersebut selesai, setelah itu pointerup, mouseup, dan pemroses peristiwa klik dijalankan, kemudian pekerjaan rendering dan proses menggambar dimulai hingga frame berikutnya disajikan

Prasyarat

  • Pengetahuan tentang pengembangan HTML dan JavaScript.
  • Direkomendasikan: baca dokumentasi INP.

Yang Anda pelajari

  • Bagaimana interaksi pengguna dan penanganan Anda terhadap interaksi tersebut memengaruhi responsivitas halaman.
  • Cara mengurangi dan menghilangkan penundaan untuk pengalaman pengguna yang lancar.

Yang Anda perlukan

  • Komputer dengan kemampuan untuk meng-clone kode dari GitHub dan menjalankan perintah npm.
  • Editor teks.
  • Chrome versi terbaru agar semua pengukuran interaksi berfungsi.

2. Memulai persiapan

Mendapatkan dan menjalankan kode

Kode ini ditemukan di repositori web-vitals-codelabs.

  1. Clone repo di terminal Anda: git clone https://github.com/GoogleChromeLabs/web-vitals-codelabs.git
  2. Jelajahi direktori yang di-clone: cd web-vitals-codelabs/understanding-inp
  3. Menginstal dependensi: npm ci
  4. Mulai server web: npm run start
  5. Buka http://localhost:5173/understanding-inp/ di browser Anda

Ringkasan aplikasi

Ada di bagian atas halaman terdapat penghitung Skor dan tombol Increment. Sebuah demo klasik tentang reaktivitas dan responsivitas!

Screenshot aplikasi demo untuk codelab ini

Di bawah tombol terdapat empat pengukuran:

  • INP: skor INP saat ini, yang biasanya merupakan interaksi terburuk.
  • Interaksi: skor interaksi terbaru.
  • FPS: frame thread per detik utama halaman.
  • Timer: animasi timer yang berjalan untuk membantu memvisualisasikan jank.

Entri FPS dan Timer sama sekali tidak diperlukan untuk mengukur interaksi. Mereka ditambahkan agar Anda lebih mudah memvisualisasikan respons.

Cobalah

Coba berinteraksi dengan tombol Penambahan dan lihat skor meningkat. Apakah nilai INP dan Interaction berubah dengan setiap penambahan?

INP mengukur durasi waktu yang diperlukan sejak pengguna berinteraksi hingga halaman benar-benar menampilkan update yang dirender kepada pengguna.

3. Mengukur interaksi dengan Chrome DevTools

Buka DevTools dari Alat Lainnya > Menu Developer Tools, dengan mengklik kanan halaman dan memilih Periksa, atau dengan menggunakan pintasan keyboard.

Beralihlah ke panel Performa, yang akan Anda gunakan untuk mengukur interaksi.

Screenshot panel Performa DevTools bersama aplikasi

Selanjutnya, rekam interaksi di panel Performa.

  1. Tekan rekam.
  2. Lakukan interaksi dengan halaman (tekan tombol Increment).
  3. Hentikan perekaman.

Di linimasa yang dihasilkan, Anda akan menemukan jalur Interaksi. Perluas dengan mengklik segitiga di sisi kiri.

Demonstrasi animasi perekaman interaksi menggunakan panel performa DevTools

Dua interaksi muncul. Perbesar tampilan kedua dengan men-scroll atau menahan tombol W.

Screenshot panel Performa DevTools, kursor diarahkan ke interaksi di panel, dan tooltip yang mencantumkan waktu interaksi yang singkat

Mengarahkan kursor ke interaksi, Anda dapat melihat interaksinya cepat, tidak menghabiskan waktu dalam durasi pemrosesan, serta jumlah waktu minimum dalam penundaan input dan penundaan presentasi, yang durasinya akan bergantung pada kecepatan mesin Anda.

4. Pemroses peristiwa yang berjalan lama

Buka file index.js, dan hapus tanda komentar pada fungsi blockFor di dalam pemroses peristiwa.

Lihat kode lengkap: click_block.html

button.addEventListener('click', () => {
  blockFor(1000);
  score.incrementAndUpdateUI();
});

Simpan file. Server akan melihat perubahan dan memuat ulang halaman untuk Anda.

Coba berinteraksi lagi dengan halaman tersebut. Interaksi sekarang akan terasa lebih lambat.

Trace performa

Ambil rekaman lain di panel Performa untuk melihat tampilannya di sana.

Interaksi satu detik di panel Performa

Yang dahulu hanya merupakan interaksi singkat sekarang membutuhkan waktu satu detik penuh.

Saat mengarahkan kursor ke interaksi, perhatikan waktu yang hampir seluruhnya dihabiskan dalam "Memproses durasi", yang merupakan jumlah waktu yang dibutuhkan untuk menjalankan callback pemroses peristiwa. Karena panggilan blockFor yang memblokir sepenuhnya berada dalam pemroses peristiwa, waktu ini akan berjalan.

5. Eksperimen: durasi pemrosesan

Coba cara untuk mengatur ulang pekerjaan pemroses peristiwa untuk melihat efeknya terhadap INP.

Mengupdate UI terlebih dahulu

Apa yang terjadi jika Anda menukar urutan panggilan js—update UI terlebih dahulu, lalu blokir?

Lihat kode lengkap: ui_first.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
  blockFor(1000);
});

Apakah Anda melihat UI muncul sebelumnya? Apakah urutan tersebut memengaruhi skor INP?

Coba lakukan pelacakan dan periksa interaksi tersebut untuk melihat apakah ada perbedaan.

Pemroses terpisah

Bagaimana jika Anda memindahkan pekerjaan ke pemroses peristiwa terpisah? Mengupdate UI dalam satu pemroses peristiwa, dan memblokir halaman dari pemroses terpisah.

Lihat kode lengkap: two_click.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
});

button.addEventListener('click', () => {
  blockFor(1000);
});

Seperti apa tampilan di panel performa sekarang?

Berbagai jenis peristiwa

Sebagian besar interaksi akan memicu berbagai jenis peristiwa, mulai dari pointer atau peristiwa utama, hingga pengarahan kursor, fokus/buram, dan peristiwa sintetis seperti beforechange dan beforeinput.

Banyak halaman sebenarnya memiliki pemroses untuk berbagai peristiwa.

Apa yang terjadi jika Anda mengubah jenis peristiwa untuk pemroses peristiwa? Misalnya, ganti salah satu pemroses peristiwa click dengan pointerup atau mouseup?

Lihat kode lengkap: diff_handlers.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
});

button.addEventListener('pointerup', () => {
  blockFor(1000);
});

Tidak ada update UI

Apa yang terjadi jika Anda menghapus panggilan untuk mengupdate UI dari pemroses peristiwa?

Lihat kode lengkap: no_ui.html

button.addEventListener('click', () => {
  blockFor(1000);
  // score.incrementAndUpdateUI();
});

6. Memproses hasil eksperimen durasi

Pelacakan performa: mengupdate UI terlebih dahulu

Lihat kode lengkap: ui_first.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
  blockFor(1000);
});

Dengan melihat rekaman panel Performa saat tombol diklik, Anda dapat melihat bahwa hasilnya tidak berubah. Meskipun update UI dipicu sebelum kode pemblokiran, browser tidak benar-benar mengupdate apa yang ditampilkan ke layar sampai pemroses peristiwa selesai, yang berarti interaksi masih memerlukan waktu lebih dari satu detik untuk diselesaikan.

Interaksi diam satu detik di panel Performa

Rekaman aktivitas performa: pemroses terpisah

Lihat kode lengkap: two_click.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
});

button.addEventListener('click', () => {
  blockFor(1000);
});

Sekali lagi, secara fungsional tidak ada perbedaan. Interaksi masih membutuhkan waktu satu detik penuh.

Dengan memperbesar interaksi klik, Anda akan melihat bahwa memang ada dua fungsi berbeda yang dipanggil sebagai hasil dari peristiwa click.

Seperti yang diharapkan, yang pertama—memperbarui UI—berjalan sangat cepat, sedangkan yang kedua memerlukan waktu penuh. Namun, jumlah efeknya menghasilkan interaksi lambat yang sama dengan pengguna akhir.

Tampilan yang lebih jelas pada interaksi satu detik dalam contoh ini, menampilkan panggilan fungsi pertama yang memerlukan waktu kurang dari satu milidetik untuk diselesaikan

Trace performa: berbagai jenis peristiwa

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
});

button.addEventListener('pointerup', () => {
  blockFor(1000);
});

Hasil ini sangat mirip. Interaksi masih satu detik penuh; satu-satunya perbedaan adalah pemroses click khusus update UI yang lebih pendek kini berjalan setelah pemroses pointerup yang memblokir.

Tampilan yang diperbesar pada interaksi satu detik dalam contoh ini, menampilkan pemroses peristiwa klik yang memerlukan waktu kurang dari satu milidetik untuk menyelesaikannya, setelah pemroses pointerup

Trace performa: tidak ada update UI

Lihat kode lengkap: no_ui.html

button.addEventListener('click', () => {
  blockFor(1000);
  // score.incrementAndUpdateUI();
});
  • Skor tidak diperbarui, tetapi halaman tetap diperbarui.
  • Animasi, efek CSS, tindakan komponen web default (input formulir), entri teks, penandaan teks, semuanya terus diperbarui.

Dalam hal ini, tombol beralih ke status aktif dan kembali saat diklik, yang memerlukan paint oleh browser, yang berarti masih ada INP.

Karena pemroses peristiwa memblokir thread utama selama satu detik untuk mencegah halaman digambar, interaksi masih membutuhkan waktu satu detik penuh.

Mengambil rekaman panel Performa akan menunjukkan interaksi yang hampir sama dengan interaksi sebelumnya.

Interaksi diam satu detik di panel Performa

Bawa pulang

Kode apa pun yang berjalan di pemroses peristiwa apa pun akan menunda interaksi.

  • Itu mencakup pemroses yang terdaftar dari berbagai skrip dan framework atau kode library yang berjalan di pemroses, seperti update status yang memicu render komponen.
  • Bukan hanya kode Anda sendiri, tetapi juga semua skrip pihak ketiga.

Ini masalah umum.

Terakhir: hanya karena kode Anda tidak memicu paint, bukan berarti paint tidak akan menunggu pemroses peristiwa yang lambat selesai.

7. Eksperimen: penundaan input

Bagaimana dengan kode yang berjalan lama di luar pemroses peristiwa? Contoh:

  • Jika Anda memiliki <script> yang dimuat terlambat yang secara acak memblokir halaman selama pemuatan.
  • Panggilan API, seperti setInterval, yang memblokir halaman secara berkala?

Coba hapus blockFor dari pemroses peristiwa dan tambahkan ke setInterval():

Lihat kode lengkap: input_jeda.html

setInterval(() => {
  blockFor(1000);
}, 3000);


button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
});

Apa yang terjadi?

8. Hasil eksperimen penundaan input

Lihat kode lengkap: input_jeda.html

setInterval(() => {
  blockFor(1000);
}, 3000);


button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
});

Merekam klik tombol yang terjadi saat tugas pemblokiran setInterval sedang berjalan akan menghasilkan interaksi yang berjalan lama, meskipun tidak ada tugas pemblokir yang dilakukan dalam interaksi itu sendiri.

Periode yang berjalan lama ini sering disebut tugas panjang.

Mengarahkan kursor ke interaksi di DevTools, Anda akan dapat melihat waktu interaksi yang sekarang terutama dikaitkan dengan penundaan input, bukan durasi pemrosesan.

Panel Performance DevTools menampilkan tugas pemblokiran satu detik, interaksi yang dilakukan sebagian dari tugas tersebut, dan interaksi 642 milidetik, yang sebagian besar disebabkan oleh penundaan input

Perhatikan, hal tersebut tidak selalu memengaruhi interaksi. Jika tidak mengklik saat tugas sedang berjalan, Anda mungkin beruntung. "Acak" seperti itu bersin bisa menjadi mimpi buruk untuk {i>debug<i} ketika mereka kadang-kadang hanya menyebabkan masalah.

Salah satu cara untuk melacaknya adalah dengan mengukur tugas yang panjang (atau Bingkai Animasi Panjang), dan Total Waktu Pemblokiran.

9. Presentasi lambat

Sejauh ini, kita telah melihat performa JavaScript, melalui penundaan input atau pemroses peristiwa, tetapi apa lagi yang memengaruhi rendering paint berikutnya?

Nah, memperbarui halaman dengan efek yang mahal!

Bahkan jika pembaruan halaman dilakukan dengan cepat, browser mungkin masih harus bekerja keras untuk merendernya!

Di thread utama:

  • Framework UI yang perlu merender update setelah perubahan status
  • Perubahan DOM, atau mengganti banyak pemilih kueri CSS yang mahal dapat memicu banyak Gaya, Tata Letak, dan Menggambar.

Di luar thread utama:

  • Menggunakan CSS untuk mengaktifkan efek GPU
  • Menambahkan gambar beresolusi tinggi yang sangat besar
  • Menggunakan SVG/Canvas untuk menggambar adegan yang kompleks

Sketsa berbagai elemen rendering di web

RenderingNG

Beberapa contoh yang umum ditemukan di web:

  • Situs SPA yang membangun kembali seluruh DOM setelah mengklik link, tanpa menjeda untuk memberikan masukan visual awal.
  • Halaman penelusuran yang menawarkan filter penelusuran kompleks dengan antarmuka pengguna yang dinamis, tetapi menjalankan pemroses yang mahal untuk melakukannya.
  • Tombol mode gelap yang memicu gaya/tata letak untuk seluruh halaman

10. Eksperimen: penundaan presentasi

requestAnimationFrame lambat

Mari kita simulasikan penundaan presentasi yang lama menggunakan requestAnimationFrame() API.

Pindahkan panggilan blockFor ke callback requestAnimationFrame agar berjalan setelah pemroses peristiwa menampilkan:

Lihat kode lengkap: Presentation_delay.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
  requestAnimationFrame(() => {
    blockFor(1000);
  });
});

Apa yang terjadi?

11. Hasil eksperimen penundaan presentasi

Lihat kode lengkap: Presentation_delay.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
  requestAnimationFrame(() => {
    blockFor(1000);
  });
});

Interaksi tetap berdurasi satu detik, lalu apa yang terjadi?

requestAnimationFrame meminta callback sebelum penggambaran berikutnya. Karena INP mengukur waktu dari interaksi ke paint berikutnya, blockFor(1000) di requestAnimationFrame terus memblokir paint berikutnya selama satu detik penuh.

Interaksi diam satu detik di panel Performa

Namun, perhatikan dua hal:

  • Jika kursor diarahkan ke atasnya, Anda akan melihat semua waktu interaksi yang kini dihabiskan dalam "penundaan presentasi" karena pemblokiran thread utama terjadi setelah pemroses peristiwa kembali.
  • Root aktivitas thread utama bukan lagi peristiwa klik, tetapi "Animation Frame Fired".

12. Mendiagnosis interaksi

Di halaman pengujian ini, responsivitasnya sangat visual, dengan skor, timer, dan UI penghitung...tetapi saat menguji halaman rata-rata, hal ini tidak terlalu terlihat.

Ketika interaksi berjalan lama, apa penyebabnya tidak selalu jelas. Apakah:

  • Penundaan input?
  • Durasi pemrosesan peristiwa?
  • Penundaan presentasi?

Di halaman mana pun yang diinginkan, Anda dapat menggunakan DevTools untuk membantu mengukur responsivitas. Untuk membiasakan diri, coba alur ini:

  1. Jelajahi web, seperti biasa.
  2. Opsional: biarkan konsol DevTools terbuka saat ekstensi Web Vitals mencatat interaksi.
  3. Jika Anda melihat interaksi yang berperforma buruk, coba ulangi:
  • Jika Anda tidak dapat mengulanginya, gunakan log konsol untuk mendapatkan insight.
  • Jika Anda dapat mengulanginya, rekam di panel performa.

Semua kemacetan

Coba tambahkan sedikit dari semua masalah ini ke halaman:

Lihat kode lengkap: all_the_things.html

setInterval(() => {
  blockFor(1000);
}, 3000);

button.addEventListener('click', () => {
  blockFor(1000);
  score.incrementAndUpdateUI();

  requestAnimationFrame(() => {
    blockFor(1000);
  });
});

Kemudian gunakan konsol dan panel performa untuk mendiagnosis masalah.

13. Eksperimen: pekerjaan asinkron

Karena Anda dapat memulai efek non-visual di dalam interaksi, seperti membuat permintaan jaringan, memulai timer, atau hanya memperbarui status global, apa yang terjadi saat akhirnya memperbarui halaman?

Selama gambar berikutnya setelah interaksi diizinkan untuk dirender, meskipun browser memutuskan bahwa browser tersebut tidak benar-benar memerlukan update rendering baru, Pengukuran interaksi akan berhenti.

Untuk mencobanya, terus update UI dari pemroses klik, tetapi jalankan tugas pemblokiran dari waktu tunggu.

Lihat kode lengkap: timeout_100.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();

  setTimeout(() => {
    blockFor(1000);
  }, 100);
});

Apa yang terjadi dengan kiriman teks?

14. Hasil eksperimen kerja asinkron

Lihat kode lengkap: timeout_100.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();

  setTimeout(() => {
    blockFor(1000);
  }, 100);
});

Interaksi 27 milidetik dengan tugas berdurasi satu detik yang sekarang terjadi kemudian dalam rekaman aktivitas

Interaksinya kini singkat karena thread utama tersedia segera setelah UI diupdate. Tugas pemblokiran yang lama masih berjalan, hanya berjalan beberapa saat setelah penggambaran, sehingga pengguna akan mendapatkan masukan UI secara langsung.

Pelajaran: jika Anda tidak dapat menghapusnya, setidaknya pindahkan saja!

Metode

Bisakah kita melakukan yang lebih baik daripada setTimeout tetap 100 milidetik? Kita mungkin tetap ingin kode berjalan secepat mungkin, jika tidak kita seharusnya menghapusnya.

Sasaran:

  • Interaksi akan berjalan incrementAndUpdateUI().
  • blockFor() akan berjalan sesegera mungkin, tetapi tidak memblokir cat berikutnya.
  • Ini menghasilkan perilaku yang dapat diprediksi tanpa "waktu tunggu ajaib".

Beberapa cara untuk melakukannya meliputi:

  • setTimeout(0)
  • Promise.then()
  • requestAnimationFrame
  • requestIdleCallback
  • scheduler.postTask()

"requestPostAnimationFrame"

Tidak seperti requestAnimationFrame sendiri (yang akan mencoba berjalan sebelum paint berikutnya dan biasanya masih akan membuat interaksi lambat), requestAnimationFrame + setTimeout membuat polyfill sederhana untuk requestPostAnimationFrame, menjalankan callback setelah paint berikutnya.

Lihat kode lengkap: raf+task.html

function afterNextPaint(callback) {
  requestAnimationFrame(() => {
    setTimeout(callback, 0);
  });
}

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();

  afterNextPaint(() => {
    blockFor(1000);
  });
});

Untuk ergonomi, Anda bahkan dapat menggabungkannya dalam promise:

Lihat kode lengkap: raf+task2.html

async function nextPaint() {
  return new Promise(resolve => afterNextPaint(resolve));
}

button.addEventListener('click', async () => {
  score.incrementAndUpdateUI();

  await nextPaint();
  blockFor(1000);
});

15. Beberapa interaksi (dan klik besar)

Memindahkan pekerjaan pemblokiran yang lama dapat membantu, tetapi tugas yang berjalan lama masih menghalangi halaman, sehingga memengaruhi interaksi mendatang serta banyak animasi dan pembaruan halaman lainnya.

Coba kembali versi kerja pemblokiran asinkron dari halaman tersebut (atau buat versi Anda sendiri jika Anda membuat variasi sendiri tentang menunda pekerjaan di langkah terakhir):

Lihat kode lengkap: timeout_100.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();

  setTimeout(() => {
    blockFor(1000);
  }, 100);
});

Apa yang terjadi jika Anda mengklik beberapa kali dengan cepat?

Trace performa

Untuk setiap klik, ada tugas berdurasi satu detik yang mengantre, untuk memastikan thread utama diblokir untuk waktu yang lama.

Beberapa tugas berdurasi detik di thread utama, menyebabkan interaksi lambat 800 md

Jika tugas yang berjalan lama tersebut tumpang-tindih dengan klik baru yang masuk, hal ini akan mengakibatkan interaksi lambat meskipun pemroses peristiwa itu sendiri hampir seketika kembali. Kami telah membuat situasi yang sama seperti pada eksperimen sebelumnya dengan penundaan input. Kali ini, penundaan input tidak berasal dari setInterval, tetapi dari pekerjaan yang dipicu oleh pemroses peristiwa sebelumnya.

Strategi

Idealnya, kita ingin menghapus tugas yang panjang sepenuhnya.

  • Menghapus semua kode yang tidak diperlukan—terutama skrip.
  • Optimalkan kode agar tugas tidak berjalan lama.
  • Membatalkan pekerjaan lama saat interaksi baru tiba.

16. Strategi 1: debounce

Strategi klasik. Setiap kali interaksi tiba dalam urutan yang cepat, dan efek pemrosesan atau jaringannya mahal, tunda memulai pekerjaan dengan sengaja agar Anda dapat membatalkan dan memulai ulang. Pola ini berguna untuk antarmuka pengguna seperti kolom pelengkapan otomatis.

  • Gunakan setTimeout untuk menunda dimulainya pekerjaan yang mahal, dengan timer, mungkin 500 hingga 1000 milidetik.
  • Simpan ID timer jika Anda melakukannya.
  • Jika interaksi baru masuk, batalkan timer sebelumnya menggunakan clearTimeout.

Lihat kode lengkap: debounce.html

let timer;
button.addEventListener('click', () => {
  score.incrementAndUpdateUI();

  if (timer) {
    clearTimeout(timer);
  }
  timer = setTimeout(() => {
    blockFor(1000);
  }, 1000);
});

Trace performa

Banyak interaksi, tetapi hanya satu tugas pekerjaan yang panjang sebagai hasil dari semuanya

Meskipun beberapa kali diklik, hanya satu tugas blockFor yang akhirnya berjalan, menunggu hingga tidak ada klik selama satu detik penuh sebelum berjalan. Untuk interaksi yang terjadi secara beruntun—seperti mengetik input teks atau target item yang diharapkan mendapatkan beberapa klik cepat—ini adalah strategi yang ideal untuk digunakan secara default.

17. Strategi 2: menginterupsi pekerjaan yang berjalan lama

Masih ada kemungkinan tidak beruntung bahwa klik lebih lanjut akan masuk tepat setelah periode sekali tekan telah berlalu, akan mendarat di tengah-tengah tugas yang panjang, dan menjadi interaksi yang sangat lambat karena penundaan input.

Idealnya, jika sebuah interaksi datang di tengah-tengah tugas, kita ingin menjeda pekerjaan yang menumpuk sehingga semua interaksi baru dapat segera ditangani. Bagaimana kami dapat melakukannya?

Ada beberapa API seperti isInputPending, tetapi umumnya lebih baik bagi tugas yang berjalan lama menjadi beberapa bagian.

Banyak setTimeout

Upaya pertama: lakukan sesuatu yang sederhana.

Lihat kode lengkap: small_tasks.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();

  requestAnimationFrame(() => {
    setTimeout(() => blockFor(100), 0);
    setTimeout(() => blockFor(100), 0);
    setTimeout(() => blockFor(100), 0);
    setTimeout(() => blockFor(100), 0);
    setTimeout(() => blockFor(100), 0);
    setTimeout(() => blockFor(100), 0);
    setTimeout(() => blockFor(100), 0);
    setTimeout(() => blockFor(100), 0);
    setTimeout(() => blockFor(100), 0);
    setTimeout(() => blockFor(100), 0);
  });
});

Cara ini berfungsi dengan memungkinkan browser menjadwalkan setiap tugas satu per satu, dan input dapat mengambil prioritas yang lebih tinggi.

Beberapa interaksi, tetapi semua pekerjaan yang dijadwalkan telah dipecah menjadi banyak tugas yang lebih kecil

Kami kembali ke lima detik penuh bekerja untuk lima klik, tetapi setiap tugas satu detik per klik telah dipecah menjadi sepuluh tugas 100 milidetik. Hasilnya—bahkan dengan beberapa interaksi yang tumpang tindih dengan tugas-tugas tersebut—tidak ada interaksi yang memiliki penundaan input lebih dari 100 milidetik. Browser memprioritaskan pemroses peristiwa yang masuk daripada pekerjaan setTimeout, dan interaksi tetap responsif.

Strategi ini berfungsi dengan sangat baik saat menjadwalkan titik masuk yang terpisah—seperti jika Anda memiliki banyak fitur independen yang perlu dipanggil pada waktu pemuatan aplikasi. Hanya memuat skrip dan menjalankan semuanya pada waktu evaluasi skrip dapat menjalankan semuanya dalam tugas panjang yang sangat besar secara default.

Namun, strategi ini tidak berfungsi dengan baik untuk memisahkan kode yang dikaitkan secara erat, seperti loop for yang menggunakan status bersama.

Sekarang dengan yield()

Namun, kita dapat memanfaatkan async dan await modern untuk menambahkan "titik hasil" dengan mudah fungsi JavaScript.

Contoh:

Lihat kode lengkap: generatey.html

// Polyfill for scheduler.yield()
async function schedulerDotYield() {
  return new Promise(resolve => {
    setTimeout(resolve, 0);
  });
}

async function blockInPiecesYieldy(ms) {
  const ms_per_part = 10;
  const parts = ms / ms_per_part;
  for (let i = 0; i < parts; i++) {
    await schedulerDotYield();

    blockFor(ms_per_part);
  }
}

button.addEventListener('click', async () => {
  score.incrementAndUpdateUI();
  await blockInPiecesYieldy(1000);
});

Seperti sebelumnya, thread utama dihasilkan setelah sejumlah tugas dan browser dapat merespons setiap interaksi yang masuk, tetapi sekarang yang diperlukan hanyalah await schedulerDotYield(), bukan setTimeout terpisah, sehingga cukup ergonomis untuk digunakan bahkan di tengah loop for.

Sekarang dengan AbortContoller()

Cara itu berhasil, tetapi setiap interaksi menjadwalkan lebih banyak pekerjaan, bahkan jika interaksi baru telah masuk dan mungkin telah mengubah pekerjaan yang harus dilakukan.

Dengan strategi penguraian, kami membatalkan waktu tunggu sebelumnya dengan setiap interaksi baru. Bisakah kita melakukan hal serupa di sini? Salah satu cara untuk melakukannya adalah dengan menggunakan AbortController():

Lihat kode lengkap: aborty.html

// Polyfill for scheduler.yield()
async function schedulerDotYield() {
  return new Promise(resolve => {
    setTimeout(resolve, 0);
  });
}

async function blockInPiecesYieldyAborty(ms, signal) {
  const parts = ms / 10;
  for (let i = 0; i < parts; i++) {
    // If AbortController has been asked to stop, abandon the current loop.
    if (signal.aborted) return;

    await schedulerDotYield();

    blockFor(10);
  }
}

let abortController = new AbortController();

button.addEventListener('click', async () => {
  score.incrementAndUpdateUI();

  abortController.abort();
  abortController = new AbortController();

  await blockInPiecesYieldyAborty(1000, abortController.signal);
});

Saat klik masuk, loop blockInPiecesYieldyAborty for akan melakukan tugas apa pun yang perlu dilakukan, sekaligus menghasilkan thread utama secara berkala sehingga browser tetap responsif terhadap interaksi baru.

Saat klik kedua masuk, loop pertama ditandai sebagai dibatalkan dengan AbortController dan loop blockInPiecesYieldyAborty baru dimulai—saat berikutnya loop pertama dijadwalkan untuk berjalan lagi, loop pertama mendapati bahwa signal.aborted sekarang true dan langsung kembali tanpa melakukan pekerjaan lebih lanjut.

Pekerjaan thread utama sekarang terdiri dari banyak bagian kecil, interaksinya singkat, dan pekerjaan hanya berlangsung selama diperlukan

18. Kesimpulan

Memisahkan semua tugas yang berjalan lama memungkinkan situs menjadi responsif terhadap interaksi baru. Hal itu memungkinkan Anda memberikan masukan awal dengan cepat, dan juga memungkinkan Anda membuat keputusan seperti membatalkan pekerjaan yang sedang berlangsung. Terkadang itu berarti menjadwalkan titik entri sebagai tugas terpisah. Terkadang hal itu berarti menambahkan "hasil" jika memungkinkan.

Ingat

  • INP mengukur semua interaksi.
  • Setiap interaksi diukur dari input hingga penggambaran berikutnya—cara pengguna melihat responsivitas.
  • Penundaan input, durasi pemrosesan peristiwa, dan penundaan presentasi semua memengaruhi responsivitas interaksi.
  • Anda bisa mengukur INP dan perincian interaksi dengan DevTools dengan mudah.

Strategi

  • Jangan gunakan kode yang berjalan lama (tugas panjang) di halaman Anda.
  • Memindahkan kode yang tidak perlu dari pemroses peristiwa hingga penggambaran berikutnya.
  • Pastikan update rendering itu sendiri efisien untuk browser.

Pelajari lebih lanjut