Category: reference
JavaScript Promises & Async/Await — จัดการ Asynchronous อย่างถูกต้อง
อธิบาย Promise chain, async/await, error handling, Promise.all/race/allSettled และ pitfalls ที่พบบ่อยเมื่อเขียน async JavaScript ใน TypeScript
สารบัญ
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 ทุกครั้ง