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 การที่หลายต้นทางแชร์การวนซ้ำเหตุการณ์เป็นเรื่องปกติ isInputPending()
จะไม่แสดงผลเป็น true
เลยหากมีการส่งอินพุตไปยังเฟรมข้ามแหล่งที่มา และหน้าเว็บที่ทำงานอยู่เบื้องหลังอาจรบกวนการตอบสนองของหน้าเว็บที่ทำงานอยู่เบื้องหน้า คุณอาจต้องการลดจำนวน เลื่อนออกไป หรือให้บ่อยขึ้นเมื่อทำงานในเบื้องหลังโดยใช้ Page visibility API
เราขอแนะนำให้คุณใช้ isInputPending()
อย่างรอบคอบ หากไม่มีงานบล็อกผู้ใช้ ให้ช่วยผู้อื่นในลูปเหตุการณ์ด้วยการยอมแพ้บ่อยขึ้น งานที่มีระยะเวลานานอาจเป็นอันตรายได้
ความคิดเห็น
- แสดงความคิดเห็นเกี่ยวกับข้อกำหนดในที่เก็บข้อมูล is-input-pending
- ติดต่อ @acomminos (หนึ่งในผู้เขียนข้อกำหนด) ทาง Twitter
บทสรุป
เรายินดีที่ isInputPending()
เปิดตัวแล้ว และนักพัฒนาแอปเริ่มใช้งานได้ในวันนี้ API นี้เป็น API บนเว็บใหม่ที่ Facebook สร้างขึ้นเป็นครั้งแรก และนำแนวคิดนี้ไปพัฒนาต่อจากการสร้างต้นแบบเป็นข้อเสนอมาตรฐานเพื่อนำไปใช้งานจริงในเบราว์เซอร์ เราขอขอบคุณทุกคนที่ช่วยให้มาถึงจุดนี้
และกล่าวขอบคุณทุกคนที่ Chrome ที่ช่วยกันเผยแพร่
ไอเดียนี้และเผยแพร่
รูปภาพหลักโดย Will H McMahan จาก Unsplash