การสร้างเว็บไซต์ที่ตอบสนองต่อข้อมูลจากผู้ใช้อย่างรวดเร็วนั้นเป็นเรื่องที่ท้าทายที่สุดเรื่องหนึ่งสำหรับประสิทธิภาพของเว็บ ซึ่งทีม Chrome ได้ทำงานอย่างเต็มที่เพื่อช่วยให้นักพัฒนาเว็บพบเจอ เมื่อปีนี้ มีการประกาศว่าเมตริก Interaction to Next Paint (INP) จะเปลี่ยนจากสถานะทดลองเป็นสถานะรอดำเนินการ ปัจจุบันมีแผนที่จะมาแทนที่ First Input Delay (FID) ให้เป็น Core Web Vitals ในเดือนมีนาคม 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 อย่างเป็นทางการได้ 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