เพิ่มประสิทธิภาพงานที่ใช้เวลานาน

คุณอาจเคยได้ยินคำแนะนำว่า "อย่าบล็อกเทรดหลัก" และ "แบ่งงานที่ใช้เวลานาน" แต่การทำเช่นนั้นหมายความว่าอย่างไร

เผยแพร่: 30 กันยายน 2022, อัปเดตล่าสุด: 19 ธันวาคม 2024

คำแนะนำทั่วไปในการทำให้แอป JavaScript ทำงานได้อย่างรวดเร็วมักจะสรุปได้ดังนี้

  • "อย่าบล็อกเทรดหลัก"
  • "แบ่งงานที่ยาวๆ ออกเป็นงานย่อยๆ"

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

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

งานคืออะไร

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

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

งานที่เชื่อมโยงกับ JavaScript จะส่งผลต่อประสิทธิภาพใน 2 ลักษณะดังนี้

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

สิ่งเหล่านี้ทั้งหมด ยกเว้น Web Worker และ API ที่คล้ายกัน จะเกิดขึ้นในชุดข้อความหลัก

เทรดหลักคืออะไร

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

เทรดหลักจะประมวลผลงานได้ครั้งละ 1 งานเท่านั้น งานที่ใช้เวลานานกว่า 50 มิลลิวินาทีถือเป็นงานที่ใช้เวลานาน สำหรับงานที่ใช้เวลาเกิน 50 มิลลิวินาที เวลาทั้งหมดของงานลบด้วย 50 มิลลิวินาทีจะเรียกว่าระยะเวลาการบล็อกของงาน

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

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

คุณสามารถแบ่งงานที่ใช้เวลานานออกเป็นงานย่อยๆ หลายงานเพื่อป้องกันไม่ให้เทรดหลักถูกบล็อกนานเกินไป

งานเดี่ยวที่ใช้เวลานานเทียบกับงานเดียวกันที่แบ่งออกเป็นงานย่อยๆ ที่ใช้เวลาสั้นกว่า งานที่ใช้เวลานานคือสี่เหลี่ยมผืนผ้าใหญ่ขนาดใหญ่ ส่วนงานที่แบ่งเป็นส่วนๆ คือกล่องขนาดเล็ก 5 กล่องซึ่งมีความกว้างรวมเท่ากับงานที่ใช้เวลานาน
ภาพการแสดงงานที่ใช้เวลานาน 1 งานเทียบกับงานเดียวกันที่แบ่งออกเป็นงานย่อย 5 งาน

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

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

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

เมื่อทราบถึงความสำคัญของการแบ่งงานแล้ว คุณก็สามารถดูวิธีแบ่งงานใน JavaScript ได้

กลยุทธ์การจัดการงาน

คำแนะนำที่พบบ่อยในสถาปัตยกรรมซอฟต์แวร์คือการแบ่งงานออกเป็นฟังก์ชันเล็กๆ ดังนี้

function saveSettings () {
  validateForm();
  showSpinner();
  saveToDatabase();
  updateUI();
  sendAnalytics();
}

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

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

อย่างไรก็ตาม ปัญหาที่อาจเกิดขึ้นที่นี่คือ JavaScript ไม่ได้เรียกใช้ฟังก์ชันเหล่านี้แต่ละฟังก์ชันเป็นงานแยกต่างหากเนื่องจากฟังก์ชันเหล่านี้จะดำเนินการภายในฟังก์ชัน saveSettings() ซึ่งหมายความว่าฟังก์ชันทั้ง 5 จะทำงานเป็นงานเดียว

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

ในกรณีที่ดีที่สุด ฟังก์ชันเหล่านั้นเพียงฟังก์ชันเดียวก็อาจทำให้ระยะเวลาทั้งหมดของงานเพิ่มขึ้น 50 มิลลิวินาทีหรือมากกว่า ในกรณีที่แย่ที่สุด งานเหล่านั้นอาจทำงานนานขึ้นมาก โดยเฉพาะในอุปกรณ์ที่มีทรัพยากรจำกัด

ในกรณีนี้ saveSettings() จะทริกเกอร์เมื่อผู้ใช้คลิก และเนื่องจากเบราว์เซอร์ไม่สามารถแสดงการตอบสนองจนกว่าฟังก์ชันทั้งหมดจะทำงานเสร็จสิ้น ผลลัพธ์ของงานที่ใช้เวลานานนี้จึงทำให้ UI ทำงานช้าและไม่ตอบสนอง และจะวัดผลเป็น Interaction to Next Paint (INP) ที่ไม่ดี

เลื่อนการเรียกใช้โค้ดด้วยตนเอง

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

วิธีหนึ่งที่นักพัฒนาแอปใช้ในการแบ่งงานออกเป็นงานย่อยๆ คือการใช้ setTimeout() เทคนิคนี้จะส่งฟังก์ชันไปยัง setTimeout() ซึ่งจะเลื่อนการเรียกใช้แฮนเดิลไปยังงานอื่น แม้ว่าคุณจะระบุการหมดเวลาเป็น 0 ก็ตาม

