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

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

Nate Schloss
Nate Schloss
Andrew Comminos
Andrew Comminos

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

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

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

การสนับสนุนเบราว์เซอร์

  • 87
  • 87
  • x
  • x

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

ที่มา

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

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

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

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

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

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

เนื่องจากผู้ใช้สนใจ API ดังกล่าว เราจึงร่วมมือกับเพื่อนร่วมงานที่ Chrome เพื่อใช้งานและจัดส่งฟีเจอร์นี้ใน Chromium ด้วยความช่วยเหลือจากวิศวกร Chrome เราจึงได้รับแพตช์จากช่วงทดลองใช้จากต้นทาง (ซึ่งเป็นวิธีที่ Chrome จะใช้ทดสอบการเปลี่ยนแปลงและรับความคิดเห็นจากนักพัฒนาแอปก่อนที่จะเผยแพร่ API อย่างเต็มรูปแบบ)

ตอนนี้เราได้รับความคิดเห็นจากช่วงทดลองใช้จากต้นทางและจากสมาชิกคนอื่นๆ ในคณะทำงานของ W3C Web Performance และนำการเปลี่ยนแปลงไปใช้กับ API แล้ว

ตัวอย่าง: เครื่องจัดตารางเวลาผลตอบแทน

สมมติว่าคุณมีการบล็อกโฆษณาแบบดิสเพลย์จำนวนมากที่ต้องทำเพื่อโหลดหน้าเว็บ เช่น การสร้างมาร์กอัปจากคอมโพเนนต์ แยกตัวประกอบ หรือแค่วาดตัวหมุนแสดงการโหลดเจ๋งๆ แต่ละเครื่องมือจะแยกออกเป็น รายการงานแยกกัน มาลองร่างวิธีที่เราอาจประมวลผลงานในฟังก์ชัน 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 หากอินพุตส่งไปยังเฟรมแบบข้ามต้นทาง หน้าที่อยู่เบื้องหลังจึงอาจรบกวนการตอบสนองของหน้าเบื้องหน้า คุณอาจต้องการลด เลื่อน หรือให้ผลการค้นหาบ่อยขึ้นเมื่อทำงานในเบื้องหลังโดยใช้ API สำหรับระดับการเข้าถึงหน้าเว็บ

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

ความคิดเห็น

  • แสดงความคิดเห็นเกี่ยวกับข้อกำหนดในที่เก็บ is-input-pending
  • ติดต่อ @acomminos (หนึ่งในผู้เขียนข้อกำหนด) บน Twitter

บทสรุป

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

รูปภาพหลักโดย Will H McMahan ใน Unsplash