🔵 Node.js

เจาะลึก Event Loop และ Asynchronous Architecture ใน Node.js สำหรับมือใหม่

9 นาที 18 views บันทึกเป็น PDF
เจาะลึก Event Loop และ Asynchronous Architecture ใน Node.js สำหรับมือใหม่

อยากเขียน Node.js ให้เก่งต้องเข้าใจ Event Loop! มาทำความรู้จัก Call Stack, Event Queue และการทำงานแบบ Asynchronous ที่ช่วยให้โปรแกรมของคุณทำงานได้ลื่นไหล

ทำความรู้จักกับหัวใจของ Node.js: Single-threaded คืออะไร

เวลาเราพูดถึงการเขียนโปรแกรมฝั่ง Backend (ระบบหลังบ้าน) ด้วย Node.js หลายคนมักได้ยินคำว่ามันเป็นแบบ Single-threaded (ทำงานทีละหนึ่งอย่างในหนึ่งช่วงเวลา) ฟังดูเหมือนจะช้าใช่ไหมครับ แต่ความจริงคือมันออกแบบมาเพื่อรับมือกับงานจำนวนมากได้อย่างยอดเยี่ยม โดยไม่ต้องเปิดหลายหน้าต่างงานให้เปลืองทรัพยากรเครื่อง

ลองจินตนาการถึงร้านกาแฟที่มีพนักงานชงกาแฟเพียงคนเดียว ถ้าพนักงานคนนี้มัวแต่ยืนรอเครื่องชงกาแฟทำงานเสร็จแล้วค่อยรับออเดอร์ถัดไป ร้านคงเจ๊งแน่นอน Node.js ทำงานเหมือนพนักงานที่ฉลาดกว่านั้น คือเมื่อกดปุ่มชงกาแฟแล้ว เขาจะหันไปรับออเดอร์จากลูกค้าคนถัดไปทันทีโดยไม่ยืนรอ ทำให้ร้านดูเหมือนมีคนทำงานอยู่ตลอดเวลา

การเข้าใจเรื่องนี้สำคัญมากสำหรับมือใหม่ เพราะถ้าคุณเขียนโค้ดโดยไม่เข้าใจธรรมชาติของมัน คุณอาจจะเขียนโปรแกรมที่ไป "บล็อก" (ขัดขวาง) การทำงานของคนอื่นโดยไม่รู้ตัว การเป็นโปรแกรมเมอร์ที่เก่งต้องเริ่มจากการเข้าใจว่า สภาพแวดล้อมในการรันโค้ด (เครื่องมือที่ใช้ประมวลผลคำสั่ง) ของเราทำงานอย่างไร เพื่อให้เราเขียนโปรแกรมที่ลื่นไหลและตอบสนองได้รวดเร็ว

// ตัวอย่างการทำงานแบบ Synchronous (ทำตามลำดับขั้นตอน)
console.log("เริ่มรับออเดอร์");
// สมมติว่านี่คือการรอให้กาแฟชงเสร็จ ซึ่งใช้เวลานาน
console.log("กำลังชงกาแฟ...");
console.log("รับออเดอร์ถัดไป");

จากตัวอย่างข้างต้น console.log จะถูกพิมพ์ออกมาตามลำดับบรรทัดที่ 1, 2 และ 3 หากบรรทัดที่ 2 เป็นงานที่ใช้เวลานานมาก บรรทัดที่ 3 จะไม่มีทางถูกเรียกใช้จนกว่าบรรทัดที่ 2 จะเสร็จสิ้น นี่คือปัญหาของโปรแกรมแบบดั้งเดิมที่มือใหม่มักเจอ

ผลลัพธ์ที่ควรเห็นใน Terminal (โปรแกรมสั่งงานผ่านตัวอักษร):
เริ่มรับออเดอร์
กำลังชงกาแฟ...
รับออเดอร์ถัดไป

Call Stack: สมุดจดลำดับงานของโปรแกรม

