เผยแพร่: 6 มีนาคม 2025
หน้าเว็บจะทำงานช้าและไม่ตอบสนองเมื่องานที่มีระยะเวลานานทำให้เธรดหลักทำงานอยู่ตลอดเวลา ซึ่งทำให้เธรดหลักไม่สามารถทำงานสำคัญอื่นๆ เช่น ตอบสนองต่อข้อมูลจากผู้ใช้ ด้วยเหตุนี้ แม้แต่ตัวควบคุมแบบฟอร์มในตัวอาจดูเหมือนใช้งานไม่ได้สำหรับผู้ใช้ ราวกับว่าหน้าเว็บค้างอยู่ ยังไม่นับคอมโพเนนต์ที่กําหนดเองที่ซับซ้อนมากขึ้น
scheduler.yield()
เป็นวิธียอมให้เทรดหลัก ซึ่งช่วยให้เบราว์เซอร์ทำงานที่รอดําเนินการอยู่ซึ่งมีความสำคัญสูง จากนั้นจะดําเนินการต่อจากที่หยุดไว้ วิธีนี้ช่วยให้หน้าเว็บตอบสนองได้ดีขึ้น และช่วยปรับปรุงการโต้ตอบกับ Next Paint (INP) ด้วย
scheduler.yield
มี API ที่ใช้งานง่ายซึ่งทํางานตามที่ระบุไว้ทุกประการ นั่นคือการดําเนินการของฟังก์ชันที่เรียกให้หยุดชั่วคราวที่นิพจน์ await scheduler.yield()
และส่งค่าไปยังเธรดหลักเพื่อแบ่งงาน ระบบจะกำหนดเวลาให้การดำเนินการของฟังก์ชันที่เหลือซึ่งเรียกว่าการดำเนินการต่อของฟังก์ชันทำงานในภารกิจลูปเหตุการณ์ใหม่
async function respondToUserClick() {
giveImmediateFeedback();
await scheduler.yield(); // Yield to the main thread.
slowerComputation();
}
ประโยชน์ที่เฉพาะเจาะจงของ scheduler.yield
คือการดำเนินการต่อหลังจากกำหนดเวลาให้ผลตอบแทนทำงานก่อนการเรียกใช้งานอื่นๆ ที่คล้ายกันซึ่งหน้าเว็บจัดคิวไว้ โดยให้ความสำคัญกับการดําเนินการงานต่อมากกว่าการเริ่มงานใหม่
คุณยังใช้ฟังก์ชันอย่าง setTimeout
หรือ scheduler.postTask
เพื่อแบ่งงานออกเป็นส่วนๆ ได้ด้วย แต่โดยทั่วไปการดําเนินการต่อเหล่านั้นจะทํางานหลังจากงานใหม่ที่อยู่ในคิวแล้ว ซึ่งอาจทําให้เกิดความล่าช้าเป็นเวลานานระหว่างการยอมให้ใช้เธรดหลักและดําเนินการงานให้เสร็จสมบูรณ์
การดําเนินการต่อที่มีลําดับความสําคัญหลังจากให้สิทธิ์
scheduler.yield
เป็นส่วนหนึ่งของ Prioritized Task Scheduling API ในฐานะนักพัฒนาเว็บ เรามักจะไม่พูดถึงลําดับที่ลูปเหตุการณ์เรียกใช้งานในแง่ของลําดับความสําคัญที่ชัดเจน แต่ลําดับความสําคัญแบบสัมพัทธ์จะยังคงอยู่เสมอ เช่น ฟังก์ชันการเรียกกลับ requestIdleCallback
ที่ทํางานหลังจากฟังก์ชันการเรียกกลับ setTimeout
ที่รอดําเนินการ หรือโปรแกรมรับฟังเหตุการณ์อินพุตที่ทริกเกอร์ซึ่งมักจะทํางานก่อนงานที่รอดําเนินการด้วย setTimeout(callback, 0)
การกำหนดเวลางานตามลําดับความสําคัญช่วยให้เห็นภาพได้ชัดเจนขึ้นว่างานใดจะทํางานก่อนงานอื่น และช่วยให้ปรับลําดับความสําคัญเพื่อเปลี่ยนลําดับการดําเนินการได้ หากจําเป็น
ดังที่ได้กล่าวไว้ การดำเนินการฟังก์ชันต่อหลังจากแสดงผลด้วย scheduler.yield()
จะมีลำดับความสำคัญสูงกว่าการเริ่มงานอื่นๆ แนวคิดหลักคือการดำเนินการต่อของงานหนึ่งๆ ควรทำงานก่อน ก่อนที่จะไปยังงานอื่นๆ หากเป็นงานที่ทำงานอย่างถูกต้องซึ่งหยุดทำงานเป็นระยะเพื่อให้เบราว์เซอร์ทำสิ่งสําคัญอื่นๆ ได้ (เช่น ตอบสนองต่ออินพุตของผู้ใช้) ก็ไม่ควรถูกลงโทษจากการหยุดทำงานโดยการจัดลําดับความสําคัญไว้หลังงานอื่นๆ ที่คล้ายกัน
ตัวอย่างคือ 2 ฟังก์ชันที่จัดคิวให้ทำงานใน 2 งานที่แตกต่างกันโดยใช้ setTimeout
setTimeout(myJob);
setTimeout(someoneElsesJob);
ในกรณีนี้ การเรียกใช้ setTimeout
2 รายการอยู่ติดกัน แต่ในหน้าจริง การเรียกใช้อาจอยู่คนละตำแหน่งกันโดยสิ้นเชิง เช่น สคริปต์ของบุคคลที่หนึ่งและสคริปต์ของบุคคลที่สามที่กําหนดค่าการทํางานให้ทํางานอย่างอิสระ หรืออาจเป็น 2 งานจากคอมโพเนนต์แยกต่างหากที่ทริกเกอร์ลึกลงไปในเครื่องมือกําหนดเวลาของเฟรมเวิร์ก
การทำงานดังกล่าวอาจมีลักษณะดังนี้ในเครื่องมือสำหรับนักพัฒนาซอฟต์แวร์
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
แต่ตอนนี้ส่วนที่สองของ myJob
จะทำงานหลังจากที่ someoneElsesJob
เสร็จแล้วเท่านั้น
ในบางกรณี การดำเนินการดังกล่าวอาจไม่มีปัญหา แต่โดยทั่วไปแล้วไม่เหมาะ myJob
ยอมให้เธรดหลักเพื่อให้แน่ใจว่าหน้าเว็บจะยังคงตอบสนองต่ออินพุตของผู้ใช้ ไม่ใช่การยอมแพ้เธรดหลักโดยสิ้นเชิง ในกรณีที่ someoneElsesJob
ทำงานช้ามาก หรือมีการกำหนดเวลางานอื่นๆ นอกเหนือจาก someoneElsesJob
ไว้ด้วย ก็อาจใช้เวลานานกว่าที่ระบบจะเรียกใช้ someoneElsesJob
ส่วนครึ่งหลังmyJob
ซึ่งอาจไม่ใช่ความตั้งใจของนักพัฒนาแอปเมื่อเพิ่ม setTimeout
ไปยัง myJob
ป้อน scheduler.yield()
ซึ่งจะจัดคิวการดําเนินการต่อของฟังก์ชันที่เรียกใช้ไว้ในคิวที่มีลําดับความสําคัญสูงกว่าการเริ่มงานอื่นๆ ที่คล้ายกันเล็กน้อย หากเปลี่ยน myJob
ให้ใช้ 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()
เป็นส่วนหนึ่งของ Prioritized Task Scheduling API ที่ใหญ่กว่า จึงทำงานร่วมกับลำดับความสำคัญแบบชัดเจนที่มีอยู่ใน scheduler.postTask()
ได้เป็นอย่างดี หากไม่ได้ตั้งค่าลําดับความสําคัญไว้อย่างชัดเจน 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 คุณจึงต้องใช้การตรวจหาฟีเจอร์และเปลี่ยนไปใช้วิธีรองในการให้ผลลัพธ์สำหรับเบราว์เซอร์อื่นๆ
scheduler-polyfill
เป็น polyfill ขนาดเล็กสําหรับ scheduler.postTask
และ scheduler.yield
ที่ใช้วิธีการต่างๆ ร่วมกันภายในเพื่อจําลองความสามารถส่วนใหญ่ของ API การจัดตารางเวลาในเบราว์เซอร์อื่นๆ (แต่ยังไม่รองรับการสืบทอดลําดับความสําคัญของ scheduler.yield()
)
สําหรับผู้ที่พยายามหลีกเลี่ยง polyfill วิธีหนึ่งคือใช้ setTimeout()
และยอมรับการสูญเสียการดําเนินการต่อที่มีลําดับความสําคัญ หรือแม้กระทั่งไม่ใช้ setTimeout()
ในเบราว์เซอร์ที่ไม่รองรับหากยอมรับไม่ได้ ดูข้อมูลเพิ่มเติมในเอกสารประกอบของ scheduler.yield()
ใน Optimize งานที่ใช้เวลานาน
นอกจากนี้ คุณยังใช้ประเภท wicg-task-scheduling
เพื่อรับการตรวจสอบประเภทและการสนับสนุน IDE ได้หากกำลังตรวจหาฟีเจอร์ scheduler.yield()
และเพิ่มทางเลือกสำรองด้วยตนเอง
ดูข้อมูลเพิ่มเติม
ดูข้อมูลเพิ่มเติมเกี่ยวกับ API และวิธีที่ API โต้ตอบกับลําดับความสําคัญของงานและ scheduler.postTask()
ได้ที่เอกสาร scheduler.yield()
และการจัดตารางงานตามลําดับความสําคัญใน MDN
อ่านข้อมูลเพิ่มเติมเกี่ยวกับงานที่ใช้เวลานาน ผลกระทบต่อประสบการณ์ของผู้ใช้ และวิธีจัดการกับงานที่ใช้เวลานานได้ที่บทความการเพิ่มประสิทธิภาพงานที่ใช้เวลานาน