ข้ามไปเนื้อหาหลัก

Category: reference

JavaScript Promises & Async/Await — จัดการ Asynchronous อย่างถูกต้อง

อธิบาย Promise chain, async/await, error handling, Promise.all/race/allSettled และ pitfalls ที่พบบ่อยเมื่อเขียน async JavaScript ใน TypeScript

· อ่านประมาณ 3 นาที

สารบัญ

Promise คืออะไร

Promise คือ object ที่แทน “งานที่จะเสร็จในอนาคต” มี 3 สถานะ:

pending  →  fulfilled (resolved)
         →  rejected
const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    if (Math.random() > 0.5) {
      resolve('สำเร็จ');
    } else {
      reject(new Error('ล้มเหลว'));
    }
  }, 1000);
});

async/await — อ่านง่ายกว่า .then chain

// แบบ .then chain (อ่านยากเมื่อซ้อนหลายชั้น)
fetch('/api/user')
  .then(res => res.json())
  .then(user => fetch(`/api/posts/${user.id}`))
  .then(res => res.json())
  .catch(err => console.error(err));

// แบบ async/await (อ่านคล้าย synchronous code)
async function getUserPosts() {
  try {
    const res = await fetch('/api/user');
    const user = await res.json();
    const postsRes = await fetch(`/api/posts/${user.id}`);
    return await postsRes.json();
  } catch (err) {
    console.error('Error:', err);
    throw err;
  }
}

Error Handling

// ❌ ลืม try/catch — unhandled rejection
async function bad() {
  const data = await fetch('/api/data').then(r => r.json());
  return data;
}

// ✅ wrap ทุก async function ด้วย try/catch
async function good() {
  try {
    const res = await fetch('/api/data');
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    return await res.json();
  } catch (err) {
    console.error('Failed to fetch:', err);
    return null;
  }
}

// ✅ helper สำหรับ error tuple (Go-style)
async function tryCatch<T>(
  promise: Promise<T>
): Promise<[T, null] | [null, Error]> {
  try {
    return [await promise, null];
  } catch (err) {
    return [null, err instanceof Error ? err : new Error(String(err))];
  }
}

const [data, err] = await tryCatch(fetch('/api').then(r => r.json()));
if (err) return; // handle error
console.log(data); // data เป็น T แน่นอน

Promise combinators

const urls = ['/api/a', '/api/b', '/api/c'];

// Promise.all — รัน parallel, fail ถ้าอันใดอันหนึ่ง reject
const [a, b, c] = await Promise.all(urls.map(u => fetch(u).then(r => r.json())));

// Promise.allSettled — รัน parallel, ไม่ fail แม้บางอัน reject
const results = await Promise.allSettled(urls.map(u => fetch(u)));
for (const result of results) {
  if (result.status === 'fulfilled') {
    console.log('ok:', result.value);
  } else {
    console.log('fail:', result.reason);
  }
}

// Promise.race — เอาผลลัพธ์จากอันที่เสร็จก่อน
const fastest = await Promise.race([
  fetch('/api/primary'),
  fetch('/api/fallback'),
]);

// Promise.any — เอาผลลัพธ์จากอันแรกที่ resolve (ไม่สนใจ reject)
const first = await Promise.any([
  fetch('/cdn1/file.js'),
  fetch('/cdn2/file.js'),
]);

Pitfall ที่พบบ่อย

// ❌ await ใน loop ทีละอัน (ช้ามาก)
for (const id of ids) {
  const user = await fetchUser(id); // รอทีละ request
  users.push(user);
}

// ✅ parallel แทน
const users = await Promise.all(ids.map(id => fetchUser(id)));

// ❌ ลืม await — ได้ Promise แทน value
async function wrong() {
  const data = fetch('/api').then(r => r.json()); // ไม่มี await!
  return data.title; // undefined — data เป็น Promise ไม่ใช่ object
}

// ❌ async ใน forEach ไม่รอ
items.forEach(async (item) => {
  await processItem(item); // forEach ไม่ await!
});
// โปรแกรม continue ก่อนที่ทุก item จะเสร็จ

// ✅ ใช้ for...of แทน
for (const item of items) {
  await processItem(item);
}
// หรือ parallel
await Promise.all(items.map(item => processItem(item)));

AbortController — Cancel Request

const controller = new AbortController();

// cancel หลัง 5 วินาที
const timeout = setTimeout(() => controller.abort(), 5000);

try {
  const res = await fetch('/api/slow-endpoint', {
    signal: controller.signal,
  });
  clearTimeout(timeout);
  return await res.json();
} catch (err) {
  if (err instanceof DOMException && err.name === 'AbortError') {
    console.log('Request timed out');
  } else {
    throw err;
  }
}

ใช้กับ Astro Content Collections

// src/pages/projects/[slug].astro
const { slug } = Astro.params;
if (!slug) return Astro.redirect('/404');

const project = await getEntry('projects', slug);
if (!project) return Astro.redirect('/404');

// render() คือ async — ต้อง await
const { Content } = await render(project);

สรุป: ใช้ async/await เป็น default, ใช้ Promise.all เมื่อต้องการ parallel, อย่าลืม try/catch ทุกครั้ง