การกำหนดเวลา 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