ป้ายกำกับ: ไม่ต้องติดตั้ง

TypeScript: Promise แบบขนานTypeScript: Promise แบบขนาน

ในโลกของการพัฒนาซอฟต์แวร์สมัยใหม่ที่เน้นประสบการณ์ผู้ใช้แบบเรียลไทม์ ความสามารถในการจัดการงานอะซิงโครนัส (Asynchronous) จึงเป็นหัวใจสำคัญของแอปพลิเคชันที่ทันสมัย TypeScript ซึ่งสร้างอยู่บนพื้นฐาน JavaScript ได้นำเสนอ Promises เพื่อช่วยให้การเขียนโค้ดที่ไม่ต้องรอคอยผลลัพธ์ตามลำดับได้อย่างปลอดภัย อย่างไรก็ตาม เมื่อเรามีงานหลายอย่างอิสระต่อกันที่เราต้องการเรียกใช้งานพร้อมๆ กัน เช่น การดึงข้อมูลจาก API หลายแหล่ง หรือการประมวลผลชุดคำสั่งจำนวนมาก การใช้ Promise แบบพื้นฐานอาจไม่ตอบโจทย์ด้านประสิทธิภาพสูงสุด

💡 ปัญหาคอขวดของการดำเนินการต่อเนื่อง (Sequential Bottleneck)

หากเรามี Task A, B และ C ที่เป็นภารกิจอิสระต่อกันและไม่มีข้อผูกพันเชิงตรรกะใด ๆ ต่อกัน การใช้ Promise Chaining ด้วยเมธอด .then() ซ้อนติดกัน จะบังคับให้โค้ดทำงานแบบลำดับ (Sequential) เสมอ นั่นหมายถึงแม้ว่าแต่ละ Task จะเสร็จได้รวดเร็วกว่าเวลาหน่วงรวม แต่โปรแกรมหลักจะต้องหยุดชะงักเพื่อ ‘เฝ้ารอ’ ให้ Task ก่อนหน้าจบลงก่อนจึงจะเริ่ม Task ถัดไปได้ ทำให้เกิดคอขวดด้านประสิทธิภาพอย่างชัดเจน

🚀 การแก้ปัญหาด้วย Promise.all()

เมื่อเป้าหมายของเราคือการเรียกใช้งาน Task ที่เป็นอิสระต่อกันพร้อมกัน (Parallel Execution) เราต้องใช้เครื่องมือที่ชื่อว่า Promise.all() เมธอดนี้รับอาร์เรย์ของ Promises เข้าไป และจะรอจนกว่า Promise ทั้งหมดในอาร์เรย์นั้นจะสำเร็จ (Resolve) หรือมี Promise ใด Promise หนึ่งล้มเหลว (Reject) ทันที

การใช้ Promise.all() ทำให้ระบบสามารถส่งคำขอไปยัง Task A, B, และ C ได้เกือบจะในเวลาเดียวกัน (Concurrent) ซึ่งช่วยลดเวลาหน่วงรวม (Total Latency) ได้อย่างมาก ทำให้แอปพลิเคชันมีความรวดเร็วและตอบสนองต่อผู้ใช้ได้ดีขึ้น

ตัวอย่างการใช้งานใน TypeScript

สมมติว่าเรามีฟังก์ชันจำลองการเรียก API 3 ตัวที่ใช้เวลาต่างกัน:

TypeScript

// ฟังก์ชันจำลองการเรียก API ที่ใช้เวลาต่างกัน (Promise) 
const fetchUserData = (id: number): Promise<string> => {
  return new Promise(resolve => {
    setTimeout(() => resolve(`User ${id} data loaded`), 1500);
  });
};

const fetchProductData = (id: number): Promise<string> => {
  return new Promise(resolve => {
    setTimeout(() => resolve(`Product ${id} data loaded`), 500);
  });
};

const fetchOrderData = (id: number): Promise<string> => {
  return new Promise(resolve => {
    setTimeout(() => resolve(`Order ${id} data loaded`), 1000);
  });
});

// --------------------------------------------------
// ❌ วิธีแบบ Sequential (ช้ากว่า)
// fetchUserData(1).then(data => {
//   console.log(data); // ต้องรอ 1.5s
//   return fetchProductData(1);
// }).then(data => {
//   console.log(data); // ต้องรอเพิ่มอีก 0.5s
//   return fetchOrderData(1);
// }).then(data => {
//   console.log(data); // ต้องรอเพิ่มอีก 1s
// });

// ✅ วิธีแบบ Parallel (เร็วที่สุด)
const parallelTasks = Promise.all([
  fetchUserData(1),
  fetchProductData(1),
  fetchOrderData(1)
]);

parallelTasks.then(results => {
  console.log("✅ All data loaded successfully!");
  console.log("Results:", results);
});

สรุปและแนวปฏิบัติที่ดีที่สุด (Best Practices)

การทำความเข้าใจความแตกต่างระหว่างการทำงานแบบลำดับ (Sequential) และแบบขนาน (Parallel) ถือเป็นทักษะสำคัญของ Senior Developer การใช้ Promise.all() เป็นทางออกที่ทรงพลังที่สุดเมื่อคุณต้องการให้งานหลายอย่างเริ่มทำงานพร้อมกันและไม่จำเป็นต้องรอผลลัพธ์ของงานก่อนหน้าเพื่อเริ่มงานถัดไป

อย่างไรก็ตาม ควรระวังการใช้ Promise.all() ในกรณีที่การล้มเหลวของ Task ใด Task หนึ่งควรทำให้ Task อื่น ๆ หยุดทำงานด้วย (Fail-fast) หากคุณต้องการการจัดการข้อผิดพลาดที่ซับซ้อนขึ้น ควรพิจารณาใช้ Promise.allSettled() ซึ่งจะรอให้ทุก Promise ทำงานเสร็จสมบูรณ์ ไม่ว่าจะสำเร็จหรือล้มเหลว ทำให้คุณสามารถวิเคราะห์สถานะของทุก Task ได้อย่างครบถ้วน