การควบคุมตัวจับเวลา JS แบบเชนจำนวนมากเริ่มต้นใน Chrome 88

Chrome 88 (มกราคม 2021) จะจำกัดตัวจับเวลา JavaScript ที่ลิงก์กันอย่างมากสำหรับหน้าเว็บที่ซ่อนอยู่ในบางเงื่อนไข ซึ่งจะช่วยลดการใช้งาน CPU และการใช้งานแบตเตอรี่ด้วย ในกรณีที่ไม่ปกติบางอย่าง การดำเนินการนี้จะเปลี่ยนลักษณะการทำงาน แต่มักจะใช้ตัวจับเวลาในกรณีที่ API อื่นมีประสิทธิภาพและเชื่อถือได้มากกว่า

โอเค ข้อมูลนี้ค่อนข้างเป็นศัพท์เทคนิคและคลุมเครือไปหน่อย มาเริ่มกันเลย

คำศัพท์

หน้าเว็บที่ซ่อนอยู่

โดยทั่วไป ซ่อนอยู่หมายความว่าแท็บอื่นทำงานอยู่หรือหน้าต่างถูกย่ออยู่ แต่เบราว์เซอร์อาจถือว่าหน้าเว็บซ่อนอยู่เมื่อเนื้อหาของหน้านั้นไม่แสดงเลย เบราว์เซอร์บางประเภทจะดำเนินการเพิ่มเติมจากเบราว์เซอร์อื่นๆ แต่คุณใช้ Page Visibility API เพื่อติดตามได้เสมอเมื่อเบราว์เซอร์คิดว่าระดับการเข้าถึงมีการเปลี่ยนแปลง

ตัวจับเวลา JavaScript

ตัวจับเวลา JavaScript หมายถึง setTimeout และ setInterval ซึ่งช่วยให้คุณกำหนดเวลาการเรียกกลับได้ในอนาคต ตัวจับเวลามีประโยชน์และยังคงอยู่ต่อไป แต่บางครั้งก็ใช้เพื่อตรวจสอบสถานะเมื่อเหตุการณ์มีประสิทธิภาพและแม่นยำมากขึ้น

ตัวจับเวลาที่เชื่อมโยงกัน

หากคุณเรียกใช้ setTimeout ในงานที่เป็นแฮนเดิลการเรียกกลับ setTimeout การเรียกใช้ครั้งที่ 2 จะ "ต่อท้าย" เมื่อใช้ setInterval แต่ละรอบจะเป็นส่วนหนึ่งของเชน ตัวอย่างนี้อาจเข้าใจได้ง่ายขึ้นเมื่อดูโค้ด

let chainCount = 0;

setInterval(() => {
  chainCount++;
  console.log(`This is number ${chainCount} in the chain`);
}, 500);

และ:

let chainCount = 0;

function setTimeoutChain() {
  setTimeout(() => {
    chainCount++;
    console.log(`This is number ${chainCount} in the chain`);
    setTimeoutChain();
  }, 500);
}

วิธีการทำงานของการจำกัด

การจำกัดจะเกิดขึ้นเป็นระยะๆ ดังนี้

การควบคุมขั้นต่ำ

กรณีนี้จะเกิดขึ้นกับตัวจับเวลาที่ตั้งเวลาไว้เมื่อเงื่อนไขใดเงื่อนไขหนึ่งต่อไปนี้เป็นจริง

  • หน้าเว็บแสดงอยู่
  • หน้าเว็บส่งเสียงในช่วง 30 วินาทีที่ผ่านมา เสียงนี้อาจมาจาก API การสร้างเสียงใดก็ได้ แต่จะยกเว้นแทร็กเสียงที่ไม่มีเสียง

ตัวจับเวลาจะไม่ถูกจำกัด เว้นแต่ว่าระยะหมดเวลาที่ขอจะน้อยกว่า 4 มิลลิวินาที และจำนวนเชนเป็น 5 รายการขึ้นไป ซึ่งในกรณีนี้ระบบจะตั้งค่าระยะหมดเวลาเป็น 4 มิลลิวินาที การดำเนินการนี้ไม่ใช่สิ่งใหม่ เบราว์เซอร์ต่างๆ ดำเนินการเช่นนี้มาหลายปีแล้ว

