Call Stack + Web APIs + Callback Queue + Event Loop = หัวใจ async
Call Stack ทำงานทีละอย่าง → Web APIs ทำงานเบื้องหลัง → Callback Queue เข้าแถวรอ → Event Loop ดึง callback เข้า stack เมื่อ stack ว่าง
JavaScript Phase Overview
ภาพรวมของ `Async JavaScript` series — เข้าใจว่า async คืออะไร ทำไมต้องใช้ ใช้เมื่อไรในงานจริง ภาพจำสำคัญ 4 ข้อ ข้อควรระวังที่พบบ่อย และเส้นทางการเรียนรู้ 7 บทลูก — จาก Non-Blocking I/O สู่ async/await และ Error Handling
ในโลกของโปรแกรมมิ่ง ส่วนใหญ่โค้ดจะทำงาน**ทีละบรรทัดจากบนลงล่าง** — บรรทัดแรกเสร็จก่อน ถึงจะข้ามไปบรรทัดสอง นี่คือ **synchronous** — ทำงานตามลำดับ แต่ในโลกจริง — หลายงานใช้**เวลา** — การเรียก API อาจใช้เวลา 200ms, การอ่านไฟล์อาจใช้ 500ms, การรอให้ผู้ใช้คลิกปุ่มอาจไม่เกิดขึ้นเลย ถ้าโปรแกรมหยุดรอทุกครั้ง — UI จะค้าง ผู้ใช้จะหงุดหงิด **Asynchronous (async)** คือการทำงานโดย**ไม่ต้องรอ** — ส่งงานให้ระบบจัดการเบื้องหลัง แล้วไปทำงานอื่นต่อ — เมื่อผลลัพธ์กลับมา ระบบจะแจ้งให้เรามารับ JavaScript เองเป็นภาษา **single-threaded** — มี Call Stack เดียว — ทำงานได้ทีละอย่าง แต่ผ่านกลไกของเบราว์เซอร์และ Node.js — มันสามารถจัดการงาน async ได้อย่างมีประสิทธิภาพโดยไม่เคยหยุดรอ
// === Synchronous ===
console.log("1: เริ่ม (sync)");
console.log("2: จบ (sync)");
// output:
// "1: เริ่ม (sync)"
// "2: จบ (sync)"
// === Asynchronous ===
console.log("A: เริ่ม (sync)");
setTimeout(function () {
console.log("C: callback (async)");
}, 0);
console.log("B: จบ (sync)");
// output:
// "A: เริ่ม (sync)"
// "B: จบ (sync)"
// "C: callback (async)" ← มาทีหลังลองนึกภาพเว็บที่ทุกครั้งที่คุณกดปุ่ม — ทั้งหน้าจอค้าง 2-3 วินาที — คลิกอะไรไม่ได้เลย — scroll ไม่ได้ — พิมพ์ไม่ได้ จนกว่า operation จะเสร็จ นี่คือสิ่งที่เกิดขึ้นถ้า JavaScript ไม่มี async Async แก้ปัญหานี้ด้วยแนวคิดง่าย ๆ: **ไม่ต้องหยุดรอ** — ส่งงานที่มีความเสี่ยงว่าจะใช้เวลานานให้ระบบจัดการเบื้องหลัง — แล้วไปทำงานอื่นต่อ — เมื่อผลลัพธ์กลับมา — ระบบจะเรียกโค้ดของเรากลับมา
| ถ้าไม่มี async | ถ้ามี async |
|---|---|
| หน้าเว็บ**ค้าง**ระหว่างรอ fetch — ผู้ใช้คลิกอะไรไม่ได้ | หน้าเว็บ**ตอบสนองปกติ** — ผู้ใช้คลิก พิมพ์ scroll ได้ระหว่างรอ fetch |
| รับ request ได้ทีละ 1 request — request หลังต้องรอ request แรกเสร็จ | รับ request พร้อมกันได้**เป็นพัน** — server ไม่เคยหยุดรอ |
| animation กระตุก เพราะ main thread ถูกบล็อก | animation ลื่น — main thread ว่างทำงาน render ระหว่างรอ |
| โค้ด async ซ้อนกันเป็น**พีระมิด** (callback hell) | โค้ด async อ่านเป็น**เส้นตรง** — ผ่าน Promise chain หรือ async/await |
async ไม่ใช่แค่ concept — มันคือลมหายใจของ JavaScript ในงานจริง ตารางด้านล่างช่วยให้เห็นว่าแต่ละสถานการณ์ใช้ async pattern แบบไหน
| สถานการณ์ | Pattern ที่ใช้ | ตัวอย่าง API |
|---|---|---|
| เรียก API หรือรอข้อมูลจาก server | Callback, Promise, async/await | `fetch()`, axios, WebSocket |
| รอให้ผู้ใช้โต้ตอบ (คลิก, พิมพ์, scroll) | Callback (event listener) | `addEventListener()`, `onclick` |
| หน่วงเวลาหรือทำงานซ้ำ | Callback | `setTimeout()`, `setInterval()` |
| ทำงานตามลำดับหลายขั้นตอนที่ต้องรอ | Promise chain, async/await | login → getProfile → getNotifications |
| รอหลายงานพร้อมกันโดยไม่ต้องรอทีละงาน | Promise.all() + async/await | โหลด users, posts, comments พร้อมกัน |
| อ่าน/เขียนไฟล์ หรือ query database | Callback, Promise, async/await | `fs.readFile()`, `db.query()` |
ภาพที่สำคัญที่สุดของ async JavaScript คือแยก 4 เรื่องออกจากกันให้ได้: 1. **Call Stack + Web APIs + Callback Queue** — 3 องค์ประกอบที่ทำงานร่วมกันเพื่อให้ single-threaded JavaScript ทำงาน async ได้ 2. **Callback = function ที่ถูกเรียกกลับมา** — ไม่ใช่ function พิเศษ — แค่ function ธรรมดาที่ถูกส่งเป็น argument 3. **Promise = กล่องใส่ของที่อาจยังไม่มาถึง** — แกะด้วย `.then()` หรือ `await` — ไม่ใช่ค่าจริงตอนที่สร้าง 4. **Microtask มี priority สูงกว่า Macrotask** — Promise callback ทำงานก่อน setTimeout callback เสมอ
จำ 4 ข้อนี้ได้ก่อนลงบทลูก เรื่อง async จะเป็นระบบขึ้นมาก
Call Stack ทำงานทีละอย่าง → Web APIs ทำงานเบื้องหลัง → Callback Queue เข้าแถวรอ → Event Loop ดึง callback เข้า stack เมื่อ stack ว่าง
Callback ไม่ใช่ของพิเศษ — คือ function ธรรมดาที่ส่งเป็น argument — เมื่อทำงานเสร็จระบบจะ 'โทรกลับ' โดยเรียก function นั้น
Promise object ≠ ค่าข้างใน — ต้องใช้ `.then()` หรือ `await` ถึงจะได้ค่าออกมา — chain ต่อกันเป็น pipeline
Promise callback ทำงานก่อน setTimeout callback เสมอ — เพราะ microtask queue ถูกระบายจนหมดก่อนค่อยดึง macrotask
Async JavaScript เป็นหัวข้อที่ทรงพลังมาก แต่ก็มีจุดพลาดซ้ำ ๆ เยอะ หน้านี้ยังไม่ลงลึกวิธีแก้ทุกข้อ — เป้าหมายคือให้คุณ**รู้จักชื่อของปัญหา**ไว้ก่อน เพื่อจะไม่ตกใจเมื่อเจอในบทถัดไป
รู้จักไว้ก่อน แล้วค่อยไปเก็บรายละเอียดและตัวอย่างเต็มในแต่ละบทลูก
callback ซ้อน callback ซ้อน callback — อ่านจากในออกนอก — แก้ด้วย Promise chain หรือ async/await
`.then(() => { fetchData() })` ≠ `.then(() => fetchData())` — ถ้าไม่มี `return` — ตัวถัดไปได้ `undefined`
Promise ที่ reject โดยไม่มี `.catch()` จะกลายเป็น `UnhandledPromiseRejection` — ดีบักยากและอาจทำให้แอปพัง
`await` ใช้ได้เฉพาะใน `async function` หรือ top-level module — ใน regular function จะ error ทันที
การ recursive Promise ที่ไม่หยุดจะบล็อก setTimeout, event handler, render ทั้งหมด — UI ค้างถาวร
`await a(); await b(); await c()` — รอทีละตัว — ใช้ `Promise.all([a(), b(), c()])` แทนเมื่อไม่ต้องพึ่งกัน
รอบนี้เราแยก `Async JavaScript` ออกมาเป็น phase ใหญ่ของตัวเอง — 7 บทลูกเรียงลำดับจากรากฐานไปสู่การประยุกต์ใช้จริง ถ้ายังใหม่กับ async — แนะนำให้เริ่มจาก `Non-Blocking I/O` และไล่ไปตามลำดับ — เพราะแต่ละบทต่อยอดจากบทก่อนหน้าโดยตรง
| ถ้าโจทย์ของคุณเป็นแบบนี้ | ควรไปบทไหน |
|---|---|
| อยากเข้าใจว่า JavaScript ทำงาน async ได้ยังไงทั้งที่เป็น single-threaded | `Non-Blocking I/O` |
| อยากส่ง function ให้ function อื่นเรียกเมื่อทำงานเสร็จ | `Callbacks` |
| อยากเข้าใจ Event Loop ว่าประสาน Call Stack, Web APIs, Callback Queue อย่างไร | `Event Loop` |
| อยากรู้ว่าทำไม Promise callback ทำงานก่อน setTimeout | `Microtask vs Macrotask` |
| อยากเขียนโค้ด async ให้อ่านง่ายเหมือน sync ด้วย async/await | `async / await` |
| อยากจัดการ error ใน async code — try/catch + recovery + cleanup | `Error handling in async` |
| อยากฝึกโจทย์ที่ใช้ทุกคอนเซปต์รวมกัน — callback → Promise → async/await | `Workshop: Async JavaScript` |