การสร้างเว็บไซต์ที่ตอบสนองต่อข้อมูลจากผู้ใช้อย่างรวดเร็วเป็นหนึ่งในเรื่องที่ท้าทายที่สุดของประสิทธิภาพเว็บ ซึ่งก็คือทีม Chrome ทำงานอย่างหนักเพื่อช่วยให้นักพัฒนาเว็บตอบสนองความต้องการได้ ในปีนี้ เราได้ประกาศว่าเมตริก Interaction to Next Paint (INP) จะเปลี่ยนจากสถานะทดลองเป็นรอดำเนินการ โดยตอนนี้ฟีเจอร์ดังกล่าวจะแทนที่ First Input Delay (FID) เป็น Core Web Vitals ในเดือนมีนาคม 2024 แล้ว
ด้วยความพยายามอย่างต่อเนื่องที่จะส่งมอบ API ใหม่ๆ ที่จะช่วยให้นักพัฒนาเว็บสร้างเว็บไซต์ได้อย่างรวดเร็วเท่าที่จะเป็นไปได้ ขณะนี้ทีม Chrome ได้ใช้ช่วงทดลองใช้จากต้นทางสำหรับ scheduler.yield
โดยเริ่มตั้งแต่ Chrome เวอร์ชัน 115 scheduler.yield
เป็นส่วนเพิ่มใหม่ที่เสนอใน API เครื่องจัดตารางเวลาที่ให้ทั้งวิธีที่สะดวกและดีกว่าเดิมในการคืนการควบคุมกลับไปยังเทรดหลักมากกว่าวิธีการเดิมที่เคยใช้
เมื่อให้ผลตอบแทน
JavaScript ใช้โมเดลการเรียกใช้ให้เสร็จสมบูรณ์เพื่อจัดการกับงาน ซึ่งหมายความว่าเมื่องานทำงานในเทรดหลัก งานนั้นจะทํางานนานเท่าที่จำเป็นเพื่อให้เสร็จสมบูรณ์ เมื่อทำงานเสร็จแล้ว ระบบจะปฏิเสธการควบคุมกลับไปยังเทรดหลัก ซึ่งจะช่วยให้เทรดหลักประมวลผลงานถัดไปในคิวได้
นอกจากเหตุการณ์สุดโต่งที่งานใดงานหนึ่งยังไม่จบ เช่น การทำงานวนซ้ำที่ไม่สิ้นสุด ก็ถือเป็นแง่มุมที่หลีกเลี่ยงไม่ได้ในตรรกะการจัดตารางเวลางานของ JavaScript เรื่องดังกล่าวจะเกิดขึ้นอยู่แล้ว แต่ขึ้นอยู่กับเวลาและเร็วกว่าย่อมดีกว่า เมื่อต้องใช้เวลานานเกินไปในการเรียกใช้ (มากกว่า 50 มิลลิวินาที) งานเหล่านั้นจะถือว่าเป็นงานที่ใช้เวลานาน
งานที่ใช้เวลานานเป็นสาเหตุที่ทำให้หน้าเว็บตอบสนองได้ไม่ดี เนื่องจากทำให้เบราว์เซอร์ไม่สามารถตอบสนองต่อข้อมูลจากผู้ใช้ได้ ยิ่งทำงานที่นานและนานเท่าไร ผู้ใช้ก็ยิ่งมีแนวโน้มที่จะรู้สึกว่าหน้าเว็บเอื่อย หรืออาจรู้สึกว่าหน้าแตกไปแล้ว
อย่างไรก็ตาม การที่โค้ดเริ่มทำงานในเบราว์เซอร์ไม่ได้หมายความว่าคุณจะต้องรอจนกว่างานนั้นเสร็จก่อนจะส่งตัวควบคุมกลับไปที่เทรดหลัก คุณปรับปรุงการตอบสนองต่อข้อมูลจากผู้ใช้ในหน้าเว็บได้โดยการส่งงานอย่างชัดเจน ซึ่งจะแบ่งงานให้เสร็จในโอกาสถัดไป วิธีนี้จะช่วยให้งานอื่นๆ ใช้เวลาในเทรดหลักได้เร็วกว่ากรณีที่ต้องรอให้งานยาวๆ เสร็จ
เมื่อคุณตอบกลับอย่างชัดเจน จะเป็นการบอกเบราว์เซอร์ว่า "สวัสดี ฉันเข้าใจว่างานที่ฉันจะทำอาจใช้เวลานาน และฉันไม่ต้องการให้คุณทำงานนั้นทั้งหมดก่อนที่จะตอบสนองต่อข้อมูลจากผู้ใช้หรืองานอื่นๆ ที่อาจสำคัญเช่นกัน" เครื่องมือนี้เป็นเครื่องมือที่มีคุณค่าในกล่องเครื่องมือของนักพัฒนาซอฟต์แวร์ที่ช่วยเพิ่มประสิทธิภาพให้ประสบการณ์ของผู้ใช้ได้อย่างมาก
ปัญหาของกลยุทธ์ผลตอบแทนในปัจจุบัน
วิธีการทั่วไปในการแสดงผล ใช้ setTimeout
โดยมีค่าระยะหมดเวลาเป็น 0
วิธีนี้ได้ผลเนื่องจากการเรียกกลับที่ส่งไปยัง setTimeout
จะย้ายงานที่เหลือไปยังงานแยกต่างหากซึ่งจะต้องอยู่ในคิวเพื่อดำเนินการในลำดับต่อมา แทนที่จะรอให้เบราว์เซอร์ทำงานเอง คุณจะพูดว่า "มาแบ่งงานชิ้นใหญ่นี้ออกเป็นส่วนเล็กๆ กันเถอะ"
อย่างไรก็ตาม ผลตอบแทนพร้อมกับ setTimeout
อาจทำให้เกิดผลข้างเคียงที่ไม่พึงประสงค์ นั่นคือ งานที่เกิดขึ้นหลังจากจุดผลตอบแทนจะกลับไปอยู่ด้านหลังของคิวงาน งานที่กำหนดเวลาไว้ตามการโต้ตอบของผู้ใช้จะยังคงไปอยู่ลำดับต้นๆ ของคิวตามที่ควรจะเป็น แต่งานที่เหลือที่คุณต้องการทำหลังจากอนุญาตอย่างชัดเจนอาจลงเอยด้วยความล่าช้าเนื่องจากงานอื่นๆ จากแหล่งที่มาที่เป็นคู่แข่งซึ่งจัดคิวรออยู่ข้างหน้า
หากต้องการดูสถานการณ์จริง ให้ลองใช้การสาธิต Glitch นี้ หรือทดลองใช้ในเวอร์ชันที่ฝังอยู่ด้านล่าง การสาธิตประกอบด้วยปุ่ม 2-3 ปุ่มที่คุณสามารถคลิกได้ และช่องใต้ปุ่มนั้นซึ่งจะบันทึกเมื่องานถูกเรียกใช้งาน เมื่อมาที่หน้านี้แล้ว ให้ดำเนินการดังต่อไปนี้
- คลิกปุ่มด้านบนที่มีป้ายกำกับว่าเรียกใช้งานเป็นระยะ ซึ่งจะกำหนดเวลาให้งานการบล็อกทำงานบ่อยๆ เมื่อคลิกปุ่มนี้ บันทึกของงานจะแสดงข้อความต่างๆ ที่ระบุว่าเรียกใช้การบล็อกด้วย
setInterval
- ถัดไป ให้คลิกปุ่ม เรียกใช้การวนซ้ำ ซึ่งให้ผลลัพธ์
setTimeout
ในการทำซ้ำแต่ละครั้ง
โดยจะเห็นว่าช่องที่ด้านล่างของการสาธิตจะมีข้อความแบบนี้
Processing loop item 1
Processing loop item 2
Ran blocking task via setInterval
Processing loop item 3
Ran blocking task via setInterval
Processing loop item 4
Ran blocking task via setInterval
Processing loop item 5
Ran blocking task via setInterval
Ran blocking task via setInterval
เอาต์พุตนี้แสดงพฤติกรรม "สิ้นสุดคิวงาน" ที่เกิดขึ้นเมื่อส่งคืนด้วย setTimeout
ลูปที่เรียกใช้การประมวลผล 5 รายการ และผลตอบแทนด้วย setTimeout
หลังจากประมวลผลแต่ละรายการ
ภาพนี้แสดงให้เห็นถึงปัญหาที่พบได้ทั่วไปบนเว็บ การบันทึกฟังก์ชันตัวจับเวลาที่จะทำงานในบางช่วงเวลานั้นไม่ใช่เรื่องผิดปกติสำหรับสคริปต์ (โดยเฉพาะสคริปต์ของบุคคลที่สาม) ลักษณะการทำงาน "สิ้นสุดคิวงาน" ที่มาพร้อมกับการแสดงผลพร้อมกับ setTimeout
หมายความว่างานจากแหล่งที่มาอื่นๆ อาจอยู่ในคิวก่อนงานที่เหลือที่ลูปต้องทำหลังจากส่ง
ผลลัพธ์นี้อาจจะเป็นหรือไม่เป็นที่น่าพอใจก็ได้ ทั้งนี้ขึ้นอยู่กับแอปพลิเคชันของคุณ แต่ในหลายกรณี พฤติกรรมเช่นนี้อาจทำให้นักพัฒนาแอปรู้สึกลังเลที่จะไม่ควบคุมเทรดหลักอย่างรวดเร็ว ผลตอบแทนเป็นสิ่งที่ดีเนื่องจากการโต้ตอบของผู้ใช้จะมีโอกาสทํางานได้เร็วขึ้น แต่ยังช่วยให้การโต้ตอบอื่นๆ ที่ไม่ใช่ของผู้ใช้มีเวลาในเทรดหลักด้วย นี่เป็นปัญหาจริงๆ แต่ scheduler.yield
ช่วยคุณแก้ปัญหาได้
เข้าไปใน scheduler.yield
scheduler.yield
พร้อมให้ใช้งานหลัง Flag ว่าเป็นฟีเจอร์แพลตฟอร์มบนเว็บเวอร์ชันทดลองมาตั้งแต่เวอร์ชัน 115 ของ Chrome คำถามหนึ่งที่คุณอาจมีอยู่คือ "ทำไมฉันถึงต้องใช้ฟังก์ชันพิเศษเพื่อตอบกลับในที่ setTimeout
มีอยู่แล้ว"
โปรดทราบว่าผลตอบแทนไม่ใช่เป้าหมายการออกแบบของ setTimeout
แต่เป็นผลข้างเคียงที่ดีในการตั้งเวลาการเรียกกลับให้ทำงานในภายหลัง แม้ว่าจะมีการระบุค่าหมดเวลาที่ 0
ก็ตาม อย่างไรก็ตาม สิ่งสำคัญที่ต้องจำไว้คือ ผลตอบแทนด้วย setTimeout
จะส่งงานที่เหลือไปยังย้อนกลับของคิวงาน โดยค่าเริ่มต้น scheduler.yield
จะส่งงานที่เหลือไปยังด้านหน้าของคิว ซึ่งหมายความว่างานที่คุณต้องการทำให้กลับมาทำงานอีกครั้งทันทีหลังจากได้ผลตอบแทนจะไม่ถูกจำกัดการทำงานจากแหล่งที่มาอื่นๆ (ยกเว้นการโต้ตอบของผู้ใช้ที่โดดเด่น)
scheduler.yield
คือฟังก์ชันที่ให้ผลลัพธ์ไปยังเทรดหลักและแสดงผล Promise
เมื่อเรียกใช้ ซึ่งหมายความว่าคุณจะawait
ค่าในฟังก์ชัน async
ได้ ดังนี้
async function yieldy () {
// Do some work...
// ...
// Yield!
await scheduler.yield();
// Do some more work...
// ...
}
หากต้องการดูการทำงานของ scheduler.yield
ให้ทำดังนี้
- นำทางไปยัง
chrome://flags
- เปิดใช้การทดสอบฟีเจอร์แพลตฟอร์มเว็บรุ่นทดลอง คุณอาจต้องรีสตาร์ท Chrome หลังจากดำเนินการนี้
- ไปที่หน้าสาธิตหรือใช้เวอร์ชันที่ฝังอยู่ด้านล่างรายการนี้
- คลิกปุ่มด้านบนที่มีป้ายกำกับว่าเรียกใช้งานเป็นระยะ
- สุดท้าย ให้คลิกปุ่ม "เรียกใช้ลูป" ซึ่งให้ผลลัพธ์กับ
scheduler.yield
ในการทำซ้ำแต่ละครั้ง
ผลลัพธ์ในช่องที่ด้านล่างของหน้าเว็บจะมีลักษณะดังนี้
Processing loop item 1
Processing loop item 2
Processing loop item 3
Processing loop item 4
Processing loop item 5
Ran blocking task via setInterval
Ran blocking task via setInterval
Ran blocking task via setInterval
Ran blocking task via setInterval
Ran blocking task via setInterval
คุณจะเห็นได้ว่าการวนซ้ำแม้จะได้ผลหลังจากการทำซ้ำทุกครั้ง ไม่ได้ส่งงานที่เหลือไปไว้หลังคิว แต่จะดูว่าการวนซ้ำดังกล่าวไปอยู่หน้าคิว ซึ่งต่างจากการสาธิตที่ใช้ setTimeout
ซึ่งถือเป็นความสามารถที่ดีที่สุดทั้ง 2 ประการ กล่าวคือ คุณสามารถปรับปรุงการตอบสนองข้อมูลในเว็บไซต์ให้ดีขึ้นได้ แต่ก็ต้องมั่นใจว่างานที่คุณต้องการทำให้เสร็จหลังจากที่ให้ผลลัพธ์นั้นไม่ล่าช้าด้วย
ลองใช้วิธีนี้เลย
หากคุณสนใจ scheduler.yield
และต้องการลองใช้ คุณสามารถทำได้ 2 วิธีโดยเริ่มจาก Chrome ในเวอร์ชัน 115 ดังนี้
- หากคุณต้องการทดลองใช้
scheduler.yield
ในเครื่อง ให้พิมพ์และป้อนchrome://flags
ในแถบที่อยู่ของ Chrome และเลือกเปิดใช้จากเมนูแบบเลื่อนลงในส่วนฟีเจอร์แพลตฟอร์มเว็บแบบทดลอง การดำเนินการนี้จะทำให้scheduler.yield
(และฟีเจอร์ทดลองอื่นๆ) ใช้ได้เฉพาะในอินสแตนซ์ของ Chrome เท่านั้น - หากต้องการเปิดใช้
scheduler.yield
สำหรับผู้ใช้ Chromium จริงในต้นทางที่เข้าถึงได้แบบสาธารณะ คุณจะต้องลงชื่อสมัครใช้ช่วงทดลองใช้scheduler.yield
จากต้นทาง ซึ่งช่วยให้คุณทดสอบฟีเจอร์ที่เสนอไว้ในระยะเวลาที่กำหนดได้อย่างปลอดภัยและให้ข้อมูลเชิงลึกที่เป็นประโยชน์แก่ทีม Chrome เกี่ยวกับวิธีการใช้ฟีเจอร์ดังกล่าวในการใช้งานจริง อ่านคู่มือนี้เพื่อดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีการทำงานของช่วงทดลองใช้จากต้นทาง
วิธีที่คุณใช้ scheduler.yield
(ในขณะที่ยังรองรับเบราว์เซอร์ที่ไม่ได้ใช้) จะขึ้นอยู่กับเป้าหมายของคุณ คุณใช้ polyfill อย่างเป็นทางการได้ Polyfill จะมีประโยชน์ในกรณีต่อไปนี้
- คุณใช้
scheduler.postTask
ในแอปพลิเคชันเพื่อตั้งเวลางานอยู่แล้ว - คุณต้องการกำหนดลำดับความสำคัญของงานและผลตอบแทน
- คุณต้องการยกเลิกหรือจัดลำดับความสำคัญของงานผ่านคลาส
TaskController
ที่scheduler.postTask
API มีให้
หากไม่ตรงกับสถานการณ์ของคุณ การใช้ Polyfill ก็อาจไม่เหมาะกับคุณ ในกรณีดังกล่าว คุณสามารถเล่นวิดีโอสำรองของตัวเองได้ 2 วิธี วิธีการแรกจะใช้ scheduler.yield
หากมี แต่จะใช้มูลค่ากลับเป็น setTimeout
หากยังไม่มี
// A function for shimming scheduler.yield and setTimeout:
function yieldToMain () {
// Use scheduler.yield if it exists:
if ('scheduler' in window && 'yield' in scheduler) {
return scheduler.yield();
}
// Fall back to setTimeout:
return new Promise(resolve => {
setTimeout(resolve, 0);
});
}
// Example usage:
async function doWork () {
// Do some work:
// ...
await yieldToMain();
// Do some other work:
// ...
}
วิธีนี้ได้ผล แต่คุณคงเดาได้ว่า เบราว์เซอร์ที่ไม่รองรับ scheduler.yield
จะให้ผลลัพธ์โดยไม่ต้องแสดง "ก่อนคิว" หากนั่นหมายความว่าคุณไม่ต้องผลตอบแทนเลย คุณสามารถลองวิธีอื่นที่ใช้ scheduler.yield
(หากมี) แต่ผลตอบแทนจะไม่ได้เลยหากไม่มี:
// A function for shimming scheduler.yield with no fallback:
function yieldToMain () {
// Use scheduler.yield if it exists:
if ('scheduler' in window && 'yield' in scheduler) {
return scheduler.yield();
}
// Fall back to nothing:
return;
}
// Example usage:
async function doWork () {
// Do some work:
// ...
await yieldToMain();
// Do some other work:
// ...
}
scheduler.yield
เป็นส่วนเพิ่มเติมอันน่าตื่นเต้นของ API เครื่องจัดตารางเวลา ที่หวังว่าจะช่วยให้นักพัฒนาซอฟต์แวร์ปรับปรุงการตอบสนองได้ง่ายกว่ากลยุทธ์ผลตอบแทนในปัจจุบัน หาก scheduler.yield
ดูเหมือนจะเป็น API ที่เป็นประโยชน์สำหรับคุณ โปรดเข้าร่วมในการวิจัยของเราเพื่อช่วยปรับปรุง และแสดงความคิดเห็นว่าควรปรับปรุงอย่างไร
รูปภาพหลักจาก Unsplash โดย Jonathan Allison