Call Stack (กองซ้อนที่เก็บตำแหน่งงานที่ต้องทำ) เปรียบเสมือนสมุดจดบันทึกของโปรแกรมเมอร์ เมื่อมีการเรียกใช้งานฟังก์ชัน (คำสั่งที่รวมกลุ่มการทำงาน) คอมพิวเตอร์จะจดบันทึกไว้ที่ด้านบนสุดของกองนี้เสมอ และเมื่อทำงานเสร็จก็จะลบงานนั้นออกไป เพื่อกลับไปทำงานก่อนหน้า

หากคุณเรียกฟังก์ชันซ้อนกันหลายชั้น Call Stack จะค่อยๆ สูงขึ้นเรื่อยๆ จนกว่างานชิ้นในสุดจะเสร็จ หากงานใดงานหนึ่งค้างอยู่ข้างในนานเกินไป งานอื่นๆ ที่ต่อคิวอยู่ก็จะถูกขวางไว้ทั้งหมด นี่คือเหตุผลว่าทำไมเราถึงต้องระวังเรื่องการใช้คำสั่งที่ใช้เวลานานภายในฟังก์ชันหลัก

การฝึกเขียนโค้ดให้ดี คุณควรหมั่นเช็คว่าฟังก์ชันที่เราเขียนมีความซับซ้อนเกินไปหรือไม่ การมี Call Stack ที่สะอาดและสั้นจะช่วยให้โปรแกรมของคุณทำงานได้เร็วขึ้น และช่วยให้คุณไล่หาจุดผิด (บั๊ก) ได้ง่ายขึ้นมากเมื่อเกิดปัญหาในอนาคต

function a() {
  console.log("ทำงานที่ A");
}
function b() {
  a();
  console.log("ทำงานที่ B");
}
b(); // เริ่มเรียกฟังก์ชัน b

ในโค้ดนี้ b() จะถูกนำเข้า Call Stack ก่อน จากนั้นเรียก a() เข้าไปวางทับด้านบน เมื่อ a() ทำงานเสร็จก็จะถูกหยิบออก แล้ว b() ถึงจะทำงานต่อในบรรทัดถัดไป

ผลลัพธ์ที่ควรเห็นใน Terminal:
ทำงานที่ A
ทำงานที่ B

Event Loop: หัวใจหลักที่คอยเชื่อมทุกอย่างเข้าด้วยกัน

Event Loop (วงจรตรวจสอบงาน) คือตัวละครลับที่คอยเฝ้าดู Call Stack และ Event Queue (แถวคอยงานที่รอการประมวลผล) อยู่ตลอดเวลา หน้าที่ของมันง่ายมากคือถ้า Call Stack ว่างลงเมื่อไหร่ มันจะรีบหยิบงานจากแถวคอยมาใส่ให้ทำทันที

ถ้าไม่มี Event Loop โปรแกรมของเราคงเป็นเหมือนหุ่นยนต์ที่ทำตามสั่งแล้วหยุดนิ่ง แต่ด้วยกลไกนี้ Node.js จึงสามารถทำงานแบบ Asynchronous (การทำงานที่ไม่ต้องรอให้อีกงานเสร็จก่อน) ได้อย่างสมบูรณ์แบบ ทำให้ระบบหลังบ้านสามารถรองรับผู้ใช้งานพร้อมกันได้หลายคนโดยไม่ล่ม

ในฐานะมือใหม่ คุณอาจยังไม่ต้องเขียน Event Loop เอง แต่การเข้าใจว่ามันทำงานอย่างไรจะเปลี่ยนวิธีคิดของคุณไปตลอดกาล คุณจะเริ่มมองหาจุดที่สามารถทำแบบ Asynchronous ได้แทนที่จะเขียนโค้ดแบบรอคิวกันยาวเหยียด ซึ่งเป็นทักษะสำคัญที่บริษัทไอทีใช้คัดเลือกโปรแกรมเมอร์เข้าทำงาน

console.log("เริ่ม");
// setTimeout เป็นฟังก์ชันที่จำลองงานใช้เวลา
setTimeout(() => {
  console.log("งานที่ใช้เวลาเสร็จแล้ว");
}, 0);
console.log("จบ");

แม้จะตั้งเวลาเป็น 0 แต่ setTimeout จะถูกส่งไปที่ Web APIs หรือระบบจัดการของ Node.js ก่อน แล้วค่อยเข้าแถวคอย ทำให้คำสั่ง console.log("จบ") ทำงานก่อนเสมอ