การควบคุม

กรณีนี้จะเกิดขึ้นกับตัวจับเวลาที่กําหนดเวลาไว้เมื่อการควบคุมปริมาณขั้นต่ำไม่มีผล และเงื่อนไขใดๆ ต่อไปนี้เป็นจริง

  • จํานวนเชนน้อยกว่า 5
  • หน้าเว็บซ่อนอยู่ไม่ถึง 5 นาที
  • มีการใช้ WebRTC กล่าวโดยละเอียดคือ มี RTCPeerConnection ที่มีสถานะ "เปิด" RTCDataChannel หรือ MediaStreamTrack ที่ "เผยแพร่อยู่"

เบราว์เซอร์จะตรวจสอบตัวจับเวลาในกลุ่มนี้ 1 ครั้งต่อวินาที เนื่องจากมีการตรวจสอบเพียงครั้งเดียวต่อวินาที ตัวจับเวลาที่มีระยะหมดเวลาคล้ายกันจะรวมกลุ่มกันเพื่อรวมเวลาที่แท็บต้องใช้เรียกใช้โค้ด การดำเนินการนี้ไม่ใช่สิ่งใหม่ เบราว์เซอร์ต่างๆ ดำเนินการนี้มาหลายปีแล้ว

การควบคุมอย่างเข้มข้น

โอเค มาดูฟีเจอร์ใหม่ใน Chrome 88 กัน การจำกัดจำนวนสูงสุดจะเกิดขึ้นกับตัวจับเวลาที่กําหนดเวลาไว้เมื่อไม่มีเงื่อนไขการจำกัดจำนวนสูงสุดขั้นต่ำหรือการจำกัดจำนวนใดๆ มีผล และเงื่อนไขทั้งหมดต่อไปนี้เป็นจริง

  • หน้าเว็บซ่อนอยู่นานกว่า 5 นาที
  • จํานวนเชนเท่ากับ 5 ขึ้นไป
  • หน้าเว็บไม่มีเสียงเป็นเวลาอย่างน้อย 30 วินาที
  • ไม่ได้ใช้ WebRTC

ในกรณีนี้ เบราว์เซอร์จะตรวจสอบตัวจับเวลาในกลุ่มนี้ 1 ครั้งต่อนาที ซึ่งหมายความว่าตัวจับเวลาจะรวมกันเป็นกลุ่มในการตรวจสอบแบบนาทีต่อนาทีเหล่านี้ เช่นเดียวกับที่เคยเป็นมา

วิธีแก้ปัญหา

โดยทั่วไปแล้วจะมีทางเลือกอื่นที่ดีกว่าตัวจับเวลา หรืออาจใช้ตัวจับเวลาร่วมกับสิ่งอื่นเพื่อถนอมอายุการใช้งานของ CPU และแบตเตอรี่

การสำรวจรัฐ

นี่เป็นการใช้ตัวจับเวลา (ในทางที่ผิด) ที่พบบ่อยที่สุด ซึ่งจะใช้เพื่อตรวจสอบหรือสอบถามอย่างต่อเนื่องเพื่อดูว่ามีการเปลี่ยนแปลงหรือไม่ ในกรณีส่วนใหญ่ จะมีการดำเนินการที่เทียบเท่าการพุช ซึ่งจะแจ้งให้คุณทราบเกี่ยวกับการเปลี่ยนแปลงที่เกิดขึ้น คุณจึงไม่ต้องคอยตรวจสอบ ตรวจสอบว่ามีเหตุการณ์ที่ทําสิ่งเดียวกันหรือไม่

ตัวอย่างมีดังต่อไปนี้

นอกจากนี้ยังมีทริกเกอร์การแจ้งเตือนหากคุณต้องการแสดงการแจ้งเตือนในเวลาที่เจาะจง

แอนิเมชัน

