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 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
.
- Clone repo di terminal Anda:
git clone https://github.com/GoogleChromeLabs/web-vitals-codelabs.git
- Jelajahi direktori yang di-clone:
cd web-vitals-codelabs/understanding-inp
- Menginstal dependensi:
npm ci
- Mulai server web:
npm run start
- 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!
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.
Selanjutnya, rekam interaksi di panel Performa.
- Tekan rekam.
- Lakukan interaksi dengan halaman (tekan tombol Increment).
- Hentikan perekaman.
Di linimasa yang dihasilkan, Anda akan menemukan jalur Interaksi. Perluas dengan mengklik segitiga di sisi kiri.
Dua interaksi muncul. Perbesar tampilan kedua dengan men-scroll atau menahan tombol W.
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.
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 mengaktifkan 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.
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.
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.
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.
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.
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 diperbaiki ketika biasanya 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
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.
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:
- Jelajahi web, seperti biasa.
- Opsional: biarkan konsol DevTools terbuka saat ekstensi Web Vitals mencatat interaksi.
- 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);
});
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.
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
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.
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.
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.