1. Pengantar
Demo interaktif dan codelab untuk mempelajari Interaction to Next Paint (INP).
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
.
- Clone repo di terminal Anda:
git clone https://github.com/GoogleChromeLabs/web-vitals-codelabs.git
- Buka direktori yang di-clone:
cd web-vitals-codelabs/understanding-inp
- Instal dependensi:
npm ci
- Mulai server web:
npm run start
- 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.
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.
Selanjutnya, rekam interaksi di panel Performa.
- Tekan rekam.
- Berinteraksi dengan halaman (tekan tombol Increment).
- Hentikan perekaman.
Di linimasa yang dihasilkan, Anda akan menemukan jalur Interaksi. Luaskan dengan mengklik segitiga di sisi kiri.
Dua interaksi akan muncul. Perbesar yang kedua dengan men-scroll atau menahan tombol W.
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 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.
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.
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.
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.
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.
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
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.
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:
- Jelajahi web seperti biasa.
- Perhatikan log Interaksi di tampilan metrik langsung pada panel Performa DevTools.
- 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 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.
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
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.
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.
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.