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