ทำความเข้าใจ Node.js Event Loop
Event Loop ไม่ใช่คนทำงาน แต่มันคือผู้ประสานงาน
นักพัฒนาหลายคนพบว่า Event Loop นั้นมีความซับซ้อน ความยากมักเกิดจากการนำแนวคิดหลายอย่างมาปนกันในคราวเดียว คุณต้องติดตามทั้ง libuv, Call Stack, Promises และ I/O ไปพร้อมๆ กัน
นี่คือความจริงที่เรียบง่าย: Event Loop ทำหน้าที่จัดตารางงาน แต่มันไม่ได้เป็นคนทำงานนั้นเอง
JavaScript ทำงานบน single thread ซึ่งหมายความว่าโค้ดจะทำงานเป็นเส้นตรง งานหนึ่งต้องเสร็จสิ้นก่อนที่งานถัดไปจะเริ่มขึ้น เราจึงต้องการระบบเพื่อจัดการงานแบบ asynchronous เช่น การอ่านไฟล์หรือการร้องขอผ่านเครือข่าย โดยไม่ทำให้โปรแกรมทั้งหมดต้องหยุดชะงัก
หลักการทำงาน:
Event Loop จะย้าย callback จากคิวต่างๆ เข้าสู่ Call Stack ให้ลองนึกภาพว่าแต่ละ phase คือแถวคอย และ Event Loop คือคนที่คอยพาคนจากแถวเข้าไปในห้อง
Phase หลักๆ มีดังนี้:
- Timers: จัดการ callback จาก setTimeout และ setInterval
- Pending Callbacks: จัดการข้อผิดพลาดเฉพาะของระบบ เช่น ข้อผิดพลาดของ TCP
- Idle and Prepare: เป็น phase ภายในที่ใช้โดย libuv ซึ่งคุณจะไม่ได้ใช้งานส่วนนี้
- Poll: เป็น phase ที่สำคัญที่สุด ทำหน้าที่ดึงเหตุการณ์ I/O ใหม่ๆ เช่น การอ่านไฟล์หรือการร้องขอ HTTP
- Check: จัดการ callback จาก setImmediate
- Close Callbacks: จัดการเหตุการณ์การปิดต่างๆ เช่น การปิด socket
ข้อผิดพลาดที่พบบ่อยคือการคิดว่า Event Loop มี Call Stack เป็นของตัวเอง ซึ่งจริงๆ แล้วไม่ใช่ Runtime มี Call Stack เพียงอันเดียวและมี microtask queue เพียงอันเดียว
กระบวนการทำงานมีลำดับดังนี้:
- Event Loop เลือก callback จาก phase ปัจจุบัน
- มันจะ push callback นั้นเข้าไปใน Call Stack
- Call Stack จะรันโค้ดนั้น
- เมื่อ callback ทำงานเสร็จสิ้น engine จะเคลียร์ microtask queue ซึ่งเป็นที่ที่ Promises และ async/await ทำงานอยู่
- Event Loop จะย้ายไปยัง phase ถัดไป
หากคุณใช้ setTimeout(..., 0) มันจะไปที่ phase Timers หากคุณใช้ setImmediate(...) มันจะไปที่ phase Check
งานหนักๆ จะเกิดขึ้นใน OS kernel และ libuv ส่วน Event Loop มีหน้าที่เพียงแค่บอก JavaScript ว่าถึงเวลาที่ต้องตอบสนองแล้ว
แหล่งที่มา: https://dev.to/joaovictor6/event-loop-entendendo-uma-das-bases-do-node-41a