Chrome 88 (Januari 2021) akan sangat membatasi timer JavaScript berantai untuk halaman tersembunyi dalam kondisi tertentu. Tindakan ini akan mengurangi penggunaan CPU, yang juga akan mengurangi penggunaan baterai. Ada beberapa kasus ekstrem yang akan mengubah perilaku, tetapi timer sering digunakan jika API yang berbeda akan lebih efisien, dan lebih andal.
Oke, itu cukup banyak menggunakan jargon, dan sedikit ambigu. Mari kita lihat…
Terminologi
Halaman tersembunyi
Umumnya, tersembunyi berarti tab lain aktif, atau jendela telah diminimalkan, tetapi browser dapat menganggap halaman tersembunyi setiap kali kontennya tidak terlihat sama sekali. Beberapa browser lebih jauh dari yang lain di sini, tetapi Anda dapat selalu menggunakan page visibility API untuk melacak kapan browser menganggap visibilitas telah berubah.
Timer JavaScript
Yang dimaksud dengan timer JavaScript adalah setTimeout
dan setInterval
, yang memungkinkan Anda
menjadwalkan callback pada masa mendatang. Timer berguna, dan tidak akan
hilang, tetapi terkadang timer digunakan untuk melakukan polling status saat peristiwa akan lebih
efisien, dan lebih akurat.
Timer berantai
Jika Anda memanggil setTimeout
dalam tugas yang sama dengan callback setTimeout
, pemanggilan
kedua akan 'dikaitkan'. Dengan setInterval
, setiap iterasi adalah bagian dari
rantai. Hal ini mungkin lebih mudah dipahami dengan kode:
let chainCount = 0;
setInterval(() => {
chainCount++;
console.log(`This is number ${chainCount} in the chain`);
}, 500);
Dan:
let chainCount = 0;
function setTimeoutChain() {
setTimeout(() => {
chainCount++;
console.log(`This is number ${chainCount} in the chain`);
setTimeoutChain();
}, 500);
}
Cara kerja throttling
Pembatasan terjadi secara bertahap:
Throttling minimal
Hal ini terjadi pada timer yang dijadwalkan jika salah satu kondisi berikut terpenuhi:
- Halaman terlihat.
- Halaman telah mengeluarkan suara dalam 30 detik terakhir. Ini dapat berasal dari API pembuat suara, tetapi trek audio tanpa suara tidak dihitung.
Timer tidak dibatasi, kecuali jika waktu tunggu yang diminta kurang dari 4 md, dan jumlah rantai adalah 5 atau lebih, dalam hal ini waktu tunggu ditetapkan ke 4 md. Hal ini bukanlah hal baru; browser telah melakukannya selama bertahun-tahun.
Throttling
Hal ini terjadi pada timer yang dijadwalkan saat throttle minimum tidak berlaku, dan salah satu dari hal berikut berlaku:
- Jumlah rantai kurang dari 5.
- Halaman telah disembunyikan selama kurang dari 5 menit.
- WebRTC sedang digunakan. Secara khusus, ada
RTCPeerConnection
denganRTCDataChannel
'terbuka' atauMediaStreamTrack
'aktif'.
Browser akan memeriksa timer dalam grup ini sekali per detik. Karena hanya diperiksa sekali per detik, timer dengan waktu tunggu yang serupa akan dikelompokkan bersama, yang menggabungkan waktu yang diperlukan tab untuk menjalankan kode. Hal ini juga bukan hal baru; browser telah melakukannya sampai batas tertentu selama bertahun-tahun.
Throttling intensif
Oke, berikut adalah fitur baru di Chrome 88. Throttling intensif terjadi pada timer yang dijadwalkan saat tidak ada kondisi throttling minimal atau throttling yang berlaku, dan semua kondisi berikut terpenuhi:
- Halaman telah disembunyikan selama lebih dari 5 menit.
- Jumlah rantai adalah 5 atau lebih.
- Halaman tidak ada aktivitas selama minimal 30 detik.
- WebRTC tidak digunakan.
Dalam hal ini, browser akan memeriksa timer dalam grup ini sekali per menit. Serupa dengan sebelumnya, ini berarti timer akan dikelompokkan dalam pemeriksaan menit demi menit ini.
Solusi
Biasanya ada alternatif yang lebih baik untuk timer, atau timer dapat digabungkan dengan hal lain agar lebih baik bagi CPU dan masa pakai baterai.
Polling status
Ini adalah (penyalahgunaan) timer yang paling umum, yaitu timer digunakan untuk terus-menerus memeriksa atau mengambil sampel untuk melihat apakah ada yang berubah. Pada umumnya, ada push yang setara, yang memberi tahu Anda tentang perubahan saat terjadi, sehingga Anda tidak perlu terus-menerus memeriksa. Lihat apakah ada peristiwa yang mencapai hal yang sama.
Beberapa contohnya:
- Jika Anda perlu mengetahui kapan elemen masuk ke area pandang, gunakan
IntersectionObserver
. - Jika Anda perlu mengetahui kapan ukuran elemen berubah, gunakan
ResizeObserver
. - Jika Anda perlu mengetahui kapan DOM berubah, gunakan
MutationObserver
, atau mungkin callback siklus proses elemen kustom. - Daripada melakukan polling server, pertimbangkan soket web, peristiwa yang dikirim server, pesan push, atau streaming pengambilan.
- Jika Anda perlu bereaksi terhadap perubahan tahap dalam audio/video, gunakan peristiwa seperti
timeupdate
danended
, ataurequestVideoFrameCallback
jika Anda perlu melakukan sesuatu dengan setiap frame.
Ada juga pemicu notifikasi jika Anda ingin menampilkan notifikasi pada waktu tertentu.
Animasi
Animasi adalah hal visual, sehingga tidak boleh menggunakan waktu CPU saat halaman disembunyikan.
requestAnimationFrame
jauh lebih baik dalam menjadwalkan pekerjaan animasi daripada timer JavaScript. Fungsi ini
disinkronkan dengan kecepatan refresh perangkat, sehingga memastikan Anda hanya mendapatkan satu
callback per frame yang dapat ditampilkan, dan Anda mendapatkan jumlah waktu maksimum untuk
membuat frame tersebut. Selain itu, requestAnimationFrame
akan menunggu halaman terlihat, sehingga tidak menggunakan CPU apa pun saat halaman disembunyikan.
Jika Anda dapat mendeklarasikan seluruh animasi di awal, pertimbangkan untuk menggunakan animasi CSS atau
web animations API. Hal ini
memiliki keuntungan yang sama dengan requestAnimationFrame
, tetapi browser dapat melakukan
pengoptimalan tambahan seperti komposisi otomatis, dan umumnya
lebih mudah digunakan.
Jika animasi Anda memiliki kecepatan frame rendah (seperti kursor yang berkedip), timer masih
merupakan opsi terbaik saat ini, tetapi Anda dapat menggabungkannya dengan requestAnimationFrame
untuk mendapatkan yang terbaik dari keduanya:
function animationInterval(ms, signal, callback) {
const start = document.timeline.currentTime;
function frame(time) {
if (signal.aborted) return;
callback(time);
scheduleFrame(time);
}
function scheduleFrame(time) {
const elapsed = time - start;
const roundedElapsed = Math.round(elapsed / ms) * ms;
const targetNext = start + roundedElapsed + ms;
const delay = targetNext - performance.now();
setTimeout(() => requestAnimationFrame(frame), delay);
}
scheduleFrame(start);
}
Penggunaan:
const controller = new AbortController();
// Create an animation callback every second:
animationInterval(1000, controller.signal, time => {
console.log('tick!', time);
});
// And stop it:
controller.abort();
Pengujian
Perubahan ini akan diaktifkan untuk semua pengguna Chrome di Chrome 88 (Januari 2021). Fitur ini saat ini diaktifkan untuk 50% pengguna Chrome Beta, Dev, dan Canary. Jika Anda ingin mengujinya, gunakan flag command line ini saat meluncurkan Chrome Beta, Dev, atau Canary:
--enable-features="IntensiveWakeUpThrottling:grace_period_seconds/10,OptOutZeroTimeoutTimersFromThrottling,AllowAggressiveThrottlingWithWebSocket"
Argumen grace_period_seconds/10
menyebabkan throttling intens diterapkan
setelah 10 detik halaman disembunyikan, bukan 5 menit penuh,
sehingga mempermudah untuk melihat dampak throttling.
Acara mendatang
Karena timer adalah sumber penggunaan CPU yang berlebihan, kami akan terus mencari
cara untuk membatasinya tanpa merusak konten web, dan API yang dapat
ditambahkan/diubah untuk memenuhi kasus penggunaan. Secara pribadi, saya ingin menghilangkan kebutuhan
animationInterval
untuk mendukung callback animasi frekuensi rendah yang efisien. Jika
ada pertanyaan, hubungi kami di Twitter.
Foto header oleh Heather Zabriskie di Unsplash.