

บางครั้งเปิด Network แล้วไม่เห็นอะไรดาวน์โหลดเพิ่ม แต่หน้าก็ยังตอบสนองช้า พอกดปุ่มต้องรอสักพักจึงเห็นผล อาการแบบนี้ทำให้เราต้องมองงานที่เบราว์เซอร์กำลังทำอยู่ด้วย เพราะการได้ไฟล์มาครบกับการทำงานจากไฟล์เหล่านั้นเสร็จเป็นคนละช่วงกัน
JavaScript ที่แปลงข้อมูล สร้างรายการ หรือจัดการเหตุการณ์อาจใช้ main thread ต่อเนื่องจนงานอื่นยังไม่ได้เริ่มครับ ถ้าจะตรวจให้ตรงจุด เราต้องเห็นว่างานใดครองเวลาในช่วงที่คนกำลังรอ และงานนั้นจำเป็นต้องจบเป็นก้อนเดียวจริงหรือไม่
Long task จึงเป็นจุดให้ตามหางานที่ยาว ไม่ใช่ชื่อสาเหตุสำเร็จรูปที่บอกทันทีว่าต้องเปลี่ยนเครื่องมือทั้งชุด
งาน JavaScript ที่อยู่ใน task เดียวจะทำต่อจนจบก่อนคืนการควบคุมตามรูปแบบการทำงานของมัน หากมีเหตุการณ์ใหม่เข้ามาระหว่างนั้น การตอบสนองก็ต้องรอจังหวะของเบราว์เซอร์ ไม่ได้แทรกเข้าไปกลางลูปของเราโดยอัตโนมัติ
Task ที่ยาวเกิน 50 มิลลิวินาทีถูกเรียกว่า long task ตามเกณฑ์ที่ใช้ตรวจ ตัวเลขนี้เป็นขอบเขตทางเทคนิค ไม่ได้แปลว่างานที่สั้นกว่านิดเดียวรับรองว่าลื่นทุกสถานการณ์ หรือการมี long task หนึ่งครั้งบอกความรู้สึกคนใช้ได้ทั้งหมด

ถ้ามีหลายงานต่อเนื่องในช่วงที่คนกด เราต้องดูทั้งบริบทด้วย เช่น หน้าเพิ่งเริ่มทำงาน ข้อมูลเพิ่งกลับมา หรือมีการอัปเดตเป็นรอบๆ เพราะงานที่ไม่เกี่ยวกับปุ่มโดยตรงก็อาจขวางปุ่มนั้นได้
จึงควรเริ่มจากบันทึกการกระทำที่เกิดอาการ แล้วเปิด trace รอบเดียวกัน เทียบเวลา interaction กับกิจกรรมบน main thread แทนการเลือกไฟล์ JavaScript ที่ใหญ่ที่สุดแล้วสรุปว่าเป็นตัวทำให้ค้างก่อนมีหลักฐาน
โค้ดที่แบ่งเป็นฟังก์ชันอ่านง่ายอาจยังถูกเรียกต่อกันใน task เดียว เช่น เตรียมข้อมูล จัดกลุ่ม สร้างผลลัพธ์ และคำนวณส่วนประกอบอื่น เมื่อเรียกทั้งหมดแบบต่อเนื่อง เบราว์เซอร์ก็ยังต้องรอจนเส้นทางนั้นคืนการควบคุม
การเติม async ที่ฟังก์ชันอย่างเดียวไม่ได้ย้ายงานไปอีก thread และการแบ่งเป็น Promise ที่ต่อ microtask กันก็ไม่รับรองว่าจะเปิดโอกาสให้วาดหรือรับ input ในจังหวะที่ต้องการ จึงต้องแยกระหว่างโครงสร้างที่อ่านง่ายกับขอบเขต task จริง
ลองเริ่มจากถามว่างานใดไม่จำเป็นเลย เช่น คำนวณซ้ำทั้งรายการทั้งที่ข้อมูลเปลี่ยนเพียงส่วนเดียว หรือเตรียมของที่ยังไม่ถูกใช้งาน การลดงานที่ไม่ต้องทำช่วยได้ตรงกว่าแบ่งงานเดิมทุกส่วนโดยยังไม่มีเหตุผลให้มันอยู่
ถ้ายังต้องทำทั้งหมด ค่อยดูขอบเขตที่แบ่งได้โดยไม่ทำให้ผลลัพธ์ผิด เช่น ประมวลผลข้อมูลเป็นช่วงและรวมผลเมื่อครบ โดยไม่ปล่อยสถานะครึ่งทางให้หน้าที่อื่นเข้าใจว่าเป็นข้อมูลสมบูรณ์
ตัวอย่างสมมติของการเปิดช่องให้เบราว์เซอร์ทำงานระหว่างชุดข้อมูล อาจเริ่มจากรูปแบบง่ายๆ ดังนี้:
const yieldTask = () => new Promise(resolve => setTimeout(resolve, 0));
async function buildRows(items, isCurrent) {
const result = [];
for (let i = 0; i < items.length; i++) {
if (!isCurrent()) return null;
result.push(makeRowData(items[i]));
if ((i + 1) % 100 === 0) await yieldTask();
}
return isCurrent() ? result : null;
}
makeRowData กับ isCurrent เป็นหน้าที่ที่แอปต้องกำหนดเอง ตัวเลข 100 เป็นเพียงขนาดชุดในตัวอย่าง ไม่ใช่ค่าที่เหมาะกับทุกเครื่อง หากงานต่อรายการหนักมาก ต้องเลือกขอบเขตจากเวลาที่วัดได้และลักษณะงานจริงอีกที

