Memahami Interaction to Next Paint (INP)

1. Pengantar

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

Diagram yang menggambarkan interaksi di thread utama. Pengguna memasukkan input saat tugas pemblokiran berjalan. Input ditunda hingga tugas tersebut selesai, setelah itu pemroses peristiwa pointerup, mouseup, dan klik berjalan, lalu rendering dan pengecatan dimulai hingga frame berikutnya ditampilkan

Prasyarat

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

Yang Anda pelajari

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

Yang Anda perlukan

  • Komputer yang dapat 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 dapat ditemukan di repositori web-vitals-codelabs.

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

Ringkasan aplikasi

Di bagian atas halaman, terdapat penghitung Skor dan tombol Tambah. Demo klasik reaktivitas dan responsivitas.

Screenshot aplikasi demo untuk codelab ini

Di bawah tombol, ada empat pengukuran:

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

Entri FPS dan Timer sama sekali tidak diperlukan untuk mengukur interaksi. Ditambahkan hanya untuk mempermudah visualisasi responsivitas.

Cobalah

Coba berinteraksi dengan tombol Increment dan lihat skornya bertambah. Apakah nilai INP dan Interaksi berubah dengan setiap penambahan?

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

3. Mengukur interaksi dengan Chrome DevTools

Buka DevTools dari menu Alat Lainnya > Alat Developer, 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. Berinteraksi dengan halaman (tekan tombol Increment).
  3. Hentikan perekaman.

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

Demonstrasi animasi tentang merekam interaksi menggunakan panel performa DevTools

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

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

Dengan mengarahkan kursor ke interaksi, Anda dapat melihat bahwa interaksi tersebut berlangsung cepat, tidak menghabiskan waktu dalam durasi pemrosesan, dan menghabiskan waktu minimum dalam penundaan input dan penundaan presentasi, yang durasi pastinya akan bergantung pada kecepatan komputer Anda.

4. Pemroses peristiwa yang berjalan lama

Buka file index.js, dan hapus komentar 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 dengan halaman lagi. Interaksi kini akan terasa lebih lambat.

Rekaman aktivitas performa

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

Interaksi selama satu detik di panel Performa

Interaksi yang dulunya singkat kini memerlukan waktu satu detik penuh.

Saat Anda mengarahkan kursor ke interaksi, perhatikan bahwa waktu hampir seluruhnya dihabiskan dalam "Durasi pemrosesan", yang merupakan jumlah waktu yang diperlukan untuk mengeksekusi callback pendengar peristiwa. Karena panggilan blockFor yang memblokir sepenuhnya berada dalam pemroses peristiwa, di situlah waktu berjalan.

5. Eksperimen: durasi pemrosesan

Coba berbagai cara untuk mengatur ulang tugas pemroses peristiwa guna melihat pengaruhnya terhadap INP.

Perbarui UI terlebih dahulu

Apa yang terjadi jika Anda menukar urutan panggilan js—perbarui 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 memengaruhi skor INP?

Coba ambil rekaman aktivitas dan periksa interaksi untuk melihat apakah ada perbedaan.

Memisahkan pemroses

Bagaimana jika Anda memindahkan pekerjaan ke pemroses peristiwa terpisah? Perbarui UI di satu pemroses peristiwa, dan blokir halaman dari pemroses terpisah.

Lihat kode lengkap: two_click.html

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

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

Seperti apa tampilannya di panel performa sekarang?

Berbagai jenis acara

Sebagian besar interaksi akan memicu banyak jenis peristiwa, mulai dari peristiwa pointer atau tombol, hingga peristiwa pengarahan kursor, fokus/blur, dan peristiwa sintetis seperti beforechange dan beforeinput.

Banyak halaman nyata memiliki pemroses untuk berbagai peristiwa.

Apa yang terjadi jika Anda mengubah jenis peristiwa untuk pemroses peristiwa? Misalnya, mengganti 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 memperbarui UI dari pemroses peristiwa?

