Throttling berat timer JS yang dirantai mulai Chrome 88

Jake Archibald
Jake Archibald

Chrome 88 (Januari 2021) akan membatasi timer JavaScript berantai secara besar-besaran untuk halaman tersembunyi dalam kondisi tertentu. Tindakan ini akan mengurangi penggunaan CPU, yang juga akan mengurangi penggunaan baterai. Ada beberapa kasus ekstrem di mana hal ini akan mengubah perilaku, tetapi timer sering digunakan ketika API yang berbeda akan lebih efisien, dan lebih andal.

Oke, itu jargon yang berat dan agak ambigu. Jadi ayo kita pelajari...

Terminologi

Halaman tersembunyi

Umumnya, tersembunyi berarti tab lain aktif, atau jendela telah diminimalkan, tetapi browser mungkin menganggap halaman disembunyikan setiap kali kontennya sama sekali tidak terlihat. Beberapa browser memiliki performa yang lebih tinggi dibandingkan yang lain di sini, tetapi Anda selalu dapat menggunakan page visibility API untuk melacak saat browser menganggap visibilitas telah berubah.

Timer JavaScript

Yang saya maksud dengan timer JavaScript adalah setTimeout dan setInterval, yang memungkinkan Anda untuk menjadwalkan callback kapan saja di masa mendatang. Timer berguna, dan tidak bergantian, tetapi terkadang digunakan untuk mengkueri status ketika suatu peristiwa akan lebih efisien, dan lebih akurat.

Timer berantai

Jika Anda memanggil setTimeout dalam tugas yang sama dengan callback setTimeout, pemanggilan kedua adalah 'dirantai'. 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

Throttling terjadi secara bertahap:

Throttling minimal

Hal ini terjadi pada timer yang dijadwalkan jika salah satu hal berikut berlaku:

  • Halaman terlihat.
  • Halaman berisi derau dalam 30 detik terakhir. Ini dapat berasal dari API pembuatan suara mana pun, tetapi trek audio senyap tidak termasuk.

Timer tidak di-throttle, kecuali jika waktu tunggu yang diminta kurang dari 4 md, dan jumlah rantai 5 atau lebih besar, dalam hal ini waktu tunggu akan disetel ke 4 md. Ini bukanlah hal baru; browser telah melakukan hal ini selama bertahun-tahun.

Throttling

Hal ini terjadi pada timer yang dijadwalkan saat throttling minimal tidak berlaku, dan salah satu hal berikut berlaku:

  • Jumlah rantai kurang dari 5.
  • Halaman telah disembunyikan selama kurang dari 5 menit.
  • WebRTC sedang digunakan. Secara khusus, ada RTCPeerConnection dengan RTCDataChannel 'terbuka' atau MediaStreamTrack 'live'.

Browser akan memeriksa timer di grup ini satu kali per detik. Karena hanya dicentang sekali per detik, timer dengan waktu tunggu yang serupa akan dikelompokkan menjadi satu, sehingga menggabungkan waktu yang diperlukan tab untuk menjalankan kode. Ini juga bukanlah hal baru; {i>browser<i} telah melakukan ini sampai batas tertentu selama bertahun-tahun.

Throttling intensif

Oke, ini dia bagian baru di Chrome 88. Throttling intensif terjadi pada timer yang dijadwalkan jika tidak ada kondisi throttling minimal atau throttling yang berlaku, dan semua kondisi berikut berlaku:

  • Halaman telah disembunyikan selama lebih dari 5 menit.
  • Jumlah rantai adalah 5 atau lebih.
  • Halaman ini senyap selama minimal 30 detik.
  • WebRTC tidak digunakan.

Dalam hal ini, browser akan memeriksa timer di grup ini satu kali per menit. Serupa dengan sebelumnya, ini berarti timer akan digabungkan dalam pemeriksaan menit demi menit ini.

Solusi

Biasanya ada alternatif yang lebih baik untuk timer, atau timer dapat digabungkan dengan sesuatu yang lain agar lebih ramah terhadap CPU dan masa pakai baterai.

Jajak pendapat status

Ini adalah yang paling umum (salah) menggunakan timer, yang digunakan untuk terus memeriksa atau melakukan polling guna melihat apakah ada sesuatu yang berubah. Dalam kebanyakan kasus, ada padanan push, yakni ketika hal tersebut memberi tahu Anda tentang perubahan saat terjadi perubahan, sehingga Anda tidak perlu terus memeriksanya. Lihat apakah ada peristiwa yang memberikan hasil yang sama.

Beberapa contoh:

Ada juga pemicu notifikasi jika Anda ingin menampilkan notifikasi pada waktu tertentu.

Animasi

Animasi adalah hal visual, jadi tidak boleh menggunakan waktu CPU saat halaman tersembunyi.

requestAnimationFrame jauh lebih baik dalam menjadwalkan pekerjaan animasi daripada timer JavaScript. Ini akan 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 halaman tidak menggunakan CPU apa pun saat halaman disembunyikan.

Jika Anda dapat mendeklarasikan seluruh animasi di awal, pertimbangkan untuk menggunakan animasi CSS atau API animasi web. Composable ini memiliki keuntungan yang sama dengan requestAnimationFrame, tetapi browser dapat melakukan pengoptimalan tambahan seperti pengomposisian otomatis, dan umumnya lebih mudah digunakan.

Jika animasi memiliki kecepatan frame rendah (seperti kursor yang berkedip), timer tetap merupakan opsi terbaik untuk saat ini, tetapi Anda dapat menggabungkannya dengan requestAnimationFrame untuk mendapatkan yang terbaik dari kedua hal tersebut:

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). Saat ini, fitur ini diaktifkan untuk 50% pengguna Chrome Beta, Dev, dan Canary. Jika Anda ingin mengujinya, gunakan tanda 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 yang intens dimulai setelah 10 detik halaman disembunyikan, bukan 5 menit penuh, sehingga lebih mudah untuk melihat dampak throttling.

Acara mendatang

Karena timer adalah sumber penggunaan CPU yang berlebihan, kita akan terus melihat cara membatasinya tanpa merusak konten web, dan API yang dapat ditambahkan/ubah untuk memenuhi kasus penggunaan. Secara pribadi, saya ingin menghilangkan kebutuhan akan animationInterval dan digantikan oleh callback animasi frekuensi rendah yang efisien. Jika ada pertanyaan, harap hubungi kami di Twitter.

Foto header oleh Heather Zabriskie di Unsplash.