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