Lihat kode lengkap: no_ui.html

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

6. Hasil eksperimen durasi pemrosesan

Rekaman aktivitas performa: perbarui UI terlebih dahulu

Lihat kode lengkap: ui_first.html

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

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

Interaksi satu detik di panel Performa

Trace performa: pemroses terpisah

Lihat kode lengkap: two_click.html

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

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

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

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

Seperti yang diharapkan, yang pertama—mengupdate UI—berjalan sangat cepat, sedangkan yang kedua memerlukan waktu satu detik penuh. Namun, jumlah efeknya menghasilkan interaksi yang sama lambatnya bagi pengguna akhir.

Tampilan yang diperbesar dari interaksi selama satu detik dalam contoh ini, yang menunjukkan 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. Interaksinya masih satu detik penuh; satu-satunya perbedaan adalah bahwa pemroses click khusus update UI yang lebih pendek kini berjalan setelah pemroses pointerup yang memblokir.

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

Rekaman aktivitas performa: tidak ada update UI

Lihat kode lengkap: no_ui.html

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

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

Karena pemroses peristiwa memblokir thread utama selama satu detik sehingga halaman tidak dapat dirender, interaksi masih memerlukan waktu satu detik penuh.

Merekam panel Performa menunjukkan interaksi yang hampir identik dengan interaksi sebelumnya.

Interaksi satu detik di panel Performa

Kesimpulan

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

  • Hal ini mencakup pemroses yang terdaftar dari skrip dan kode framework atau library yang berbeda yang berjalan di pemroses, seperti update status yang memicu rendering komponen.
  • Tidak hanya kode Anda sendiri, tetapi juga semua skrip pihak ketiga.

Ini adalah masalah umum.

Terakhir: hanya karena kode Anda tidak memicu pengecatan tidak berarti pengecatan tidak akan menunggu penyelesaian pemroses peristiwa yang lambat.

7. Experiment: input delay

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

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

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

Lihat kode lengkap: input_delay.html

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


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

Apa yang terjadi?

8. Hasil eksperimen penundaan input

Lihat kode lengkap: input_delay.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 pekerjaan pemblokiran yang dilakukan dalam interaksi itu sendiri.

Periode yang berjalan lama ini sering disebut tugas panjang.

Dengan mengarahkan kursor ke interaksi di DevTools, Anda akan dapat melihat bahwa waktu interaksi kini terutama disebabkan oleh jeda input, bukan durasi pemrosesan.

Panel Performa DevTools yang menampilkan tugas pemblokiran satu detik, interaksi yang masuk di tengah tugas tersebut, dan interaksi 642 milidetik, yang sebagian besar disebabkan oleh penundaan input

Perhatikan, hal ini tidak selalu memengaruhi interaksi. Jika Anda tidak mengklik saat tugas sedang berjalan, Anda mungkin beruntung. Bersin "acak" seperti itu bisa menjadi mimpi buruk untuk di-debug jika hanya terkadang menyebabkan masalah.

Salah satu cara untuk melacaknya adalah dengan mengukur tugas panjang (atau Long Animation Frames), dan Total Blocking Time.

9. Presentasi lambat

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

Nah, memperbarui halaman dengan efek yang mahal.

Meskipun pembaruan halaman datang 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 Style, Layout, dan Paint.

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 ulang seluruh DOM setelah mengklik link, tanpa jeda untuk memberikan masukan visual awal.
  • Halaman penelusuran yang menawarkan filter penelusuran kompleks dengan antarmuka pengguna dinamis, tetapi menjalankan pendengar yang mahal untuk melakukannya.
  • Tombol mode gelap yang memicu gaya/tata letak untuk seluruh halaman

10. Experiment: presentation delay

requestAnimationFrame lambat

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

Pindahkan panggilan blockFor ke callback requestAnimationFrame sehingga berjalan setelah pemroses peristiwa ditampilkan:

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 berlangsung selama satu detik, jadi apa yang terjadi?

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

