Throttling berat timer JS yang dirantai mulai Chrome 88

Jake Archibald
Jake Archibald

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 dengan RTCDataChannel 'terbuka' atau MediaStreamTrack '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:

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.