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

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

โอเค ศัพท์ศัพท์เฉพาะทางค่อนข้างใหญ่และกำกวมไปหน่อยนะ มาเริ่มกันเลย...

คำศัพท์

หน้าที่ซ่อน

โดยทั่วไป ซ่อนอยู่หมายความว่ามีแท็บอื่นทำงานอยู่ หรือหน้าต่างถูกย่อขนาดลง แต่เบราว์เซอร์อาจพิจารณาว่ามีการซ่อนหน้าไว้ทุกครั้งที่เนื้อหาของหน้านั้นมองไม่เห็นเลย บางเบราว์เซอร์จะทำงานได้มากกว่าเบราว์เซอร์อื่นๆ แต่คุณสามารถใช้ 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 และอายุการใช้งานแบตเตอรี่

การจัดทำแบบสำรวจของรัฐ

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

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

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

แอนิเมชัน

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

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

หากคุณประกาศภาพเคลื่อนไหวทั้งหมดไว้ล่วงหน้าได้ ให้ลองใช้ภาพเคลื่อนไหวของ CSS หรือ 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 ที่เราสามารถเพิ่ม/เปลี่ยนแปลงเพื่อให้ตรงตามกรณีการใช้งานได้ โดยส่วนตัวแล้ว ผมไม่อยากใช้ animationInterval เพื่อใช้โค้ดเรียกกลับของภาพเคลื่อนไหวความถี่ต่ำที่มีประสิทธิภาพ หากมีข้อสงสัย โปรดติดต่อเราทาง Twitter

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