JavaScript API ใหม่ที่อาจช่วยให้คุณหลีกเลี่ยงการประนีประนอมระหว่างประสิทธิภาพการโหลดกับการตอบสนองอินพุต
การโหลดอย่างรวดเร็วนั้นทำได้ยาก ปัจจุบันเว็บไซต์ที่ใช้ประโยชน์จาก JS เพื่อแสดงผลเนื้อหาต้องเลือกระหว่างประสิทธิภาพการโหลดกับการตอบสนองต่ออินพุต โดยอาจทำงานทั้งหมดที่จำเป็นในการแสดงผลพร้อมกัน (ประสิทธิภาพการโหลดดีขึ้น แต่การตอบสนองต่ออินพุตแย่ลง) หรือแบ่งงานออกเป็นงานเล็กๆ เพื่อให้ตอบสนองต่ออินพุตและแสดงผลได้อย่างต่อเนื่อง (ประสิทธิภาพการโหลดแย่ลง แต่การตอบสนองต่ออินพุตดีขึ้น)
Facebook จึงเสนอและติดตั้งใช้งาน isInputPending()
API ใน Chromium เพื่อปรับปรุงการตอบสนองโดยไม่สูญเสียประสิทธิภาพ เราได้ทำอัปเดต API หลายรายการตามความคิดเห็นที่ได้รับจากช่วงทดลองใช้จากต้นทาง และยินดีที่จะแจ้งให้ทราบว่าตอนนี้ API พร้อมใช้งานโดยค่าเริ่มต้นใน Chromium 87 แล้ว
ความเข้ากันได้กับเบราว์เซอร์
isInputPending()
มาพร้อมกับเบราว์เซอร์ที่ใช้ Chromium ตั้งแต่เวอร์ชัน 87 เป็นต้นไป
ไม่มีเบราว์เซอร์อื่นที่ส่งสัญญาณความตั้งใจที่จะเปิดตัว API
ข้อมูลเบื้องต้น
งานส่วนใหญ่ในระบบนิเวศ JS ในปัจจุบันจะทําในเธรดเดียวคือเธรดหลัก การดำเนินการนี้จะมอบรูปแบบการดำเนินการที่มีประสิทธิภาพให้แก่นักพัฒนาซอฟต์แวร์ แต่ประสบการณ์ของผู้ใช้ (โดยเฉพาะการตอบสนอง) อาจแย่ลงอย่างมากหากสคริปต์ทำงานเป็นเวลานาน หากหน้าเว็บทํางานหลายอย่างขณะที่เหตุการณ์อินพุตเริ่มทำงาน เช่น หน้าเว็บจะไม่จัดการเหตุการณ์อินพุตการคลิกจนกว่างานดังกล่าวจะเสร็จสมบูรณ์
แนวทางปฏิบัติแนะนำในปัจจุบันคือการแก้ปัญหานี้ด้วยการแบ่ง JavaScript ออกเป็นบล็อกเล็กๆ ขณะโหลดหน้าเว็บ หน้าเว็บสามารถเรียกใช้ JavaScript ได้เล็กน้อย จากนั้นจะหยุดดำเนินการและส่งการควบคุมกลับไปที่เบราว์เซอร์ จากนั้นเบราว์เซอร์จะตรวจสอบคิวเหตุการณ์อินพุตเพื่อดูว่ามีอะไรที่ต้องแจ้งให้หน้าเว็บทราบหรือไม่ จากนั้นเบราว์เซอร์จะกลับไปเรียกใช้บล็อก JavaScript ได้ตามปกติเมื่อมีการเพิ่มบล็อก วิธีนี้ช่วยได้ แต่อาจทำให้เกิดปัญหาอื่นๆ
ทุกครั้งที่หน้าเว็บส่งการควบคุมกลับไปยังเบราว์เซอร์ เบราว์เซอร์จะใช้เวลาสักครู่ในการตรวจสอบคิวเหตุการณ์อินพุต ประมวลผลเหตุการณ์ และเลือกบล็อก JavaScript ถัดไป แม้ว่าเบราว์เซอร์จะตอบสนองต่อเหตุการณ์ได้เร็วขึ้น แต่เวลาในการโหลดหน้าเว็บโดยรวมจะช้าลง และหากเรายอมแพ้บ่อยเกินไป หน้าเว็บก็จะโหลดช้าเกินไป หากเรายอมให้เบราว์เซอร์ใช้ทรัพยากรน้อยลง เบราว์เซอร์จะใช้เวลานานขึ้นในการตอบสนองต่อเหตุการณ์ของผู้ใช้ และผู้ใช้อาจไม่พอใจ ไม่สนุก
เราที่ Facebook ต้องการดูว่าจะเกิดอะไรขึ้นหากเราคิดวิธีการโหลดแบบใหม่ที่จะขจัดข้อเสียที่ต้องแลกกันนี้ เราติดต่อเพื่อนที่ Chrome เพื่อพูดคุยเรื่องนี้และได้ข้อเสนอสำหรับ isInputPending()
isInputPending()
API เป็น API แรกที่ใช้แนวคิดการขัดจังหวะสำหรับการป้อนข้อมูลของผู้ใช้บนเว็บ และช่วยให้ JavaScript ตรวจสอบอินพุตได้โดยไม่ต้องยอมให้เบราว์เซอร์ทำงาน
เนื่องจากมีความสนใจใน API นี้ เราจึงร่วมมือกับเพื่อนร่วมงานที่ Chrome เพื่อติดตั้งใช้งานและเปิดตัวฟีเจอร์นี้ใน Chromium เราได้ติดตั้งแพตช์ไว้หลังช่วงทดลองใช้จากต้นทาง (ซึ่งเป็นวิธีที่ Chrome ใช้ทดสอบการเปลี่ยนแปลงและรับความคิดเห็นจากนักพัฒนาซอฟต์แวร์ก่อนที่จะเปิดตัว API อย่างเต็มรูปแบบ) ด้วยความความช่วยเหลือจากวิศวกรของ Chrome
ตอนนี้เราได้นำความคิดเห็นจากการทดลองใช้ต้นทางและจากสมาชิกคนอื่นๆ ของกลุ่มทํางานด้านประสิทธิภาพเว็บของ W3C มาปรับใช้กับ API แล้ว
ตัวอย่างเช่น ตัวจัดตารางเวลาของ Yieldier
สมมติว่าคุณมีงานจำนวนมากที่บล็อกการแสดงผลซึ่งต้องทําเพื่อโหลดหน้าเว็บ เช่น การสร้างมาร์กอัปจากคอมโพเนนต์ การแยกตัวหารออก หรือเพียงแค่วาดภาพสปินเนอร์การโหลดที่เจ๋ง แต่ละรายการจะแบ่งออกเป็นรายการงานแยกต่างหาก เมื่อใช้รูปแบบตัวจัดตารางเวลา มาดูกันว่าเราจะประมวลผลงานในฟังก์ชัน processWorkQueue()
สมมติได้อย่างไร
const DEADLINE = performance.now() + QUANTUM;
while (workQueue.length > 0) {
if (performance.now() >= DEADLINE) {
// Yield the event loop if we're out of time.
setTimeout(processWorkQueue);
return;
}
let job = workQueue.shift();
job.execute();
}
การเรียกใช้ processWorkQueue()
ในภายหลังในมาโครแทสก์ใหม่ผ่าน setTimeout()
จะช่วยให้เบราว์เซอร์ยังคงตอบสนองต่ออินพุตได้อยู่บ้าง (สามารถเรียกใช้ตัวแฮนเดิลเหตุการณ์ได้ก่อนที่จะทํางานต่อ) ขณะเดียวกันก็ยังคงทํางานได้อย่างต่อเนื่อง อย่างไรก็ตาม เราอาจถูกยกเลิกกำหนดการเป็นเวลานานเนื่องจากงานอื่นๆ ที่ต้องการให้ควบคุมลูปเหตุการณ์ หรืออาจเพิ่มเวลาในการตอบสนองของเหตุการณ์ได้สูงสุด QUANTUM
มิลลิวินาที
คุณภาพนี้โอเค แต่เราทำได้ดีกว่านี้ไหม แน่นอน!
const DEADLINE = performance.now() + QUANTUM;
while (workQueue.length > 0) {
if (navigator.scheduling.isInputPending() || performance.now() >= DEADLINE) {
// Yield if we have to handle an input event, or we're out of time.
setTimeout(processWorkQueue);
return;
}
let job = workQueue.shift();
job.execute();
}
การใช้การเรียก navigator.scheduling.isInputPending()
ช่วยให้เราสามารถตอบสนองต่ออินพุตได้เร็วขึ้น ในขณะเดียวกันก็ยังคงทำงานบล็อกการแสดงผลได้อย่างต่อเนื่อง หากเราไม่สนใจที่จะจัดการสิ่งใดนอกจากอินพุต (เช่น การวาดภาพ) จนกว่างานจะเสร็จสมบูรณ์ เราก็สามารถเพิ่มความยาวของ QUANTUM
ได้อย่างง่ายดายเช่นกัน
โดยค่าเริ่มต้น ระบบจะไม่แสดงเหตุการณ์ "ต่อเนื่อง" จาก isInputPending()
ซึ่งรวมถึง mousemove
, pointermove
และอื่นๆ หากสนใจที่จะให้ผลตอบแทนสำหรับรายการเหล่านี้ด้วย ก็ไม่เป็นปัญหา เพียงระบุออบเจ็กต์ให้กับ isInputPending()
โดยตั้งค่า includeContinuous
เป็น true
เราก็พร้อมใช้งาน
const DEADLINE = performance.now() + QUANTUM;
const options = { includeContinuous: true };
while (workQueue.length > 0) {
if (navigator.scheduling.isInputPending(options) || performance.now() >= DEADLINE) {
// Yield if we have to handle an input event (any of them!), or we're out of time.
setTimeout(processWorkQueue);
return;
}
let job = workQueue.shift();
job.execute();
}
เท่านี้ก็เรียบร้อย เฟรมเวิร์กอย่าง React กำลังสร้างการรองรับ isInputPending()
ลงในไลบรารีการจัดตารางเวลาหลักโดยใช้ตรรกะแบบเดียวกัน เราหวังว่าการเปลี่ยนแปลงนี้จะช่วยให้นักพัฒนาซอฟต์แวร์ที่ใช้เฟรมเวิร์กเหล่านี้ได้รับประโยชน์จาก isInputPending()
เบื้องหลังได้โดยไม่ต้องเขียนโค้ดใหม่มากนัก
การยอมแพ้ไม่ได้แย่เสมอไป
โปรดทราบว่าการลดจำนวนโฆษณาลงไม่ใช่โซลูชันที่เหมาะสมสำหรับทุกกรณี การส่งคืนการควบคุมไปยังเบราว์เซอร์มีเหตุผลหลายประการนอกเหนือจากการประมวลผลเหตุการณ์อินพุต เช่น การแสดงผลและการเรียกใช้สคริปต์อื่นๆ ในหน้า
อาจมีกรณีที่เบราว์เซอร์ระบุแหล่งที่มาของเหตุการณ์อินพุตที่รอดำเนินการอย่างไม่ถูกต้อง โดยเฉพาะอย่างยิ่ง การตั้งค่าคลิปและมาสก์ที่ซับซ้อนสําหรับ iframe จากแหล่งที่มาต่างๆ อาจรายงานผลเป็นลบที่ไม่ถูกต้อง (นั่นคือ isInputPending()
อาจแสดงผลเป็นเท็จโดยไม่คาดคิดเมื่อกําหนดเป้าหมายเฟรมเหล่านี้) ตรวจสอบว่าคุณให้ผลลัพธ์บ่อยพอหากเว็บไซต์ต้องมีการโต้ตอบกับเฟรมย่อยที่มีสไตล์
โปรดคำนึงถึงหน้าอื่นๆ ที่แชร์ลูปเหตุการณ์ด้วย ในแพลตฟอร์มต่างๆ เช่น Chrome สําหรับ Android การที่ต้นทางหลายแห่งใช้ Event Loop ร่วมกันนั้นเป็นเรื่องปกติ isInputPending()
จะไม่แสดงผลเป็น true
เลยหากมีการส่งอินพุตไปยังเฟรมข้ามแหล่งที่มา และหน้าเว็บที่ทำงานอยู่เบื้องหลังอาจรบกวนการตอบสนองของหน้าเว็บที่ทำงานอยู่เบื้องหน้า คุณอาจต้องลด เลื่อนเวลา หรือลดเวลาทำงานบ่อยขึ้นเมื่อทํางานในเบื้องหลังโดยใช้ Page Visibility API
เราขอแนะนำให้คุณใช้ isInputPending()
อย่างรอบคอบ หากไม่มีงานบล็อกผู้ใช้ ให้ช่วยผู้อื่นในลูปเหตุการณ์ด้วยการยอมแพ้บ่อยขึ้น งานที่มีระยะเวลานานอาจเป็นอันตรายได้
ความคิดเห็น
- แสดงความคิดเห็นเกี่ยวกับข้อกำหนดในที่เก็บข้อมูล is-input-pending
- ติดต่อ @acomminos (หนึ่งในผู้เขียนข้อกำหนด) ทาง Twitter
บทสรุป
เรายินดีที่ isInputPending()
เปิดตัวแล้ว และนักพัฒนาแอปเริ่มใช้งานได้ในวันนี้ API นี้เป็น API บนเว็บใหม่ที่ Facebook สร้างขึ้นเป็นครั้งแรก และนำแนวคิดนี้ไปพัฒนาต่อจากการสร้างต้นแบบเป็นข้อเสนอมาตรฐานเพื่อนำไปใช้งานจริงในเบราว์เซอร์ ขอขอบคุณทุกคนที่ช่วยให้เราได้มาถึงจุดนี้ และขอขอบคุณเป็นพิเศษสำหรับทุกคนในทีม Chrome ที่ช่วยเราพัฒนาไอเดียนี้และทำให้พร้อมใช้งาน
รูปภาพหลักโดย Will H McMahan จาก Unsplash