ภาพเคลื่อนไหวเป็นองค์ประกอบที่แสดงผลภาพ ดังนั้นจึงไม่ควรใช้เวลาของ CPU เมื่อหน้าเว็บซ่อนอยู่

requestAnimationFrame กำหนดเวลางานภาพเคลื่อนไหวได้ดีกว่าตัวจับเวลา JavaScript มาก โดยจะซิงค์กับอัตราการรีเฟรชของอุปกรณ์ เพื่อให้คุณได้รับเฉพาะการเรียกกลับ 1 ครั้งต่อเฟรมที่แสดงได้ และคุณจะมีเวลาสูงสุดในการสร้างเฟรมนั้น นอกจากนี้ requestAnimationFrame จะรอให้หน้าเว็บปรากฏขึ้น เพื่อที่จะไม่ใช้ CPU เมื่อหน้าเว็บซ่อนอยู่

หากประกาศภาพเคลื่อนไหวทั้งหมดล่วงหน้าได้ ให้พิจารณาใช้ภาพเคลื่อนไหว CSS หรือ Web Animation API ซึ่งมีข้อดีเช่นเดียวกับ requestAnimationFrame แต่เบราว์เซอร์จะเพิ่มประสิทธิภาพเพิ่มเติมได้ เช่น การคอมโพสอัตโนมัติ และโดยทั่วไปแล้วใช้งานได้ง่ายกว่า

หากภาพเคลื่อนไหวมีอัตราเฟรมต่ำ (เช่น เคอร์เซอร์กะพริบ) ตัวจับเวลาจะยังคงเป็นตัวเลือกที่ดีที่สุดในตอนนี้ แต่คุณสามารถใช้ร่วมกับ requestAnimationFrame เพื่อใช้ข้อดีของทั้ง 2 อย่างร่วมกันได้ ดังนี้

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);
}

การใช้งาน:

const controller = new AbortController();

// Create an animation callback every second:
animationInterval(1000, controller.signal, time => {
  console.log('tick!', time);
});

// And stop it:
controller.abort();

การทดสอบ

การเปลี่ยนแปลงนี้จะเปิดใช้สำหรับผู้ใช้ Chrome ทุกคนใน Chrome 88 (มกราคม 2021) ปัจจุบันฟีเจอร์นี้เปิดใช้สำหรับผู้ใช้ Chrome เบต้า เวอร์ชันที่กำลังพัฒนา และ Canary 50% หากต้องการทดสอบ ให้ใช้การติดธงบรรทัดคำสั่งนี้เมื่อเปิด Chrome เวอร์ชันเบต้า กำลังพัฒนา หรือ Canary

--enable-features="IntensiveWakeUpThrottling:grace_period_seconds/10,OptOutZeroTimeoutTimersFromThrottling,AllowAggressiveThrottlingWithWebSocket"

อาร์กิวเมนต์ grace_period_seconds/10 จะทําให้การควบคุมการเรียกใช้อย่างเข้มข้นเริ่มทำงานหลังจากหน้าเว็บซ่อนอยู่ 10 วินาทีแทนที่จะเป็น 5 นาทีเต็ม ซึ่งจะช่วยให้เห็นผลกระทบของการควบคุมการเรียกใช้ได้ง่ายขึ้น

อนาคต

เนื่องจากตัวจับเวลาเป็นแหล่งที่มาของการใช้ CPU มากเกินไป เราจะยังคงมองหาวิธีควบคุมตัวจับเวลาโดยไม่ทำให้เนื้อหาเว็บเสียหาย และ API ที่เราสามารถเพิ่ม/เปลี่ยนแปลงเพื่อให้เป็นไปตาม Use Case เราต้องการยกเลิกการใช้ animationInterval เพื่อใช้การเรียกกลับภาพเคลื่อนไหวความถี่ต่ำที่มีประสิทธิภาพแทน หากมีข้อสงสัย โปรดติดต่อเราทาง Twitter

รูปภาพส่วนหัวโดย Heather Zabriskie จาก Unsplash