ผลลัพธ์ที่ควรเห็นใน Terminal:
เริ่ม
จบ
งานที่ใช้เวลาเสร็จแล้ว

Event Queue และการจัดการงานที่ค้างอยู่

Event Queue (แถวคอยที่รอให้งานว่าง) คือสถานที่พักของงานที่ยังทำไม่ได้ในขณะที่ Call Stack กำลังยุ่งอยู่ เมื่อฟังก์ชันอย่าง setTimeout หรือการอ่านไฟล์จากฐานข้อมูลทำงานเสร็จ งานเหล่านั้นจะถูกส่งมาต่อแถวรอที่นี่ เพื่อรอเวลาที่เหมาะสมในการประมวลผล

ความน่าสนใจคือ Event Queue ไม่ใช่แถวเดียวจบ แต่มีการแบ่งลำดับความสำคัญของงานด้วย เพื่อให้งานที่สำคัญจริงๆ ได้ทำก่อนงานทั่วไป นี่คือเหตุผลที่บางครั้งโปรแกรมอาจทำงานเร็วกว่าที่เราคาดไว้ หากเราเข้าใจลำดับการจัดการของมันอย่างถูกต้อง

มือใหม่หลายคนมักสับสนว่าทำไมโค้ดถึงไม่ทำงานตามลำดับที่เขียนไว้ การทำความเข้าใจว่า Event Queue ทำงานอย่างไรจะช่วยแก้ปัญหานี้ได้ คุณจะเริ่มเห็นภาพว่าโค้ดบรรทัดไหนบ้างที่ถูกโยนออกไปทำงานข้างนอก และบรรทัดไหนที่ทำงานทันทีใน Call Stack

// การจำลองการทำงานที่ต้องรอข้อมูลจากภายนอก
setTimeout(() => console.log("งาน A"), 100);
setTimeout(() => console.log("งาน B"), 0);
console.log("งานหลัก");

แม้ งาน A จะถูกเขียนก่อน แต่ งาน B จะเข้าคิวและเสร็จก่อนเพราะเวลารอน้อยกว่า ส่วน งานหลัก จะถูกประมวลผลทันทีเพราะอยู่ใน Call Stack

ผลลัพธ์ที่ควรเห็นใน Terminal:
งานหลัก
งาน B
งาน A

Microtask และ Macrotask: ลำดับความสำคัญที่ต้องรู้

ในระบบของ Node.js ยังมีสิ่งที่เรียกว่า Microtask Queue (แถวคอยงานพิเศษที่มีลำดับสูงสุด) ซึ่งสำคัญกว่า Macrotask Queue (แถวคอยงานทั่วไป) โดย Microtask มักมาจาก Promise (งานที่สัญญาว่าจะเสร็จในอนาคต) ซึ่งจะถูกรันให้เสร็จก่อนงานอื่นๆ เสมอ

การแยกแยะระหว่าง Microtask และ Macrotask คือกุญแจสำคัญในการแก้บั๊กที่ซับซ้อน หากคุณเขียนโปรแกรมที่ต้องจัดการข้อมูลจำนวนมาก การจัดลำดับให้งานที่สำคัญที่สุดอยู่ใน Microtask จะทำให้แอปพลิเคชันของคุณตอบสนองได้รวดเร็วและแม่นยำกว่าการปล่อยให้ทุกอย่างปนกันอยู่ในแถวคอยเดียว

จำไว้เสมอว่า Event Loop จะกวาดล้าง Microtask ให้หมดเกลี้ยงก่อนที่จะหันไปมอง Macrotask เพียงหนึ่งงาน นี่เป็นกฎเหล็กที่โปรแกรมเมอร์มืออาชีพทุกคนต้องแม่นยำ เพื่อให้แน่ใจว่าลำดับการทำงานของระบบเป็นไปตามที่เราออกแบบไว้

console.log("เริ่มต้น");
setTimeout(() => console.log("Macrotask"), 0);
Promise.resolve().then(() => console.log("Microtask"));
console.log("สิ้นสุด");

