เหตุใดเบราว์เซอร์จึงทำ Throttle ตัวจับเวลา JavaScript
(nolanlawson.com)- ใน JavaScript นั้น
setTimeout(0)ไม่ได้ทำงานทันทีจริง ๆ และมักถูกหน่วงอย่างน้อย 4ms ซึ่งเป็นข้อจำกัดพื้นฐานของเบราว์เซอร์เพื่อป้องกันการใช้งานในทางที่ผิด - ข้อจำกัดนี้มีไว้เพื่อป้องกันไม่ให้เว็บไซต์ใช้ตัวจับเวลาอย่างพร่ำเพรื่อจนทำให้ แบตเตอรี่หมดเร็ว หรือ ประสิทธิภาพการโต้ตอบลดลง และในโหมดประหยัดพลังงานอาจถูกจำกัดหนักขึ้นเป็น 16ms ส่วนแท็บเบื้องหลังอาจเพิ่มเป็น 1 วินาที
- นักพัฒนาได้ใช้ Timer API ทางเลือก หลายแบบเพื่อหลบข้อจำกัดของ
setTimeoutเช่นsetImmediate,MessageChannel.postMessage,window.postMessage,scheduler.postTask - จากผลเบนช์มาร์กจริง Chrome และ Firefox ใช้การ clamp ที่ 4ms แต่
MessageChannelและscheduler.postTaskทำงานได้แทบไม่มีดีเลย์ ขณะที่ Safari มีลักษณะจำกัดsetTimeoutเข้มงวดกว่า - โดยแก่นแล้วนี่คือปัญหาเรื่องสมดุลระหว่างการปกป้องประสบการณ์ผู้ใช้กับเสรีภาพของนักพัฒนา ปัจจุบัน Scheduler API กำลังกลายเป็นแนวทางมาตรฐาน แต่หากมีการใช้งานในทางที่ผิด ก็ยังเป็นไปได้ที่จะมี การแทรกแซงของเบราว์เซอร์ (Intervention) แบบใหม่ตามมา
ที่มาของข้อจำกัด setTimeout
- แม้จะเป็น
setTimeout(0)แต่เพราะการใช้งานแบบ abuse การทำงานจริงก็มักเกิดขึ้นหลังอย่างน้อย 4msconst start = performance.now() setTimeout(() => { // ทำงานหลังจากผ่านไปราว 4ms console.log(performance.now() - start) }, 0) - จุดประสงค์คือเพื่อป้องกันการเรียกซ้ำอย่างไร้ขอบเขต ลดการกินแบตเตอรี่และลดความหน่วงในการเรนเดอร์
- บางเบราว์เซอร์เพิ่มความเข้มของข้อจำกัดตามสภาพแวดล้อม
- โหมดประหยัดพลังงาน: Edge รุ่นเก่าจำกัดที่ 16ms
- แท็บเบื้องหลัง: Chrome อาจหน่วงได้สูงสุดถึง 1 วินาที
การมาของ Timer API แบบอื่น
setImmediate: รองรับเฉพาะใน IE และ Edge รุ่นเก่า ปัจจุบันแทบเลิกใช้แล้วMessageChannel.postMessage: ส่งงานเข้าสู่ event loop ผ่านแชนเนลแยกwindow.postMessage: ประสิทธิภาพดี แต่มีความเสี่ยงชนกับสคริปต์อื่นscheduler.postTask: รองรับในเบราว์เซอร์รุ่นใหม่ และถูกมองว่าเป็นตัวเลือกที่เสถียรที่สุด
ผลเบนช์มาร์ก (MacBook Pro 2021, วัดซ้ำ 101 ครั้ง)
- Chrome 139:
setTimeout4.2ms,scheduler.postTask0ms - Firefox 142:
setTimeout4.72ms,scheduler.postTask0.01ms - Safari 18.4:
setTimeout26.73ms,MessageChannel0.52ms,window.postMessage0.05ms
กรณีของ fake-indexeddb
- IndexedDB ต้องการ auto-commit ธุรกรรมทันทีหลังไมโครทาสก์ใน event loop สิ้นสุดลง
setImmediateของ Node.js เหมาะอย่างยิ่ง แต่ในเบราว์เซอร์setTimeoutไม่มีประสิทธิภาพ- งานที่ใช้เวลา 300ms ใน Chrome กลับขยายเป็น 4.8 วินาทีเมื่อรันในเบราว์เซอร์
- วิธีแก้คือใช้
scheduler.postTaskเป็นค่าเริ่มต้น และใช้MessageChannel/window.postMessageเป็น fallback เพื่อความเข้ากันได้
ข้อถกเถียงเรื่องการแทรกแซงของเบราว์เซอร์
- ฝ่ายหนึ่งมองว่าต้องจำกัดตัวจับเวลาเพื่อให้นักพัฒนาป้องกันตัวเองจากการออกแบบที่ไม่เหมาะสม
- อีกฝ่ายมองว่าควร รับประกันเสรีภาพ ให้นักพัฒนาสามารถวัดผลและปรับแต่งได้ด้วยตนเอง
- ท้ายที่สุดตามหลักให้ผู้ใช้มาก่อน เบราว์เซอร์จึงเข้ามาแทรกแซง (intervention) เพื่อป้องกันการใช้งานในทางที่ผิด
- Scheduler API ถูกออกแบบมาเพื่อประนีประนอมสองแนวทางนี้ โดยให้นักพัฒนามี สิทธิ์ควบคุมงาน อย่างละเอียด ขณะเดียวกันก็สอดคล้องกับ rendering pipeline ของเบราว์เซอร์
แนวโน้มต่อจากนี้
postTaskและpostMessageน่าจะยังไม่ถูก throttle ไปอีกระยะหนึ่ง- แต่หากมีการใช้ลำดับความสำคัญสูงอย่าง
user-blockingในทางที่ผิด ก็อาจเกิดการแทรกแซงอีกครั้งได้ - ในระยะยาว อาจจำเป็นต้องมี API ทางเลือกใหม่อย่าง
scheduler2อีกก็เป็นได้
ยังไม่มีความคิดเห็น