Asynchronous Programming คือรูปแบบการทำงานที่ช่วยให้โปรแกรมสามารถดำเนินการอื่นต่อไปได้ทันที โดยไม่ต้องรอผลลัพธ์จากคำสั่งที่ใช้เวลานาน (เช่น การเรียก API, การอ่านไฟล์) ทำให้โปรแกรมไม่หยุดนิ่ง (Non-blocking)
ใน TypeScript เราใช้ Promise, async, และ await ในการจัดการการทำงานแบบ Asynchronous
1. ความเข้าใจพื้นฐาน: Promise
Promise คือ Object ใน TypeScript/JavaScript ที่เป็นตัวแทนของผลลัพธ์ที่จะเกิดขึ้นในอนาคต (คล้ายกับ Future ใน Dart) Promise มี 3 สถานะ:
-
Pending: กำลังรอผลลัพธ์
-
Fulfilled (Resolved): ทำงานสำเร็จและส่งค่ากลับมา
-
Rejected: เกิดข้อผิดพลาด
ปัญหาของการเรียกใช้แบบ Synchronous
หากเราเรียกใช้ฟังก์ชัน Asynchronous โดยตรงเหมือนฟังก์ชันทั่วไป จะได้ผลลัพธ์เป็น Object ที่ยังไม่เสร็จสมบูรณ์ (Pending Promise)
// ฟังก์ชันจำลองการทำงานที่ใช้เวลา 4 วินาที และคืนค่าเป็น Promise
function getUserOrder(): Promise<string> {
return new Promise((resolve) => {
// ใช้ setTimeout จำลองการทำงานที่ต้องรอ
setTimeout(() => {
resolve('Large Latte');
}, 4000);
});
}
// ฟังก์ชันนี้ไม่ได้ถูกกำหนดให้เป็น async
function createOrderMessage() {
// การเรียกใช้แบบนี้จะไม่ได้รอผลลัพธ์
const order = getUserOrder();
return 'Your order is: ' + order;
}
console.log(createOrderMessage());
// Output: Your order is: [object Promise] หรือ Promise { <pending> }
// ซึ่งไม่ใช่ผลลัพธ์ "Large Latte" ที่เราต้องการ
2. การใช้งาน async และ await
เพื่อแก้ปัญหาข้างต้น เราใช้คีย์เวิร์ด async และ await เพื่อเปลี่ยนรูปแบบการเขียน Asynchronous ให้ดูเหมือนการทำงานแบบลำดับ (Sequential)
A. async Keyword
-
ใช้กำกับหน้าฟังก์ชันเพื่อประกาศว่าเป็น Asynchronous Function
-
ฟังก์ชัน
asyncจะคืนค่าเป็น Promise<T> เสมอ (โดยที่ $T$ คือชนิดข้อมูลที่ฟังก์ชันควรจะคืนค่าตามปกติ)
B. await Keyword
-
ใช้กำกับหน้า Promise เพื่อบอกให้โปรแกรม หยุดรอ จนกว่า
Promiseนั้นจะสำเร็จ (Resolved) แล้วจึงนำค่าสุดท้ายมาใช้ -
สามารถใช้ await ได้เฉพาะ ภายใน ฟังก์ชันที่ถูกกำหนดด้วย async เท่านั้น
ตัวอย่างการปรับใช้ async/await:
// 1. กำหนดให้ฟังก์ชันเป็น async และระบุชนิดข้อมูลที่คืนค่าเป็น Promise<string>
async function createOrderMessage(): Promise<string> {
// 2. ใช้ await เพื่อรอค่าสุดท้ายจาก Promise ก่อนนำไปใช้งาน
const order = await getUserOrder();
// โค้ดจะหยุดที่บรรทัดบนจนกว่าจะได้ค่า order
return 'Your order is: ' + order;
}
// เพื่อให้สามารถใช้ await ใน Top-Level (เช่น main/เริ่มต้นโปรแกรม)
// เราต้องกำหนดให้ฟังก์ชันหลักเป็น async ด้วย
async function main() {
console.log("Run 1: Starting order...");
// 3. ใช้ await เพื่อรอผลลัพธ์ของ createOrderMessage() ก่อนไปคำสั่งถัดไป
console.log(await createOrderMessage());
console.log("Run 2: Order completed.");
}
main();
3. ลำดับการทำงาน (Flow Control)
การใช้ await ไม่ได้ทำให้โปรแกรมทั้งหมดหยุดทำงาน แต่จะทำให้ ฟังก์ชัน async นั้น ๆ หยุดรอชั่วคราว เพื่อให้คำสั่งอื่นที่ไม่เกี่ยวข้องสามารถทำงานต่อไปได้ก่อน
ในตัวอย่างนี้ (สมมติ getUserOrder ใช้เวลา 4 วินาที):
-
Run 1: Starting order...จะแสดงทันที -
โปรแกรมจะเข้าสู่
await createOrderMessage()และ ปล่อยให้ฟังก์ชันmainหยุดรอ -
หลังจาก 4 วินาที
createOrderMessageทำงานเสร็จสิ้นและคืนค่า -
ข้อความ
Your order is: Large Latteจะแสดง -
Run 2: Order completed.จะแสดงเป็นลำดับสุดท้าย
4. การจัดการข้อผิดพลาด (Error Handling)
ในการจัดการข้อผิดพลาด (Rejected Promise) ที่เกิดขึ้นจากการทำงานแบบ Asynchronous เราใช้คำสั่ง try...catch รอบคำสั่ง await
async function fetchUser(id: number): Promise<string> {
if (id !== 1) {
// หากเกิดข้อผิดพลาด ให้ reject Promise
return Promise.reject(new Error("User not found"));
}
return "User: Alice"; // resolve Promise
}
async function runFetch() {
try {
const user = await fetchUser(99); // จะเกิด error
console.log(user);
} catch (error) {
// ดักจับ error จาก Promise.reject()
console.error("Error fetching data:", error.message);
}
}
runFetch(); // Output: Error fetching data: User not found