การกำหนดเวลา JS ที่ดีขึ้นด้วย isInputPending()

JavaScript API ใหม่ที่อาจช่วยให้คุณหลีกเลี่ยงการประนีประนอมระหว่างประสิทธิภาพการโหลดกับการตอบสนองอินพุต

Nate Schloss
Nate Schloss
Andrew Comminos
Andrew Comminos

การโหลดอย่างรวดเร็วนั้นทำได้ยาก ปัจจุบันเว็บไซต์ที่ใช้ประโยชน์จาก JS เพื่อแสดงผลเนื้อหาต้องเลือกระหว่างประสิทธิภาพการโหลดกับการตอบสนองต่ออินพุต โดยอาจทำงานทั้งหมดที่จำเป็นในการแสดงผลพร้อมกัน (ประสิทธิภาพการโหลดดีขึ้น แต่การตอบสนองต่ออินพุตแย่ลง) หรือแบ่งงานออกเป็นงานเล็กๆ เพื่อให้ตอบสนองต่ออินพุตและแสดงผลได้อย่างต่อเนื่อง (ประสิทธิภาพการโหลดแย่ลง แต่การตอบสนองต่ออินพุตดีขึ้น)

Facebook จึงเสนอและติดตั้งใช้งาน isInputPending() API ใน Chromium เพื่อปรับปรุงการตอบสนองโดยไม่สูญเสียประสิทธิภาพ เราได้ทำอัปเดต API หลายรายการตามความคิดเห็นที่ได้รับจากช่วงทดลองใช้จากต้นทาง และยินดีที่จะแจ้งให้ทราบว่าตอนนี้ API พร้อมใช้งานโดยค่าเริ่มต้นใน Chromium 87 แล้ว

ความเข้ากันได้กับเบราว์เซอร์

การรองรับเบราว์เซอร์

  • Chrome: 87
  • Edge: 87
  • Firefox: ไม่สนับสนุน
  • Safari: ไม่รองรับ

แหล่งที่มา

isInputPending() มาพร้อมกับเบราว์เซอร์ที่ใช้ Chromium ตั้งแต่เวอร์ชัน 87 เป็นต้นไป ไม่มีเบราว์เซอร์อื่นที่ส่งสัญญาณความตั้งใจที่จะเปิดตัว API

ข้อมูลเบื้องต้น

งานส่วนใหญ่ในระบบนิเวศ JS ในปัจจุบันจะทําในเธรดเดียวคือเธรดหลัก การดำเนินการนี้จะมอบรูปแบบการดำเนินการที่มีประสิทธิภาพให้แก่นักพัฒนาซอฟต์แวร์ แต่ประสบการณ์ของผู้ใช้ (โดยเฉพาะการตอบสนอง) อาจแย่ลงอย่างมากหากสคริปต์ทำงานเป็นเวลานาน หากหน้าเว็บทํางานหลายอย่างขณะที่เหตุการณ์อินพุตเริ่มทำงาน เช่น หน้าเว็บจะไม่จัดการเหตุการณ์อินพุตการคลิกจนกว่างานดังกล่าวจะเสร็จสมบูรณ์

แนวทางปฏิบัติแนะนำในปัจจุบันคือการแก้ปัญหานี้ด้วยการแบ่ง JavaScript ออกเป็นบล็อกเล็กๆ ขณะโหลดหน้าเว็บ หน้าเว็บสามารถเรียกใช้ JavaScript ได้เล็กน้อย จากนั้นจะหยุดดำเนินการและส่งการควบคุมกลับไปที่เบราว์เซอร์ จากนั้นเบราว์เซอร์จะตรวจสอบคิวเหตุการณ์อินพุตเพื่อดูว่ามีอะไรที่ต้องแจ้งให้หน้าเว็บทราบหรือไม่ จากนั้นเบราว์เซอร์จะกลับไปเรียกใช้บล็อก JavaScript ได้ตามปกติเมื่อมีการเพิ่มบล็อก วิธีนี้ช่วยได้ แต่อาจทำให้เกิดปัญหาอื่นๆ

ทุกครั้งที่หน้าเว็บส่งการควบคุมกลับไปยังเบราว์เซอร์ เบราว์เซอร์จะใช้เวลาสักครู่ในการตรวจสอบคิวเหตุการณ์อินพุต ประมวลผลเหตุการณ์ และเลือกบล็อก JavaScript ถัดไป แม้ว่าเบราว์เซอร์จะตอบสนองต่อเหตุการณ์ได้เร็วขึ้น แต่เวลาในการโหลดหน้าเว็บโดยรวมจะช้าลง และหากเรายอมแพ้บ่อยเกินไป หน้าเว็บก็จะโหลดช้าเกินไป ถ้าเราให้ผลตอบแทนน้อย อาจทำให้เบราว์เซอร์ใช้เวลานานขึ้นในการตอบสนองเหตุการณ์ของผู้ใช้ และผู้คนอาจหงุดหงิดได้ ไม่สนุก

แผนภาพที่แสดงให้เห็นว่าเมื่อคุณเรียกใช้งาน JS ที่ใช้เวลานาน เบราว์เซอร์จะมีเวลาน้อยลงในการกระจายเหตุการณ์

เราที่ Facebook ต้องการดูว่าจะเกิดอะไรขึ้นหากเราคิดวิธีการโหลดแบบใหม่ที่จะขจัดข้อเสียที่น่าหงุดหงิดนี้ เราติดต่อเพื่อนที่ Chrome เพื่อพูดคุยเรื่องนี้และได้ข้อเสนอสำหรับ isInputPending() isInputPending() API เป็น API แรกที่ใช้แนวคิดการขัดจังหวะสำหรับการป้อนข้อมูลของผู้ใช้บนเว็บ และช่วยให้ JavaScript ตรวจสอบอินพุตได้โดยไม่ต้องยอมให้เบราว์เซอร์ทำงาน

แผนภาพที่แสดงให้เห็นว่า isInputPending() ช่วยให้ JS ตรวจสอบได้ว่ามีอินพุตที่รอดำเนินการของผู้ใช้หรือไม่ โดยไม่ต้องส่งคืนการดำเนินการไปยังเบราว์เซอร์โดยสมบูรณ์

เนื่องจากมีความสนใจใน 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