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

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

เผยแพร่: 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 มิลลิวินาทีจะเรียกว่าระยะเวลาการบล็อกของงาน

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

ในกรณีนี้ saveSettings() จะทริกเกอร์เมื่อผู้ใช้คลิก และเนื่องจากเบราว์เซอร์ไม่สามารถแสดงการตอบสนองได้จนกว่าฟังก์ชันทั้งหมดจะทำงานเสร็จสิ้น ผลลัพธ์ของ Long Task นี้จึงทำให้ 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 Technology Preview: supported.
  • 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 จะจัดการการเปลี่ยนไปใช้ฟังก์ชันการจัดกำหนดการงานอื่นๆ เพื่อให้ทำงานได้คล้ายกันในเบราว์เซอร์ต่างๆ

หรือจะเขียนเวอร์ชันที่ซับซ้อนน้อยกว่าใน 2-3 บรรทัดโดยใช้เฉพาะ 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() และการจัดลําดับความสําคัญของงานได้ที่เอกสารประกอบของ Prioritized Task Scheduling API

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

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

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