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 membuat input saat tugas pemblokiran berjalan. Input ditunda hingga tugas tersebut selesai, setelah itu pemroses peristiwa pointerup, mouseup, dan peristiwa klik dijalankan, lalu pekerjaan rendering dan proses menggambar dimulai hingga bingkai berikutnya ditampilkan

Prasyarat

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

Yang Anda pelajari

  • Cara 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 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 telah 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 Score dan tombol Increment. 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 berjalan untuk membantu memvisualisasikan jank.

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

Cobalah

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

INP mengukur 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 menu Alat Lainnya > Alat Developer, dengan mengklik kanan halaman dan memilih Periksa, atau dengan menggunakan pintasan keyboard.

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

Screenshot panel Performa DevTools di samping aplikasi

Selanjutnya, rekam interaksi di panel Performance.

  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 perekaman interaksi menggunakan panel performa DevTools

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

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

Dengan mengarahkan kursor ke interaksi, Anda dapat melihat bahwa interaksi tersebut cepat, tidak menghabiskan waktu dalam durasi pemrosesan, dan jumlah waktu minimum dalam penundaan input dan penundaan presentasi, yang durasi pastinya 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 dengan halaman lagi. Interaksi kini akan terasa lebih lambat.

Pelacakan performa

Lakukan perekaman lain di panel Performance untuk melihat tampilannya di sana.

Interaksi berdurasi satu detik di panel Performa

Interaksi yang tadinya singkat kini memerlukan waktu satu detik penuh.

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

5. Eksperimen: durasi pemrosesan

Coba berbagai cara untuk mengatur ulang pekerjaan pemroses peristiwa guna melihat efeknya pada INP.

Update UI terlebih dahulu

Apa yang terjadi jika Anda menukar urutan panggilan JS—memperbarui UI terlebih dahulu, lalu memblokir?

Lihat kode lengkap: ui_first.html

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

Apakah Anda melihat UI muncul lebih awal? Apakah urutan memengaruhi skor INP?

Coba ambil rekaman aktivitas dan periksa interaksinya 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 peristiwa

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 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. Hasil eksperimen durasi pemrosesan

Performance trace: update UI first

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 pemblokir, browser tidak benar-benar mengupdate apa yang digambar ke layar hingga pemroses peristiwa selesai, yang berarti interaksi masih memerlukan waktu lebih dari satu detik untuk selesai.

Interaksi diam selama satu detik di panel Performa

Performance trace: separate listeners

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 memerlukan waktu 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 lambat yang sama bagi pengguna akhir.

Tampilan yang diperbesar pada interaksi berdurasi satu detik dalam contoh ini, yang menunjukkan panggilan fungsi pertama 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 berlangsung selama satu detik penuh; satu-satunya perbedaan adalah pemroses click yang hanya mengupdate UI dan lebih singkat 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 selesai, 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 tetap diperbarui.
  • Animasi, efek CSS, tindakan komponen web default (input formulir), entri teks, penandaan teks, semuanya terus diperbarui.

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

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

Melakukan perekaman panel Performa akan menampilkan interaksi yang hampir sama dengan interaksi sebelumnya.

Interaksi diam selama satu detik di panel Performa

Kesimpulan

Setiap kode yang berjalan di setiap pemroses peristiwa akan menunda interaksi.

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

Ini adalah masalah umum.

Terakhir: hanya karena kode Anda tidak memicu render, bukan berarti render 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 dan memblokir halaman secara acak 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_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 kebetulan 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 berdurasi lama.

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

Panel Performa DevTools 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 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 berdurasi lama (atau Frame Animasi Panjang), 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 rendering next paint?

Yaitu, memperbarui halaman dengan efek yang mahal.

Meskipun 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 mengaktifkan/menonaktifkan banyak pemilih kueri CSS yang berat dapat memicu banyak Gaya, Tata Letak, dan Paint.

Di luar thread utama:

  • Menggunakan CSS untuk mendukung 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 biasa ditemukan di web:

  • Situs SPA yang membangun ulang seluruh DOM setelah mengklik link, tanpa jeda untuk memberikan respons 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 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 render berikutnya. Karena INP mengukur waktu dari interaksi hingga render berikutnya, blockFor(1000) di requestAnimationFrame terus memblokir render berikutnya selama satu detik penuh.

Interaksi diam selama satu detik di panel Performa

Namun, perhatikan dua hal:

  • Saat mengarahkan kursor, Anda akan melihat 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, melainkan "Animation Frame Fired".

12. Mendiagnosis interaksi