แม้ setTimeout จะถูกเรียกก่อน Promise แต่ Microtask อย่าง Promise จะได้สิทธิพิเศษในการทำงานก่อน Macrotask เสมอเมื่อ Call Stack ว่าง

ผลลัพธ์ที่ควรเห็นใน Terminal:
เริ่มต้น
สิ้นสุด
Microtask
Macrotask

สรุป: การนำความเข้าใจเรื่อง Event Loop ไปใช้จริง

การเป็นโปรแกรมเมอร์ไม่ได้หมายความว่าต้องจำทฤษฎีได้ทั้งหมด แต่หมายถึงการเข้าใจว่าเครื่องมือที่เราใช้ทำงานอย่างไร เพื่อให้เราเขียนโค้ดที่ มีประสิทธิภาพ (ทำงานได้ดีโดยใช้ทรัพยากรน้อย) และไม่สร้างปัญหาให้ระบบในระยะยาว สถาปัตยกรรม Event Loop คือหัวใจที่ทำให้ Node.js ครองใจนักพัฒนาทั่วโลก

เมื่อคุณเริ่มสร้างโปรเจกต์จริง เช่น การทำ API เชื่อมต่อฐานข้อมูล ให้จำไว้ว่างานทุกอย่างที่ต้องรอ (เช่น การดึงข้อมูลผู้ใช้) ควรเขียนในรูปแบบ Asynchronous เสมอ เพื่อไม่ให้ Call Stack ของคุณค้างจนผู้ใช้งานคนอื่นต้องรอคอย การฝึกฝนเรื่องนี้จะทำให้คุณก้าวข้ามจากมือใหม่ไปสู่ระดับที่ทำงานในทีมจริงได้อย่างมั่นใจ

จงหมั่นทดลองและสังเกตลำดับการรันโค้ดด้วย console.log อยู่เสมอ แล้วคุณจะเริ่มเห็นภาพการทำงานของระบบที่ซับซ้อนนี้ชัดเจนขึ้นเอง หากวันนี้ยังไม่เข้าใจทั้งหมดก็ไม่เป็นไรครับ เพราะการเป็นโปรแกรมเมอร์คือการเรียนรู้ไปพร้อมกับการลงมือทำจริงทุกวัน

แชร์บทความ

Facebook X LINE

บทความที่เกี่ยวข้อง

คู่มือการทำ Production Deployment: รันโปรเจกต์ด้วย PM2 และ Docker ให้เสถียรเหมือนมือโปร
Node.js

คู่มือการทำ Production Deployment: รันโปรเจกต์ด้วย PM2 และ Docker ให้เสถียรเหมือนมือโปร

เรียนรู้วิธีนำโปรเจกต์ขึ้นเซิร์ฟเวอร์จริงให้รันได้ 24 ชั่วโมงด้วย PM2 และใช้ Docker ช่วยคุมสภาพแวดล้อมให้เหมือนกันทุกเครื่อง ลดปัญหาโค้ดพังเมื่อย้ายที่

1 week ago 11 นาที
13 views
เปลี่ยนโปรเจกต์ Node.js เป็น TypeScript: วิธีตั้งค่าและใช้งานสำหรับมือใหม่
Node.js

เปลี่ยนโปรเจกต์ Node.js เป็น TypeScript: วิธีตั้งค่าและใช้งานสำหรับมือใหม่

อยากเปลี่ยนจาก Node.js มาใช้ TypeScript แต่ไม่รู้เริ่มยังไง? บทความนี้สอนตั้งค่า tsconfig.json, การรันด้วย tsx และการทำ Type Checking ให้ Express แบบเข้าใจง่าย

1 week ago 9 นาที
11 views
สอนเขียน Automated Testing ด้วย Jest และ Supertest สำหรับมือใหม่
Node.js

สอนเขียน Automated Testing ด้วย Jest และ Supertest สำหรับมือใหม่

เลิกทดสอบโค้ดด้วยมือแบบเดิม ๆ มาหัดเขียน Automated Testing ด้วย Jest และ Supertest เพื่อเช็กฟังก์ชันและ API ให้ทำงานถูกต้องแม่นยำ ลดบั๊กกวนใจ

1 week ago 9 นาที
14 views