Vì sao trình duyệt giới hạn tốc độ các bộ hẹn giờ JavaScript
(nolanlawson.com)- Trong JavaScript,
setTimeout(0)thực tế thường không chạy ngay lập tức mà bị trễ tối thiểu 4ms, đây là giới hạn mặc định của trình duyệt để ngăn lạm dụng - Ràng buộc này nhằm ngăn website lạm dụng bộ hẹn giờ một cách bừa bãi gây hao pin hoặc làm giảm khả năng tương tác, và trong chế độ pin hay ở tab nền, giới hạn còn có thể bị siết mạnh hơn như 16ms hoặc 1 giây
- Các nhà phát triển đã sử dụng nhiều API hẹn giờ thay thế như
setImmediate,MessageChannel.postMessage,window.postMessage,scheduler.postTaskđể vượt qua giới hạn củasetTimeout - Kết quả benchmark thực tế cho thấy Chrome và Firefox áp dụng mức chặn 4ms, nhưng
MessageChannelvàscheduler.postTaskhoạt động gần như không có độ trễ, còn Safari có xu hướng hạn chếsetTimeoutmạnh hơn - Về bản chất, đây là vấn đề cân bằng giữa bảo vệ trải nghiệm người dùng và quyền tự do của nhà phát triển; hiện tại Scheduler API đang trở thành lời giải được chuẩn hóa, nhưng nếu xảy ra lạm dụng thì cũng có thể sẽ xuất hiện can thiệp của trình duyệt (Intervention) mới
Bối cảnh của giới hạn setTimeout
- Ngay cả
setTimeout(0)cũng thường chỉ thực sự chạy sau tối thiểu 4ms do lạm dụngconst start = performance.now() setTimeout(() => { // chạy sau khoảng 4ms console.log(performance.now() - start) }, 0) - Mục đích là ngăn các lệnh gọi lặp vô tội vạ để giảm hao pin và độ trễ khi render
- Một số trình duyệt còn tăng mức hạn chế tùy theo môi trường
- Chế độ pin: Edge cũ là 16ms
- Tab nền: Chrome có thể trì hoãn tới 1 giây
Sự xuất hiện của các API hẹn giờ khác
setImmediate: chỉ được hỗ trợ trên IE và Edge cũ, nay gần như đã biến mấtMessageChannel.postMessage: chuyển công việc vào event loop qua một kênh riêngwindow.postMessage: hiệu năng tốt nhưng có nguy cơ xung đột với script khácscheduler.postTask: được hỗ trợ trên trình duyệt hiện đại và được đánh giá là lựa chọn ổn định nhất
Kết quả benchmark (MacBook Pro 2021, đo lặp 101 lần)
- Chrome 139:
setTimeout4.2ms,scheduler.postTask0ms - Firefox 142:
setTimeout4.72ms,scheduler.postTask0.01ms - Safari 18.4:
setTimeout26.73ms,MessageChannel0.52ms,window.postMessage0.05ms
Trường hợp của fake-indexeddb
- IndexedDB muốn transaction được tự động commit ngay sau khi microtask của event loop kết thúc
setImmediatecủa Node.js là lý tưởng, nhưng trên trình duyệt thìsetTimeoutlại kém hiệu quả- Trên Chrome, một tác vụ mất 300ms có thể kéo dài tới 4.8 giây trên trình duyệt
- Cách giải quyết là dùng mặc định
scheduler.postTask, và dùngMessageChannel/window.postMessagelàm phương án dự phòng để đảm bảo tương thích
Tranh luận về can thiệp của trình duyệt
- Một phía cho rằng cần giới hạn timer để nhà phát triển được bảo vệ khỏi chính mình
- Phía còn lại cho rằng cần đảm bảo tự do để nhà phát triển tự đo đạc và tối ưu
- Cuối cùng, theo nguyên tắc ưu tiên người dùng, trình duyệt vẫn can thiệp (intervention) để ngăn lạm dụng
- Scheduler API là sự thỏa hiệp giữa hai lập trường, được thiết kế để trao cho nhà phát triển quyền kiểm soát tác vụ chi tiết hơn đồng thời phù hợp với pipeline render của trình duyệt
Triển vọng sắp tới
postTaskvàpostMessagecó vẻ sẽ tiếp tục không bị throttle trong một thời gian- Nhưng nếu các mức ưu tiên cao như
user-blockingbị lạm dụng thì vẫn có thể lại xuất hiện can thiệp - Về dài hạn, thậm chí có thể sẽ cần một API thay thế khác như
scheduler2
Chưa có bình luận nào.