Interaksi satu detik di panel Performa

Namun, perhatikan dua hal:

  • Saat mengarahkan kursor, Anda akan melihat bahwa semua waktu interaksi kini dihabiskan dalam "penundaan presentasi" karena pemblokiran thread utama terjadi setelah pemroses peristiwa ditampilkan.
  • Root aktivitas thread utama bukan lagi peristiwa klik, tetapi "Frame Animasi Diaktifkan".

12. Mendiagnosis interaksi

Di halaman pengujian ini, responsivitas sangat terlihat, dengan skor dan timer serta UI penghitung...tetapi saat menguji halaman rata-rata, responsivitasnya lebih halus.

Jika interaksi berjalan lama, penyebabnya tidak selalu jelas. Apakah:

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

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

  1. Jelajahi web seperti biasa.
  2. Perhatikan log Interaksi di tampilan metrik langsung pada panel Performa DevTools.
  3. Jika Anda melihat interaksi yang berperforma buruk, coba ulangi:
  • Jika Anda tidak dapat mengulanginya, gunakan Log interaksi untuk mendapatkan insight.
  • Jika Anda dapat mengulanginya, rekam aktivitas di panel Performa.

Semua penundaan

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 efek tersebut akhirnya memperbarui halaman?

Selama paint berikutnya setelah interaksi diizinkan untuk dirender, meskipun browser memutuskan bahwa sebenarnya tidak memerlukan update rendering baru, pengukuran Interaksi akan berhenti.

Untuk mencobanya, terus perbarui UI dari pemroses klik, tetapi jalankan pekerjaan 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 pekerjaan asinkron

Lihat kode lengkap: timeout_100.html

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

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

Interaksi 27 milidetik dengan tugas yang berlangsung selama satu detik kini terjadi lebih lambat di rekaman aktivitas

Interaksi kini singkat karena thread utama tersedia segera setelah UI diperbarui. Tugas pemblokiran yang panjang masih berjalan, hanya saja berjalan beberapa saat setelah paint, sehingga pengguna akan mendapatkan masukan UI langsung.

Pelajaran: jika Anda tidak dapat menghapusnya, setidaknya pindahkan!

Metode

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

Sasaran:

  • Interaksi akan berjalan incrementAndUpdateUI().
  • blockFor() akan berjalan sesegera mungkin, tetapi tidak memblokir gambar berikutnya.
  • Hal 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 saja (yang akan mencoba berjalan sebelum gambar berikutnya dan biasanya masih membuat interaksi menjadi lambat), requestAnimationFrame + setTimeout membuat polyfill sederhana untuk requestPostAnimationFrame, yang menjalankan callback setelah gambar 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 membungkusnya 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 berlebihan)

Memindahkan pekerjaan pemblokiran yang panjang dapat membantu, tetapi tugas yang panjang tersebut tetap memblokir halaman, sehingga memengaruhi interaksi mendatang serta banyak animasi dan update halaman lainnya.

Coba lagi versi halaman yang memblokir pekerjaan asinkron (atau versi Anda sendiri jika Anda membuat variasi sendiri dalam 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?

Rekaman aktivitas performa

Untuk setiap klik, ada tugas berdurasi satu detik yang diantrekan, sehingga thread utama diblokir dalam waktu yang cukup lama.

Beberapa tugas yang berjalan selama beberapa detik di thread utama, menyebabkan interaksi menjadi lambat hingga 800 md

Jika tugas yang panjang tersebut tumpang-tindih dengan klik baru yang masuk, interaksi akan menjadi lambat meskipun pemroses peristiwa itu sendiri langsung merespons. Kita telah menciptakan situasi yang sama seperti pada eksperimen sebelumnya dengan penundaan input. Hanya saja, kali ini penundaan input tidak berasal dari setInterval, tetapi dari pekerjaan yang dipicu oleh pemroses peristiwa sebelumnya.

Strategi

Idealnya, kita ingin menghapus tugas panjang sepenuhnya.

  • Hapus semua kode yang tidak diperlukan, terutama skrip.
  • Optimalkan kode untuk menghindari menjalankan tugas yang panjang.
  • Batalkan pekerjaan yang tidak aktif saat interaksi baru tiba.

16. Strategi 1: debounce

Strategi klasik. Setiap kali interaksi datang secara berurutan dengan cepat, dan efek pemrosesan atau jaringan mahal, tunda memulai pekerjaan dengan sengaja sehingga Anda dapat membatalkan dan memulai ulang. Pola ini berguna untuk antarmuka pengguna seperti kolom pelengkapan otomatis.

  • Gunakan setTimeout untuk menunda memulai pekerjaan yang mahal, dengan timer, mungkin 500 hingga 1.000 milidetik.
  • Simpan ID timer saat Anda melakukannya.
  • Jika interaksi baru tiba, 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);
});

