Async + Event Loop
callback → Promise → async/await + Event Loop ที่อธิบายว่าทำไม Promise ชนะ setTimeout(0) เสมอ บทสุดท้ายและเป็น highlight ของ topic
ทดสอบ: โค้ดนี้ปริ้นอะไรออกมาตามลำดับ
console.log("1")
setTimeout(() => console.log("2"), 0)
Promise.resolve().then(() => console.log("3"))
console.log("4")คำตอบคือ 1, 4, 3, 2 ไม่ใช่ 1, 2, 3, 4 หรือ 1, 4, 2, 3 ที่หลายคนเดา
ทำไม setTimeout(..., 0) ที่ “รอ 0 วินาที” ถึงรันหลัง Promise คำตอบ อยู่ใน Event Loop ซึ่งเป็นบทสุดท้ายและเป็น highlight ของ topic นี้
Sync กับ Async ทำไมต้องมี async
JS เป็น single-threaded คือมีแค่ 1 thread รันโค้ด ถ้าเขียน sync (รันบรรทัด ต่อบรรทัดรอจบทีละอัน) งานหนักๆ จะทำให้ UI ค้าง
// ถ้า fetch API ทำแบบ sync
const data = fetchSync("/api/users") // ⚠️ รอ 2 วินาที UI ก็ค้าง 2 วิ
console.log(data)Async คือสั่งงานแล้วไม่รอ ปล่อยให้ทำงานเบื้องหลัง พร้อมเมื่อไหร่ค่อยมาบอก ทำให้ UI ตอบสนองได้
ยุค 1: Callback (เก่าแต่ยังเจอ)
สมัยก่อน JS ใช้ callback คือส่ง function ให้ไปรันเมื่อพร้อม
fetchUsers((err, users) => {
if (err) return console.log("error:", err)
fetchPosts(users[0].id, (err, posts) => {
if (err) return console.log("error:", err)
fetchComments(posts[0].id, (err, comments) => {
// ขอบขวาเลื่อนไปไกลๆ เรียกว่า "callback hell" หรือ "pyramid of doom"
})
})
})ปัญหาคือ nested ลึก อ่านยาก จัดการ error ลำบาก ปี 2015 มี Promise มาแก้
ยุค 2: Promise ค่าที่จะมาในอนาคต
Promise คือ object ที่บอกว่า “ตอนนี้ไม่มีค่า แต่จะมีในอนาคต” มี 3 state เท่านั้นคือ pending (รอ), fulfilled (สำเร็จ), rejected (พัง)
fetchUser(userId)
.then(user => console.log("ได้ user:", user))
.catch(err => console.log("error:", err))ใช้ .then() รับค่าตอน fulfilled, .catch() รับ error ตอน rejected, chain ต่อกันได้
fetchUsers()
.then(users => fetchPosts(users[0].id))
.then(posts => fetchComments(posts[0].id))
.then(comments => console.log(comments))
.catch(err => console.log("error:", err))อ่านง่ายกว่า callback hell แต่ยังไม่ดีพอ
ยุค 3: async / await คือ sync syntax สำหรับ async
ปี 2017 มี async/await เป็น sugar ที่ทำให้เขียน Promise เหมือนเขียน sync code
async function loadAll() {
try {
const users = await fetchUsers()
const posts = await fetchPosts(users[0].id)
const comments = await fetchComments(posts[0].id)
console.log(comments)
} catch (err) {
console.log("error:", err)
}
}อ่านเหมือน sync ทุกบรรทัด ใช้ try/catch ได้ปกติ JS สมัยใหม่เขียนแบบนี้เกือบทั้งหมด
Event Loop หัวใจของ async ใน JS
ทำไม setTimeout(fn, 0) ไม่รันทันที ทำไม Promise ชนะ setTimeout เสมอ เพราะ JS มี Event Loop ที่จัดคิวงานเรียงตาม priority
ลองดูว่ามันทำงานยังไง
console.log("1")
setTimeout(() => console.log("2"), 0)
Promise.resolve().then(() => console.log("3"))
console.log("4")
// output: 1, 4, 3, 24 ส่วนของ JavaScript runtime
- Call Stack คือกอง function ที่กำลังรัน (LIFO ตัวล่าสุดเข้าก่อนออก)
- Web APIs คือโค้ดของ browser (ไม่ใช่ JS) ที่จัดการ setTimeout, fetch, DOM event
- Macro Queue (หรือ Task Queue) คือคิวของงานทั่วไป เช่น setTimeout, setInterval, DOM event
- Micro Queue คือคิว priority สูงสำหรับ Promise.then, queueMicrotask, async/await continuation
ขั้นตอนของ Event Loop (ย่อ)
- มีงานใน Call Stack ไหม ถ้ามีก็รันต่อ
- Call Stack ว่าง เช็ค Micro Queue รันให้หมดก่อน
- Micro Queue ว่าง เช็ค Macro Queue ดึง 1 ตัว push เข้า Call Stack
- กลับไปข้อ 1
fetch async API ที่ใช้บ่อยที่สุด
fetch เป็น API แบบ Promise สำหรับขอข้อมูลจาก server ปัจจุบันมีในทุก browser และ Node 18 ขึ้นไป
async function loadUser(id) {
try {
const response = await fetch(`/api/users/${id}`)
if (!response.ok) {
throw new Error(`HTTP ${response.status}`)
}
const user = await response.json()
return user
} catch (err) {
console.error("โหลด user ไม่ได้:", err)
return null
}
}สรุป
- JS เป็น single-thread ใช้ async หลีกเลี่ยง UI ค้าง
- 3 ยุคคือ callback (เก่า), Promise (ดีขึ้น), async/await (ปัจจุบัน)
- Promise มี 3 state คือ pending, fulfilled, rejected (เปลี่ยนได้ครั้งเดียว)
- async function return Promise เสมอ, await ต้องอยู่ใน async function
- Event Loop: Call Stack, Micro Queue (Promise), Macro Queue (setTimeout)
- Promise ชนะ setTimeout(0) เสมอ เพราะ Micro Queue priority สูงกว่า
จบ topic JavaScript! 🎉
ผ่านครบ 10 บทแล้ว รู้ทุกอย่างที่จำเป็นสำหรับเขียน JS พื้นฐาน
- ✓ Phase 1 (1-4): syntax พื้นฐาน คือ variable, type, operator, control flow
- ✓ Phase 2 (5-7): function กับข้อมูล คือ arrow function, array/object, reference vs value
- ✓ Phase 3 (8-10): JS แท้ๆ คือ array methods, closure, async + event loop
ขั้นต่อไปแนะนำ
- เปิด browser console ลองเขียนของจริง แต่ละ concept ลองทำ 2-3 ตัวอย่าง
- เรียน DOM เพื่อเอา JS ไปทำเว็บจริง
- หรือกระโดดไป React เลย ทุก concept ที่เรียนมานำไปใช้ได้ทันที
- เพิ่ม TypeScript เมื่อพร้อม เป็น type system ที่ปลอดภัยกว่า dynamic typed
ขอให้สนุกกับการเขียน JS ภาษาที่เกิดใน 10 วันแต่จะอยู่กับคุณไปอีกหลายปี