จุดที่เพิ่มเข้ามาคือระหว่าง await ผู้ใช้อาจเปลี่ยนคำค้นหรือออกจากหน้าแล้ว ผลที่ทำต่อจึงอาจเก่า ต้องมีวิธีรู้ว่างานรอบนี้ยังเป็นรอบที่ต้องการหรือควรหยุด รวมถึงกำหนดว่าจะทำงานกับสำเนาข้อมูลหรือข้อมูลที่เปลี่ยนได้ระหว่างรัน
การแบ่ง task ไม่ได้รับรองว่าแต่ละช่วงจะวาดหนึ่ง frame และไม่ได้รับรองเวลาที่ callback จะกลับมาเริ่ม แต่ช่วยเปลี่ยนจากการครองงานเป็นก้อนเดียวให้มีจุดที่คืนการควบคุมได้ ซึ่งต้องตรวจผลในสภาพหน้าจริงต่อ
ถ้างานเป็นการคำนวณที่ไม่ต้องแตะ DOM อาจพิจารณาย้ายไป Web Worker แต่ยังมีต้นทุนส่งข้อมูล การเริ่มงาน และการรวมผลกลับ จึงควรวัดตามขนาดข้อมูลที่ใช้จริง ไม่คิดว่าการเพิ่ม worker ทำให้ทุกขั้นไม่มีต้นทุน
งานที่ต้องปรับ DOM ยังต้องวางแผนฝั่งหน้า เช่น ลดจำนวน element ที่สร้างพร้อมกัน หรือจำกัดส่วนที่อัปเดต การย้ายแค่การคำนวณออกไปอาจช่วยส่วนหนึ่ง แต่ถ้ากลับมาสร้างรายการใหญ่มากในครั้งเดียวก็ยังเหลืองานวาดให้ตรวจต่อ
สำหรับงานหลายรอบ ต้องมีรหัสหรือนโยบายแยกผลเก่ากับใหม่ด้วย เช่น คำค้นล่าสุดควรได้รับผลของรอบล่าสุด ไม่ปล่อยให้ worker ที่จบทีหลังเขียนทับผลใหม่เพียงเพราะมาถึงหน้าจอช้ากว่า
หลังปรับ ควรทำ interaction เดิมในเงื่อนไขเดิมแล้วเทียบช่วงเวลาที่รอ รวมถึงเวลาจบงานทั้งหมด เพราะการกระจายงานอาจทำให้ UI รับคำสั่งได้ดีขึ้นโดยใช้เวลารวมต่างออกไป ต้องรู้ว่ากำลังเลือกข้อแลกเปลี่ยนใด
ลองการกดหรือพิมพ์ต่อระหว่างงานทำอยู่ด้วย จะเห็นว่าเงื่อนไขยกเลิกและลำดับผลถูกต้องหรือไม่ ส่วนข้อมูลที่ไม่ครบหรือคำขอผิดพลาดควรมีสถานะที่คนเข้าใจ ไม่แสดงผลบางส่วนเหมือนงานเสร็จแล้ว
ภาพในบทนี้เป็นแนวคิดการแบ่งงาน ไม่มี trace หรือผลเวลาที่อ้างจากเว็บจริง หากต้องรายงานว่าแก้ long task ของหน้าใดได้ ควรเก็บการกระทำ รุ่นโค้ด และ trace ก่อนหลังให้ตรงกัน จึงจะตรวจได้ว่าจุดที่ดีขึ้นสัมพันธ์กับงานที่เปลี่ยนจริง
หน้าเว็บที่ไม่มีดาวน์โหลดเพิ่มยังอาจติดงานบน main thread ได้ การตรวจ long task ควรเริ่มจากอาการและ trace แล้วลดงานที่ไม่จำเป็นก่อนเลือกแบ่งหรือย้ายการคำนวณ เมื่อเพิ่มจุดคืนการควบคุม ต้องดูผลเก่า การยกเลิก และความครบของข้อมูลควบคู่ไปด้วย จึงจะได้ทั้งหน้าที่ตอบสนองดีและผลลัพธ์ที่ตรงกับคำสั่งล่าสุดครับ