function saveSettings () {
  // Do critical work that is user-visible:
  validateForm();
  showSpinner();
  updateUI();

  // Defer work that isn't user-visible to a separate task:
  setTimeout(() => {
    saveToDatabase();
    sendAnalytics();
  }, 0);
}

ซึ่งเรียกว่าการส่งผ่าน และเหมาะที่สุดสำหรับฟังก์ชันชุดหนึ่งที่ต้องทำงานตามลำดับ

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

function processData () {
  for (const item of largeDataArray) {
    // Process the individual item here.
  }
}

การใช้ setTimeout() ที่นี่มีปัญหาเนื่องจากสรีรศาสตร์ของนักพัฒนาซอฟต์แวร์ และหลังจาก setTimeout() ซ้อนกัน 5 รอบ เบราว์เซอร์จะเริ่มกำหนดให้มีการหน่วงเวลาขั้นต่ำ 5 มิลลิวินาทีสำหรับ setTimeout() เพิ่มเติมแต่ละรายการ

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

API การเพิ่มประสิทธิภาพเฉพาะ: scheduler.yield()

Browser Support

  • Chrome: 129.
  • Edge: 129.
  • Firefox: 142.
  • Safari: not supported.

Source

scheduler.yield() เป็น API ที่ออกแบบมาโดยเฉพาะเพื่อการยอมให้ใช้เธรดหลักในเบราว์เซอร์

โดยไม่ใช่ไวยากรณ์ระดับภาษาหรือโครงสร้างพิเศษ แต่ scheduler.yield() เป็นเพียงฟังก์ชันที่ส่งคืน Promise ซึ่งจะได้รับการแก้ไขในงานในอนาคต โค้ดใดก็ตามที่เชื่อมโยงให้ทำงานหลังจากที่ Promise ได้รับการแก้ไขแล้ว (ไม่ว่าจะอยู่ในเชน .then() ที่ชัดเจนหรือหลังจาก await ในฟังก์ชันแบบไม่พร้อมกัน) จะทำงานในงานในอนาคตนั้น

ในทางปฏิบัติ ให้แทรก await scheduler.yield() แล้วฟังก์ชันจะหยุดการดำเนินการชั่วคราว ณ จุดนั้นและส่งต่อให้เทรดหลัก ระบบจะกำหนดเวลาการดำเนินการฟังก์ชันที่เหลือ ซึ่งเรียกว่าการดำเนินการต่อของฟังก์ชัน ให้ทำงานในงาน Event Loop ใหม่ เมื่อเริ่มงานนั้นแล้ว Promise ที่รออยู่จะได้รับการแก้ไข และฟังก์ชันจะดำเนินการต่อจากจุดที่หยุดไว้

async function saveSettings () {
  // Do critical work that is user-visible:
  validateForm();
  showSpinner();
  updateUI();

  // Yield to the main thread:
  await scheduler.yield()

  // Work that isn't user-visible, continued in a separate task:
  saveToDatabase();
  sendAnalytics();
}
ฟังก์ชัน saveSettings ตามที่แสดงในโปรไฟล์ประสิทธิภาพของ Chrome ตอนนี้แบ่งออกเป็น 2 งานแล้ว งานแรกจะเรียกใช้ 2 ฟังก์ชัน จากนั้นจะหยุดชั่วคราวเพื่อให้การทำงานของเลย์เอาต์และการแสดงผลเกิดขึ้น และให้การตอบสนองที่ผู้ใช้มองเห็นได้ ด้วยเหตุนี้ กิจกรรมการคลิกจึงเสร็จสิ้นในเวลาเพียง 64 มิลลิวินาที ซึ่งเร็วกว่ามาก งานที่ 2 จะเรียกใช้ฟังก์ชัน 3 รายการสุดท้าย
ตอนนี้การเรียกใช้ฟังก์ชัน saveSettings() จะแยกออกเป็น 2 งาน ด้วยเหตุนี้ เลย์เอาต์และการแสดงผลจึงสามารถทำงานระหว่างงานต่างๆ ได้ ทำให้ผู้ใช้ได้รับการตอบสนองด้วยภาพที่รวดเร็วขึ้น ซึ่งวัดได้จากการโต้ตอบของเคอร์เซอร์ที่สั้นลงมากในตอนนี้

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

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

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

การรองรับข้ามเบราว์เซอร์

scheduler.yield() ยังไม่รองรับในเบราว์เซอร์บางรายการ จึงต้องมีฟอลแบ็ก

วิธีหนึ่งคือการวาง scheduler-polyfill ลงในบิลด์ แล้วใช้ scheduler.yield() ได้โดยตรง Polyfill จะจัดการการเปลี่ยนไปใช้ฟังก์ชันการจัดกำหนดเวลางานอื่นๆ เพื่อให้ทำงานได้คล้ายกันในเบราว์เซอร์ต่างๆ

หรือจะเขียนเวอร์ชันที่ซับซ้อนน้อยกว่านี้ในไม่กี่บรรทัดก็ได้ โดยใช้เฉพาะ setTimeout ที่ห่อหุ้มใน Promise เป็นตัวสำรองหาก scheduler.yield() ไม่พร้อมใช้งาน