Di halaman pengujian ini, responsivitas sangat visual, dengan skor, timer, dan 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 ini:

  1. Jelajahi web, seperti biasa.
  2. Pantau log Interaksi di tampilan metrik live pada panel Performa DevTools.
  3. Jika Anda melihat interaksi berperforma buruk, coba ulangi:
  • Jika Anda tidak dapat mengulanginya, gunakan log Interaksi untuk mendapatkan insight.
  • Jika Anda dapat mengulanginya, rekam rekaman 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 masalahnya.

13. Eksperimen: pekerjaan asinkron

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

Selama next paint setelah interaksi diizinkan untuk dirender, meskipun browser memutuskan bahwa update rendering baru tidak benar-benar diperlukan, 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 akan terjadi sekarang?

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 berdurasi lama satu detik kini terjadi lebih lambat dalam rekaman aktivitas

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

Pelajaran: jika Anda tidak dapat menghapusnya, setidaknya pindahkan!

Metode

Dapatkah kita melakukan yang lebih baik daripada setTimeout 100 milidetik yang 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 render 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 render berikutnya dan biasanya masih akan membuat interaksi lambat), requestAnimationFrame + setTimeout membuat polyfill sederhana untuk requestPostAnimationFrame, yang menjalankan callback setelah render 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 berulang)

Memindahkan tugas pemblokiran yang panjang dapat membantu, tetapi tugas yang panjang tersebut masih memblokir halaman, sehingga memengaruhi interaksi di masa mendatang serta banyak animasi dan pembaruan halaman lainnya.

Coba lagi versi halaman yang melakukan tugas pemblokiran asinkron (atau versi Anda sendiri jika Anda membuat variasi sendiri dalam menunda tugas pada 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?

Pelacakan performa

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

Beberapa tugas berdurasi beberapa detik di thread utama, yang menyebabkan interaksi selambat 800 md

Jika tugas berdurasi lama tersebut tumpang-tindih dengan klik baru yang masuk, interaksi akan menjadi lambat meskipun pemroses peristiwa itu sendiri ditampilkan hampir seketika. Kami telah membuat situasi yang sama seperti dalam 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 berdurasi lama sepenuhnya.

  • Hapus kode yang tidak diperlukan sama sekali—terutama skrip.
  • Optimalkan kode untuk menghindari menjalankan tugas berdurasi lama.
  • Batalkan pekerjaan yang tidak berlaku saat interaksi baru tiba.

16. Strategi 1: debounce

Strategi klasik. Setiap kali interaksi terjadi secara berurutan, 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 berat, dengan timer, mungkin 500 hingga 1.000 milidetik.
  • Simpan ID timer saat 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);
});

Pelacakan performa

Beberapa interaksi, tetapi hanya satu tugas berdurasi lama sebagai hasil dari semuanya

Meskipun ada beberapa klik, 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 dalam 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 klik berikutnya akan masuk tepat setelah periode debounce berlalu, akan mendarat di tengah tugas berdurasi lama tersebut, dan menjadi interaksi yang sangat lambat karena penundaan input.

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

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

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

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

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

Strategi ini sangat efektif saat menjadwalkan titik entri terpisah—misalnya, 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 berdurasi lama yang sangat besar secara default.

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

Kini dengan yield()

Namun, kita dapat memanfaatkan async dan await modern untuk menambahkan "poin 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 dihasilkan setelah sebagian tugas 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.

Kini dengan AbortContoller()

Cara ini berhasil, tetapi setiap interaksi menjadwalkan lebih banyak pekerjaan, meskipun interaksi baru telah masuk dan mungkin telah mengubah pekerjaan yang perlu dilakukan.

Dengan strategi debounce, kita 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 dimulai dan melakukan pekerjaan apa pun yang perlu dilakukan sambil secara berkala menghasilkan thread utama sehingga 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 ditampilkan tanpa melakukan pekerjaan lebih lanjut.

Pekerjaan thread utama kini dibagi menjadi banyak bagian kecil, interaksi menjadi singkat, dan pekerjaan hanya berlangsung selama diperlukan

18. Kesimpulan

Memecah semua tugas berdurasi lama memungkinkan situs menjadi responsif terhadap interaksi baru. Dengan demikian, Anda dapat memberikan masukan awal dengan cepat, dan juga dapat membuat keputusan seperti membatalkan pekerjaan yang sedang berlangsung. Terkadang, hal ini berarti menjadwalkan titik entri sebagai tugas terpisah. Terkadang, hal ini berarti menambahkan titik "yield" jika memungkinkan.

Ingat

  • INP mengukur semua interaksi.
  • Setiap interaksi diukur dari input hingga render 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 dengan mudah.

Strategi

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

Pelajari lebih lanjut