การสร้างเว็บไซต์ที่ตอบสนองต่ออินพุตของผู้ใช้อย่างรวดเร็วเป็นหนึ่งในแง่มุมที่ท้าทายที่สุดของประสิทธิภาพเว็บ ซึ่งทีม Chrome พยายามอย่างเต็มที่เพื่อช่วยให้นักพัฒนาเว็บบรรลุเป้าหมายนี้ เมื่อต้นปีนี้ มีการประกาศว่าเมตริกการโต้ตอบกับ Next Paint (INP) จะเปลี่ยนจากสถานะทดลองเป็นสถานะรอดำเนินการ ตอนนี้ INP พร้อมที่จะเข้ามาแทนที่ First Input Delay (FID) เป็น Core Web Vital ในเดือนมีนาคม 2024
ทีม Chrome กำลังดำเนินการการทดลองใช้ต้นทางสำหรับ scheduler.yield
ตั้งแต่ Chrome เวอร์ชัน 115 เป็นต้นไป เพื่อพยายามอย่างต่อเนื่องที่จะส่ง API ใหม่ๆ ที่ช่วยนักพัฒนาเว็บสร้างเว็บไซต์ให้รวดเร็วที่สุด scheduler.yield
เป็นการเพิ่มใหม่ใน scheduler 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 เป็นฟีเจอร์แพลตฟอร์มเว็บเวอร์ชันทดลองตั้งแต่ Chrome เวอร์ชัน 115 คำถามที่คุณอาจสงสัยคือ "ทำไมฉันต้องใช้ฟังก์ชันพิเศษเพื่อแสดงผลลัพธ์ในเมื่อ 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 อย่างเป็นทางการ โพลีฟิลล์มีประโยชน์ในกรณีต่อไปนี้
- คุณใช้
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
เป็นส่วนเสริมที่น่าตื่นเต้นของ Scheduler API ซึ่งหวังว่าจะช่วยให้นักพัฒนาแอปปรับปรุงการตอบสนองได้ง่ายขึ้นกว่ากลยุทธ์การสร้างรายได้ในปัจจุบัน หาก scheduler.yield
เป็น API ที่มีประโยชน์สำหรับคุณ โปรดเข้าร่วมการวิจัยของเราเพื่อช่วยปรับปรุง API ดังกล่าวและแสดงความคิดเห็นเกี่ยวกับวิธีปรับปรุง API ให้ดียิ่งขึ้น
รูปภาพหลักจาก Unsplash โดย Jonathan Allison