function yieldToMain () {
  if (globalThis.scheduler?.yield) {
    return scheduler.yield();
  }

  // Fall back to yielding with setTimeout.
  return new Promise(resolve => {
    setTimeout(resolve, 0);
  });
}

แม้ว่าเบราว์เซอร์ที่ไม่รองรับ scheduler.yield() จะไม่ได้รับการเล่นต่อเนื่องที่มีการจัดลำดับความสำคัญ แต่เบราว์เซอร์จะยังคงให้ผลลัพธ์เพื่อให้เบราว์เซอร์ตอบสนองได้

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

ซึ่งทำได้ทั้งโดยการตรวจหาฟีเจอร์และการกลับไปรอ Microtask เดียวในบรรทัดเดียวที่สะดวก ดังนี้

// Yield to the main thread if scheduler.yield() is available.
await globalThis.scheduler?.yield?.();

แบ่งงานที่ใช้เวลานานด้วย scheduler.yield()

ข้อดีของการใช้วิธีใดก็ตามในการใช้ scheduler.yield() คือคุณสามารถawait ในฟังก์ชัน async ใดก็ได้

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

async function runJobs(jobQueue) {
  for (const job of jobQueue) {
    // Run the job:
    job();

    // Yield to the main thread:
    await yieldToMain();
  }
}

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

อย่างไรก็ตาม การใช้การเพิ่มประสิทธิภาพแบบนี้ไม่คุ้มค่า scheduler.yield() รวดเร็วและมีประสิทธิภาพ แต่ก็มีค่าใช้จ่ายบางอย่าง หากงานบางอย่างใน jobQueue สั้นมาก ค่าใช้จ่ายเพิ่มเติมอาจทำให้ใช้เวลาในการสร้างและกลับมาทำงานมากกว่าการทำงานจริง

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

async function runJobs(jobQueue, deadline=50) {
  let lastYield = performance.now();

  for (const job of jobQueue) {
    // Run the job:
    job();

    // If it's been longer than the deadline, yield to the main thread:
    if (performance.now() - lastYield > deadline) {
      await yieldToMain();
      lastYield = performance.now();
    }
  }
}

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

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

อย่าใช้ isInputPending()

Browser Support

  • Chrome: 87.
  • Edge: 87.
  • Firefox: not supported.
  • Safari: not supported.

Source

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

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

อย่างไรก็ตาม ตั้งแต่เปิดตัว API ดังกล่าว ความเข้าใจของเราเกี่ยวกับการเพิ่มประสิทธิภาพรายได้ก็เพิ่มขึ้น โดยเฉพาะอย่างยิ่งเมื่อมีการเปิดตัว INP เราไม่แนะนำให้ใช้ API นี้อีกต่อไป และขอแนะนำให้ใช้ yield ไม่ว่าจะมีอินพุตที่รอดำเนินการหรือไม่ก็ตามด้วยเหตุผลหลายประการดังนี้

  • isInputPending() อาจแสดงผล false อย่างไม่ถูกต้องแม้ว่าผู้ใช้จะโต้ตอบในบางสถานการณ์ก็ตาม
  • อินพุตไม่ใช่กรณีเดียวที่งานควรให้ผลลัพธ์ ภาพเคลื่อนไหวและการอัปเดตอินเทอร์เฟซผู้ใช้แบบปกติอื่นๆ มีความสำคัญไม่แพ้กันในการมอบหน้าเว็บที่ตอบสนอง
  • ต่อมาเราได้เปิดตัว API การเพิ่มประสิทธิภาพที่ครอบคลุมมากขึ้น ซึ่งช่วยแก้ปัญหาข้อกังวลเกี่ยวกับการเพิ่มประสิทธิภาพ เช่น scheduler.postTask() และ scheduler.yield()

บทสรุป

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

  • หลีกทางให้ชุดข้อความหลักสำหรับงานที่สำคัญซึ่งผู้ใช้ต้องเห็น
  • ใช้ scheduler.yield() (พร้อมการสำรองข้อมูลข้ามเบราว์เซอร์) เพื่อให้ได้ผลตอบแทนตามหลักสรีรศาสตร์และรับการดำเนินการต่อที่มีลำดับความสำคัญสูง
  • สุดท้ายนี้ ทำงานในฟังก์ชันให้น้อยที่สุด

ดูข้อมูลเพิ่มเติมเกี่ยวกับ scheduler.yield(), scheduler.postTask() ที่เกี่ยวข้องกับการตั้งเวลางานอย่างชัดเจน และการจัดลําดับความสําคัญของงานได้ที่เอกสารประกอบ API การตั้งเวลางานที่มีลําดับความสําคัญ

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

ขอขอบคุณเป็นพิเศษสำหรับPhilip Walton ที่ตรวจสอบทางเทคนิคของคู่มือนี้

ภาพขนาดย่อจาก Unsplash โดยได้รับความอนุเคราะห์จาก Amirali Mirhashemian