Rekaman aktivitas performa

Beberapa interaksi, tetapi hanya satu tugas panjang sebagai akibat dari semua interaksi tersebut

Meskipun diklik beberapa kali, 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 di input teks atau target item yang diperkirakan akan mendapatkan beberapa klik cepat—ini adalah strategi ideal untuk digunakan secara default.

17. Strategi 2: menghentikan pekerjaan yang berjalan lama

Masih ada kemungkinan kecil bahwa klik lain akan masuk tepat setelah periode penghilangan pentalan berlalu, akan mendarat di tengah tugas yang panjang tersebut, dan menjadi interaksi yang sangat lambat karena penundaan input.

Idealnya, jika interaksi terjadi di tengah tugas kita, kita ingin menjeda tugas yang sedang kita kerjakan sehingga interaksi baru dapat segera ditangani. Bagaimana cara melakukannya?

Ada beberapa API seperti isInputPending, tetapi biasanya lebih baik membagi tugas yang panjang 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);
  });
});

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

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

Kita kembali ke lima detik penuh 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 tersebut—tidak ada interaksi yang memiliki penundaan input lebih dari 100 milidetik. Browser memprioritaskan pemroses peristiwa masuk daripada pekerjaan setTimeout, dan interaksi tetap responsif.

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

Namun, strategi ini tidak berfungsi dengan baik untuk memisahkan kode yang terhubung 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 ke fungsi JavaScript apa pun.

Contoh:

Lihat kode lengkap: yieldy.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 akan di-yield setelah sebagian tugas selesai dan browser dapat merespons 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 tersebut berhasil, tetapi setiap interaksi menjadwalkan lebih banyak pekerjaan, meskipun interaksi baru telah masuk dan mungkin telah mengubah pekerjaan yang perlu dilakukan.

Dengan strategi penghilangan derau, 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 ada klik, loop blockInPiecesYieldyAborty for akan dimulai untuk melakukan pekerjaan apa pun yang perlu dilakukan sambil secara berkala melepaskan thread utama agar browser tetap responsif terhadap interaksi baru.

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

Tugas thread utama kini dibagi menjadi banyak bagian kecil, interaksi menjadi singkat, dan tugas hanya berlangsung selama yang diperlukan

18. Kesimpulan

Memecah semua tugas panjang memungkinkan situs merespons interaksi baru. Hal ini memungkinkan Anda memberikan masukan awal dengan cepat, dan juga memungkinkan Anda membuat keputusan seperti membatalkan pekerjaan yang sedang berlangsung. Terkadang, Anda perlu menjadwalkan titik entri sebagai tugas terpisah. Terkadang, itu berarti menambahkan titik "hasil" di tempat yang sesuai.

Ingat

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

Strategi

  • Jangan memiliki kode yang berjalan lama (tugas panjang) di halaman Anda.
  • Pindahkan kode yang tidak diperlukan dari pemroses peristiwa hingga setelah gambar berikutnya.
  • Pastikan update rendering itu sendiri efisien untuk browser.

Pelajari lebih lanjut