Js-async-await-gemini
JavaScript Async/Await: จาก Callback สู่ความทันสมัย. การเขียนโปรแกรมแบบ Asynchronous (อะซิงโครนัส) เป็นหัวใจสำคัญของการทำเว็บแอปพลิเคชันให้ลื่นไหล ในบทความนี้เราจะมาทำความเข้าใจว่าทำไมเราถึงต้องใช้มัน และวิวัฒนาการจากอดีตจนถึงปัจจุบันคือ `async/await`
เนื้อหา
ธรรมชาติของ JavaScript: Single-Threaded
JavaScript เป็นภาษาแบบ Single-Threaded หมายความว่ามันมี "สมอง" หรือ Call Stack เพียงอันเดียว ทำงานได้ทีละคำสั่ง (One thing at a time)
สมมติว่าคุณเปิดร้านอาหารตามสั่งที่มีพ่อครัวคนเดียว:
- Synchronous (แบบปกติ): ลูกค้า A สั่งกะเพรา -> พ่อครัวทำกะเพรา -> ลูกค้า A ได้กิน -> ลูกค้า B สั่งไข่เจียว -> พ่อครัวทำไข่เจียว
- ปัญหา: ถ้าลูกค้า A สั่งเมนูที่ใช้เวลาทำนานมาก (เช่น ต้มซุป 1 ชั่วโมง) ลูกค้า B จะต้องรอจนกว่าลูกค้า A จะได้ของ ถึงจะเริ่มสั่งได้ ร้านจะค้าง (Blocking)
- Asynchronous (แบบไม่รอ): ลูกค้า A สั่งซุป -> พ่อครัวรับออเดอร์แล้วตั้งหม้อไว้ (ส่งงานไปทำเบื้องหลัง) -> หันมารับออเดอร์ลูกค้า B ต่อทันที -> พอซุปเสร็จค่อยเสิร์ฟลูกค้า A
ในโปรแกรมจริง งานที่กินเวลานานคือการดึงข้อมูลจาก Server, การอ่านไฟล์, หรือการตั้งเวลา (Timer) ถ้าเราทำแบบ Synchronous หน้าเว็บจะค้าง กดอะไรไม่ได้เลย เราจึงต้องใช้วิธี Asynchronous
ยุคที่ 1: Callback Hell (นรกของคนรอ)
ในสมัยก่อน เราใช้ Callback function เพื่อบอกว่า "ถ้างานเสร็จแล้ว ให้ทำฟังก์ชันนี้นะ"
- ปัญหา
เมื่อมีการทำงานต่อกันหลายๆ ขั้นตอน โค้ดจะซ้อนกันลึกเข้าไปเรื่อยๆ จนเป็นรูปทรงพีระมิด หรือที่เรียกว่า Callback Hell ซึ่งอ่านยากและแก้ไขยากมาก
ตัวอย่าง: การโหลดข้อมูลผู้ใช้ -> โหลดโพสต์ของผู้ใช้ -> โหลดคอมเมนต์ล่าสุด
function getUser(id, callback) {
setTimeout(() => {
console.log("ได้ข้อมูลผู้ใช้แล้ว");
callback({ id: id, name: "Somchai" });
}, 1000);
}
function getPosts(user, callback) {
setTimeout(() => {
console.log("ได้โพสต์ของ " + user.name);
callback(["Post 1", "Post 2"]);
}, 1000);
}
function getComments(post, callback) {
setTimeout(() => {
console.log("ได้คอมเมนต์ของ " + post);
callback(["Cool!", "Nice!"]);
}, 1000);
}
// การเรียกใช้แบบ Callback Hell
getUser(1, function(user) {
getPosts(user, function(posts) {
getComments(posts[0], function(comments) {
console.log("จบการทำงาน: " + comments);
// ถ้ามี error ต้องดักจับในทุกชั้นเอง
});
});
});
ยุคที่ 2: Promise (คำสัญญา)
เพื่อแก้ปัญหา Callback Hell จึงเกิด Promise ขึ้นมาใน ES6 (2015)
Promise คือวัตถุที่แทนค่าที่ "อาจจะมีในอนาคต" (สำเร็จ หรือ ล้มเหลว) ช่วยให้เราเขียนโค้ดเป็นลำดับลงมา (Chain) ด้วย `.then()` ได้ ทำให้โค้ดแบนราบลง
ตัวอย่าง: แปลงฟังก์ชันข้างบนให้คืนค่าเป็น Promise
// สมมติว่าฟังก์ชันเหล่านี้คืนค่าเป็น Promise แล้ว
getUser(1)
.then(user => {
return getPosts(user); // ส่งต่อให้ .then ถัดไป
})
.then(posts => {
return getComments(posts[0]);
})
.then(comments => {
console.log("จบการทำงาน: " + comments);
})
.catch(error => {
// ดักจับ error ได้ในจุดเดียว
console.error(error);
});
ถึงแม้จะดีขึ้น แต่ก็ยังมีวงเล็บและ `.then()` เยอะอยู่ดี ถ้าลอจิกซับซ้อน
ยุคปัจจุบัน: Async / Await
มาถึง keyword ของเรา Async/Await (ES2017) มันคือ "น้ำตาลเคลือบ" (Syntactic Sugar) ของ Promise ที่ช่วยให้เราเขียนโค้ด Asynchronous ให้ดูเหมือนโค้ด Synchronous ปกติ!
- async: ใส่หน้า function เพื่อบอกว่าฟังก์ชันนี้จะทำงานแบบ Async และจะคืนค่าเป็น Promise เสมอ
- await: ใส่หน้า Promise เพื่อสั่งให้ "หยุดรอ" บรรทัดนี้จนกว่าจะได้ผลลัพธ์ (โดยไม่บล็อกการทำงานส่วนอื่นของโปรแกรมหลัก)
ตัวอย่าง: การทำงานเดียวกันที่เขียนง่ายที่สุด
async function showUserComments() {
try {
// โค้ดอ่านง่ายเหมือนบรรทัดต่อบรรทัด
const user = await getUser(1);
const posts = await getPosts(user);
const comments = await getComments(posts[0]);
console.log("จบการทำงาน: " + comments);
} catch (error) {
// ใช้ try...catch แบบปกติในการดัก error
console.error("เกิดข้อผิดพลาด:", error);
}
}
showUserComments();
ตารางเปรียบเทียบ
| คุณสมบัติ | Callback | Promise (.then) | Async / Await |
|---|---|---|---|
| ความอ่านง่าย | ยาก (ซ้อนลึก) | ปานกลาง (Chain ยาว) | ง่ายที่สุด (เหมือนโค้ดปกติ) |
| การจัดการ Error | ต้องเช็คทุกชั้น | ใช้ .catch() | ใช้ try...catch |
| ลำดับการทำงาน | ดูยากเมื่อซับซ้อน | ชัดเจนขึ้น | เป็นธรรมชาติ |
| Browser Support | ทุกรุ่น | เกือบทุกรุ่น | Browser สมัยใหม่ (Modern) |
สรุป
- JavaScript ทำงานแบบ Single-Threaded
- หลีกเลี่ยง Callback Hell เพราะดูแลรักษายาก
- Promise ช่วยจัดระเบียบการรอข้อมูล
- Async/Await คือวิธีที่ดีที่สุดในปัจจุบัน ทำให้โค้ดอ่านง่าย สะอาด และจัดการ Error ได้ด้วย `try...catch` ที่คุ้นเคย
ถ้าเริ่มโปรเจกต์ใหม่ หรือเรียนรู้ตอนนี้ ให้โฟกัสที่การใช้ Async/Await เป็นหลัก
ตัวอย่างโปรแกรมสำหรับทดลอง
- ส่วนนี้ไม่ได้เขียนด้วย Gemini แต่มาจาก js/async
Code เริ่มต้น
function doSomething(msg) {
let t = Math.random()*1500;
setTimeout(() => {
console.log(msg + " " + t);
}, t);
}
console.log("----started----");
doSomething("A ");
doSomething(" B ");
doSomething(" C ");
doSomething(" D ");
doSomething(" E ");
console.log("---------------");
Callback
function doSomething(msg, callback) {
let t = Math.random()*1500;
setTimeout(() => {
console.log(msg + " " + t);
if (callback !== undefined) callback();
}, t);
}
console.log("----started----");
doSomething("A ", () => {
doSomething(" B ", () => {
doSomething(" C ", () => {
doSomething(" D ", () => {
doSomething(" E ");
});
});
});
});
console.log("---------------");
Promise
function doSomething(msg) {
return new Promise((resolve,reject) => {
let t = Math.random()*1500;
setTimeout(() => {
console.log(msg + " " + t);
resolve();
}, t);
});
}
console.log("----started----");
doSomething("A ")
.then(() => {return doSomething(" B ")})
.then(() => {return doSomething(" C ")})
.then(() => {return doSomething(" D ")})
.then(() => {return doSomething(" E")});
console.log("---------------");
Async / await
function doSomething(msg) {
return new Promise((resolve,reject) => {
let t = Math.random()*1500;
setTimeout(() => {
console.log(msg + " " + t);
resolve();
}, t);
});
}
async function main() {
await doSomething("A ");
await doSomething(" B ");
await doSomething(" C ");
await doSomething(" D ");
await doSomething(" E");
return 10;
}
function main2alternative() {
return (main()
.then((x) => { return doSomething(" Z " + x); }));
}
async function main2() {
let x = await main();
await doSomething(" Z " + x);
}
async function main3() {
await main2();
await doSomething("---------");
}
console.log("----started----");
main3();
console.log("---------------");