• ใน 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 การทำงานจริงก็มักเกิดขึ้นหลังอย่างน้อย 4ms
    const 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: setTimeout 4.2ms, scheduler.postTask 0ms
  • Firefox 142: setTimeout 4.72ms, scheduler.postTask 0.01ms
  • Safari 18.4: setTimeout 26.73ms, MessageChannel 0.52ms, window.postMessage 0.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 อีกก็เป็นได้

ยังไม่มีความคิดเห็น

ยังไม่มีความคิดเห็น