เผยแพร่: 6 มีนาคม 2025
หน้าเว็บจะรู้สึกว่าทำงานช้าและไม่ตอบสนองเมื่องานที่ใช้เวลานานทำให้เธรดหลักทำงานอยู่ตลอดเวลา ซึ่งทำให้เธรดหลักไม่สามารถทำงานสำคัญอื่นๆ ได้ เช่น การตอบสนองต่อข้อมูลจากผู้ใช้ ด้วยเหตุนี้ แม้แต่ตัวควบคุมแบบฟอร์มในตัวก็อาจดูเหมือนใช้งานไม่ได้สำหรับผู้ใช้ ซึ่งทำให้รู้สึกเหมือนหน้าเว็บค้างอยู่ และไม่ต้องพูดถึงคอมโพเนนต์ที่กำหนดเองที่ซับซ้อนกว่า
scheduler.yield()
เป็นวิธีในการยอมให้เทรดหลักทำงาน ซึ่งจะช่วยให้เบราว์เซอร์เรียกใช้งานที่มีลำดับความสำคัญสูงที่รอดำเนินการได้ จากนั้นจึงดำเนินการต่อจากจุดที่หยุดไว้ วิธีนี้จะช่วยให้หน้าเว็บตอบสนองได้ดีขึ้น และช่วยปรับปรุงการโต้ตอบกับ Next Paint (INP)
scheduler.yield
มี API ที่ใช้งานง่ายซึ่งทําหน้าที่ตามที่ระบุไว้ทุกประการ นั่นคือการเรียกใช้ฟังก์ชันที่เรียกใช้ในหยุดชั่วคราวที่นิพจน์ await scheduler.yield()
และส่งต่อให้เทรดหลักเพื่อแบ่งงาน ระบบจะกำหนดเวลาการดำเนินการฟังก์ชันที่เหลือ ซึ่งเรียกว่าการดำเนินการต่อของฟังก์ชัน ให้ทำงานในงาน Event Loop ใหม่
async function respondToUserClick() {
giveImmediateFeedback();
await scheduler.yield(); // Yield to the main thread.
slowerComputation();
}
ประโยชน์ที่เฉพาะเจาะจงของ scheduler.yield
คือการกำหนดเวลาให้ดำเนินการต่อหลังจากที่ได้ผลลัพธ์ก่อนที่จะเรียกใช้ทาคล้ายกันอื่นๆ ที่หน้าเว็บจัดคิวไว้ โดยจะให้ความสำคัญกับการทำงานที่ค้างอยู่มากกว่าการเริ่มงานใหม่
นอกจากนี้ คุณยังใช้ฟังก์ชันอย่าง setTimeout
หรือ scheduler.postTask
เพื่อแบ่งงานได้ด้วย แต่โดยปกติแล้วฟังก์ชันเหล่านี้จะทำงานหลังจากงานใหม่ที่อยู่ในคิวอยู่แล้ว ซึ่งอาจทำให้เกิดความล่าช้าเป็นเวลานานระหว่างการส่งต่อให้เทรดหลักกับงานที่ทำเสร็จ
การดำเนินการต่อที่จัดลำดับความสำคัญหลังจากส่งผล
scheduler.yield
เป็นส่วนหนึ่งของ Prioritized Task Scheduling API ในฐานะนักพัฒนาเว็บ โดยปกติแล้วเราจะไม่พูดถึงลำดับที่ Event Loop เรียกใช้ Task ในแง่ของลำดับความสำคัญที่ชัดเจน แต่ลำดับความสำคัญที่เกี่ยวข้องจะมีอยู่เสมอ เช่น requestIdleCallback
Callback ที่ทำงานหลังจาก Callback ที่อยู่ในคิวsetTimeout
หรือ Listener ของเหตุการณ์อินพุตที่ทริกเกอร์มักจะทำงานก่อน Task ที่อยู่ในคิวด้วย setTimeout(callback, 0)
การจัดกำหนดการงานที่มีลำดับความสำคัญจะช่วยให้การดำเนินการนี้ชัดเจนยิ่งขึ้น ทำให้ทราบได้ง่ายขึ้นว่างานใดจะทำงานก่อนงานอื่น และช่วยให้ปรับลำดับความสำคัญเพื่อเปลี่ยนลำดับการดำเนินการได้หากจำเป็น
ดังที่กล่าวไว้ การดำเนินการฟังก์ชันต่อหลังจากส่งผลด้วย scheduler.yield()
จะได้รับลำดับความสำคัญสูงกว่าการเริ่มงานอื่นๆ แนวคิดที่ใช้เป็นแนวทางคือการทำงานที่ต่อเนื่องควรทำงานก่อนที่จะไปยังงานอื่นๆ หากงานเป็นโค้ดที่ทำงานได้ดีซึ่งให้ผลลัพธ์เป็นระยะๆ เพื่อให้เบราว์เซอร์ทำสิ่งสำคัญอื่นๆ ได้ (เช่น ตอบสนองต่อข้อมูลจากผู้ใช้) ก็ไม่ควรลงโทษด้วยการจัดลำดับความสำคัญหลังจากงานอื่นๆ ที่คล้ายกัน
ตัวอย่างเช่น ฟังก์ชัน 2 รายการที่จัดคิวไว้เพื่อเรียกใช้ในงานต่างๆ โดยใช้ setTimeout
setTimeout(myJob);
setTimeout(someoneElsesJob);
ในกรณีนี้ setTimeout
การเรียกใช้ทั้ง 2 รายการอยู่ติดกัน แต่ในหน้าเว็บจริง การเรียกใช้ดังกล่าวอาจอยู่ในตำแหน่งที่แตกต่างกันโดยสิ้นเชิง เช่น สคริปต์ของบุคคลที่หนึ่งและสคริปต์ของบุคคลที่สามที่ตั้งค่าการทำงานแยกกัน หรืออาจเป็น 2 งานจากคอมโพเนนต์แยกกันที่ทริกเกอร์ในตัวกำหนดเวลาระดับลึกของเฟรมเวิร์ก
ซึ่งใน DevTools จะมีลักษณะดังนี้
myJob
จะได้รับการแจ้งว่าเป็นงานที่ใช้เวลานาน ซึ่งจะบล็อกไม่ให้เบราว์เซอร์ทำอย่างอื่นขณะที่ทำงานอยู่ หากเป็นสคริปต์ของบุคคลที่หนึ่ง เราจะแบ่งออกเป็นส่วนๆ ได้ดังนี้
function myJob() {
// Run part 1.
myJobPart1();
// Yield with setTimeout() to break up long task, then run part2.
setTimeout(myJobPart2, 0);
}
เนื่องจากมีการกำหนดเวลาให้ myJobPart2
ทำงานร่วมกับ setTimeout
ภายใน myJob
แต่การกำหนดเวลานั้นจะทำงานหลังจากที่มีการกำหนดเวลา someoneElsesJob
ไปแล้ว การดำเนินการจึงเป็นดังนี้
เราได้แบ่งงานด้วย setTimeout
เพื่อให้เบราว์เซอร์ตอบสนองได้ในระหว่างmyJob
แต่ตอนนี้ส่วนที่ 2 ของ myJob
จะทำงานหลังจากที่ someoneElsesJob
เสร็จสิ้นแล้วเท่านั้น
ในบางกรณีอาจใช้ได้ แต่โดยปกติแล้ววิธีนี้ไม่ใช่แนวทางที่ดีที่สุด myJob
จะส่งต่อการควบคุมไปยังเธรดหลักเพื่อให้แน่ใจว่าหน้าเว็บจะยังคงตอบสนองต่อข้อมูลจากผู้ใช้ได้ ไม่ใช่การยกเลิกเธรดหลักทั้งหมด ในกรณีที่ someoneElsesJob
ทำงานช้าเป็นพิเศษ หรือมีการกำหนดเวลางานอื่นๆ นอกเหนือจาก someoneElsesJob
ไว้ด้วย อาจต้องใช้เวลานานกว่าที่ครึ่งหลังของ myJob
จะทำงาน ซึ่งอาจไม่ใช่สิ่งที่นักพัฒนาแอปตั้งใจไว้เมื่อเพิ่ม setTimeout
ลงใน myJob
ป้อน scheduler.yield()
ซึ่งจะทำให้การดำเนินการต่อของฟังก์ชันใดก็ตามที่เรียกใช้ฟังก์ชันนี้อยู่ในคิวที่มีลำดับความสำคัญสูงกว่าเล็กน้อยเมื่อเทียบกับการเริ่มงานอื่นๆ ที่คล้ายกัน หากมีการเปลี่ยน myJob
ให้ใช้
async function myJob() {
// Run part 1.
myJobPart1();
// Yield with scheduler.yield() to break up long task, then run part2.
await scheduler.yield();
myJobPart2();
}
ตอนนี้การดำเนินการจะมีลักษณะดังนี้
เบราว์เซอร์ยังคงมีโอกาสตอบสนอง แต่ตอนนี้ระบบจะจัดลำดับความสำคัญของการดำเนินการต่อของงาน myJob
มากกว่าการเริ่มงานใหม่ someoneElsesJob
ดังนั้น myJob
จะเสร็จสมบูรณ์ก่อนที่ someoneElsesJob
จะเริ่ม ซึ่งใกล้เคียงกับความคาดหวังในการส่งต่อเทรดหลักเพื่อรักษาการตอบสนองมากกว่าการละทิ้งเทรดหลักไปโดยสิ้นเชิง
การรับช่วงลำดับความสำคัญ
scheduler.yield()
ทำงานร่วมกับลำดับความสำคัญที่ชัดเจนซึ่งมีอยู่ใน scheduler.postTask()
ได้เป็นอย่างดี ซึ่งเป็นส่วนหนึ่งของ API การจัดกำหนดการงานที่มีลำดับความสำคัญที่ใหญ่ขึ้น หากไม่ได้ตั้งค่าลำดับความสำคัญอย่างชัดเจน scheduler.yield()
ภายในแฮนเดิลการเรียกกลับ scheduler.postTask()
จะทำงานเหมือนกับตัวอย่างก่อนหน้านี้โดยพื้นฐาน
อย่างไรก็ตาม หากตั้งค่าลำดับความสำคัญ เช่น ใช้'background'
ลำดับความสำคัญต่ำ
async function lowPriorityJob() {
part1();
await scheduler.yield();
part2();
}
scheduler.postTask(lowPriorityJob, {priority: 'background'});
ระบบจะกำหนดเวลาการดำเนินการต่อโดยมีลำดับความสำคัญสูงกว่างาน 'background'
อื่นๆ เพื่อให้คุณได้รับการดำเนินการต่อที่มีลำดับความสำคัญตามที่คาดไว้ก่อนงาน 'background'
ที่รอดำเนินการ แต่ก็ยังมีลำดับความสำคัญต่ำกว่างานอื่นๆ ที่เป็นค่าเริ่มต้นหรือมีลำดับความสำคัญสูง โดยยังคงเป็นงาน 'background'
ซึ่งหมายความว่าหากคุณกำหนดเวลาให้งานที่มีลำดับความสำคัญต่ำด้วย 'background'
scheduler.postTask()
(หรือด้วย requestIdleCallback
) การดำเนินการต่อหลังจาก scheduler.yield()
ภายในจะรอจนกว่างานอื่นๆ ส่วนใหญ่จะเสร็จสมบูรณ์และเทรดหลักจะไม่ได้ใช้งานเพื่อเรียกใช้ ซึ่งเป็นสิ่งที่คุณต้องการจากการหยุดชั่วคราวในงานที่มีลำดับความสำคัญต่ำ
วิธีใช้ API
ปัจจุบัน scheduler.yield()
ใช้ได้เฉพาะในเบราว์เซอร์แบบ Chromium ดังนั้นหากต้องการใช้ คุณจะต้องตรวจหาฟีเจอร์และย้อนกลับไปใช้วิธีที่ 2 ในการส่งผลลัพธ์สำหรับเบราว์เซอร์อื่นๆ
scheduler-polyfill
เป็น Polyfill ขนาดเล็กสำหรับ scheduler.postTask
และ scheduler.yield
ซึ่งใช้ชุดเมธอดภายในเพื่อจำลองความสามารถส่วนใหญ่ของ Scheduling API ในเบราว์เซอร์อื่นๆ (แม้ว่าจะไม่รองรับการรับค่าลำดับความสำคัญของ scheduler.yield()
)
สำหรับผู้ที่ต้องการหลีกเลี่ยง Polyfill วิธีหนึ่งคือการใช้ setTimeout()
และยอมรับการสูญเสียการดำเนินการต่อที่มีลำดับความสำคัญ หรือแม้แต่การไม่ใช้ในเบราว์เซอร์ที่ไม่รองรับหากยอมรับไม่ได้ ดูscheduler.yield()
เอกสารประกอบในเพิ่มประสิทธิภาพงานที่ใช้เวลานานเพื่อดูข้อมูลเพิ่มเติม
นอกจากนี้ยังใช้wicg-task-scheduling
ประเภทเพื่อรับการตรวจสอบประเภทและการรองรับ IDE ได้ด้วย หากคุณตรวจหาฟีเจอร์ scheduler.yield()
และเพิ่มการสำรองด้วยตนเอง
ดูข้อมูลเพิ่มเติม
ดูข้อมูลเพิ่มเติมเกี่ยวกับ API และวิธีที่ API โต้ตอบกับลำดับความสำคัญของงานและ scheduler.postTask()
ได้ในเอกสาร scheduler.yield()
และการจัดกำหนดเวลางานที่มีลำดับความสำคัญใน MDN
ดูข้อมูลเพิ่มเติมเกี่ยวกับงานที่ใช้เวลานาน ผลกระทบต่อประสบการณ์ของผู้ใช้ และสิ่งที่ควรทำได้ที่หัวข้อการเพิ่มประสิทธิภาพงานที่ใช้เวลานาน