Web Workers: รัน JavaScript ใน Background Thread
ในบทก่อนหน้า เราได้เรียนรู้เกี่ยวกับการใช้งาน Cookies เพื่อเก็บข้อมูลใน Browser แต่ Cookies มีข้อจำกัดหลายอย่าง โดยเฉพาะอย่างยิ่งเมื่อต้องการเก็บข้อมูลจำนวนมาก หรือต้องการประมวลผลข้อมูลที่ซับซ้อน การประมวลผลข้อมูลที่ซับซ้อนใน Main Thread ของ Browser อาจทำให้ UI ค้างหรือตอบสนองช้าลงได้
Web Workers เข้ามาช่วยแก้ปัญหานี้ โดยเป็น API ที่ช่วยให้เราสามารถรัน JavaScript ใน Background Thread ได้ ซึ่งหมายความว่าเราสามารถประมวลผลข้อมูลที่ซับซ้อนโดยไม่กระทบต่อ UI ของเว็บไซต์
Web Workers คืออะไร?
Web Workers เป็น JavaScript scripts ที่รันใน background thread แยกต่างหากจาก main thread ของ Browser ซึ่งทำให้ Browser สามารถทำงานอื่นๆ ได้ตามปกติ โดยไม่ถูกขัดจังหวะจากการประมวลผลที่หนักหน่วงของ Web Workers
ลองนึกภาพว่า Main Thread คือพนักงานต้อนรับที่ต้องรับแขก, ตอบคำถาม, และจัดการเอกสารต่างๆ การให้พนักงานต้อนรับคำนวณภาษีทั้งบริษัทไปด้วย อาจทำให้งานอื่นๆ ช้าลง Web Workers ก็เหมือนกับทีมงานเบื้องหลังที่ช่วยคำนวณภาษี ทำให้พนักงานต้อนรับยังคงทำงานหลักได้อย่างราบรื่น
ข้อดีของการใช้ Web Workers
- UI ที่ตอบสนอง: ช่วยให้ UI ไม่ค้างหรือตอบสนองช้าลง เมื่อมีการประมวลผลที่ซับซ้อน
- การประมวลผลแบบ Parallel: สามารถรันงานหลายอย่างพร้อมกันได้
- ประสิทธิภาพที่ดีขึ้น: ช่วยเพิ่มประสิทธิภาพโดยรวมของเว็บไซต์
ข้อจำกัดของ Web Workers
Web Workers มีข้อจำกัดบางประการที่ควรทราบ:
- ไม่สามารถเข้าถึง DOM โดยตรง: Web Workers ไม่สามารถเข้าถึง DOM (Document Object Model) ได้โดยตรง ต้องส่ง message ไปยัง Main Thread เพื่อทำการเปลี่ยนแปลง DOM
- ไม่สามารถเข้าถึง window object: Web Workers ไม่สามารถเข้าถึง `window` object ได้ (แต่สามารถเข้าถึง `navigator` และ `location` ได้)
- การสื่อสารแบบ Asynchronous: การสื่อสารระหว่าง Main Thread และ Worker เป็นแบบ Asynchronous โดยใช้ Message Passing
- ข้อจำกัดด้าน Security: Web Workers ต้องอยู่บน Domain เดียวกันกับหน้าที่เรียกใช้ (Same-Origin Policy) หรือต้องมีการตั้งค่า CORS ที่ถูกต้อง
การสร้าง Web Worker
การสร้าง Web Worker ทำได้ง่ายๆ โดยใช้ Constructor `Worker()` และระบุ Path ไปยัง JavaScript file ที่จะรันใน Worker:
// main.js
const worker = new Worker('worker.js');
worker.addEventListener('message', (event) => {
console.log('Message from worker:', event.data);
});
worker.postMessage('Hello from main thread!');
ในตัวอย่างนี้ `main.js` จะสร้าง Web Worker โดยใช้ `worker.js` เป็น Script ที่จะรันใน Background Thread
worker.js:
// worker.js
self.addEventListener('message', (event) => {
console.log('Message from main thread:', event.data);
// Perform some complex calculation
const result = doSomeHeavyComputation();
self.postMessage(result);
});
function doSomeHeavyComputation() {
// Simulate a long-running task
let sum = 0;
for (let i = 0; i < 1000000000; i++) {
sum += i;
}
return sum;
}
ใน `worker.js` เราใช้ `self.addEventListener('message', ...)` เพื่อรับ Message จาก Main Thread และใช้ `self.postMessage()` เพื่อส่ง Message กลับไปยัง Main Thread
การส่ง Message ระหว่าง Main Thread และ Worker
การสื่อสารระหว่าง Main Thread และ Worker ทำได้โดยใช้ Message Passing โดยใช้ Method `postMessage()` และ Event Listener `message`:
- `postMessage(data)`: ใช้เพื่อส่งข้อมูลจาก Thread หนึ่งไปยังอีก Thread หนึ่ง
- `addEventListener('message', (event) => { ... })`: ใช้เพื่อรับข้อมูลที่ส่งมาจากอีก Thread หนึ่ง โดยข้อมูลจะอยู่ใน `event.data`
ตัวอย่าง:
// main.js (ส่งข้อมูลไปยัง worker)
worker.postMessage({ type: 'calculate', data: [1, 2, 3, 4, 5] });
// worker.js (รับข้อมูลและส่งผลลัพธ์กลับ)
self.addEventListener('message', (event) => {
if (event.data.type === 'calculate') {
const numbers = event.data.data;
const sum = numbers.reduce((a, b) => a + b, 0);
self.postMessage({ type: 'result', data: sum });
}
});
// main.js (รับผลลัพธ์จาก worker)
worker.addEventListener('message', (event) => {
if (event.data.type === 'result') {
console.log('Sum:', event.data.data);
}
});
ในตัวอย่างนี้ Main Thread ส่งข้อมูล `type` และ `data` ไปยัง Worker, Worker คำนวณผลรวม และส่งผลลัพธ์กลับไปยัง Main Thread พร้อมกับ `type` ที่ระบุว่า Message นี้เป็นผลลัพธ์
กรณีใช้งานจริงและข้อผิดพลาดที่พบบ่อย
กรณีใช้งานจริง:
- การประมวลผลภาพ: สามารถใช้ Web Workers เพื่อประมวลผลภาพขนาดใหญ่ โดยไม่ทำให้ UI ค้าง
- การคำนวณทางคณิตศาสตร์ที่ซับซ้อน: เช่น การคำนวณทางสถิติ, การจำลองทางวิทยาศาสตร์
- การเข้ารหัส/ถอดรหัสข้อมูล: สามารถใช้ Web Workers เพื่อเข้ารหัสหรือถอดรหัสข้อมูลขนาดใหญ่ โดยไม่กระทบต่อ UI
- Game Development: การคำนวณ AI หรือ Physics ในเกม
ข้อผิดพลาดที่พบบ่อย:
- ลืมตรวจสอบ Same-Origin Policy: ตรวจสอบให้แน่ใจว่า Worker script อยู่บน Domain เดียวกัน หรือมีการตั้งค่า CORS ที่ถูกต้อง
- พยายามเข้าถึง DOM โดยตรงใน Worker: Worker ไม่สามารถเข้าถึง DOM ได้โดยตรง ต้องส่ง message ไปยัง Main Thread
- ส่งข้อมูลขนาดใหญ่เกินไปผ่าน `postMessage()`: การส่งข้อมูลขนาดใหญ่ผ่าน `postMessage()` อาจทำให้เกิดปัญหาด้านประสิทธิภาพ ควรพิจารณาใช้ SharedArrayBuffer (Advanced)
- ลืม terminate Worker: เมื่อ Worker ไม่จำเป็นต้องใช้งานแล้ว ควร `terminate()` เพื่อปล่อย Resources
// Terminate worker
worker.terminate();
แนวคิดเชิงระบบและ Pattern ทั่วไป
Message Queue Pattern:
สามารถใช้ Web Workers ร่วมกับ Message Queue Pattern เพื่อจัดการงานจำนวนมากได้อย่างมีประสิทธิภาพ โดย Main Thread จะส่งงานไปยัง Queue และ Workers จะดึงงานจาก Queue ไปประมวลผล
Error Handling:
ควรมีการจัดการ Error ที่ดีทั้งใน Main Thread และ Worker Thread:
// main.js
worker.addEventListener('error', (error) => {
console.error('Worker error:', error);
});
// worker.js
self.addEventListener('error', (error) => {
console.error('Worker error:', error);
});
สรุป
Web Workers เป็นเครื่องมือที่มีประโยชน์ในการเพิ่มประสิทธิภาพของเว็บไซต์ โดยช่วยให้เราสามารถรัน JavaScript ใน Background Thread ได้ โดยไม่กระทบต่อ UI อย่างไรก็ตาม ควรคำนึงถึงข้อจำกัดต่างๆ ของ Web Workers และใช้งานอย่างเหมาะสม
ในบทถัดไป เราจะเรียนรู้เกี่ยวกับ Fetch API ซึ่งเป็น API ที่ใช้ในการดึงข้อมูลจาก Server