การเรียกใช้ AI API โดยไม่มีการควบคุม timeout อาจทำให้แอปพลิเคชันค้างหรือทำให้ผู้ใช้งานรอนานเกินไป ในบทความนี้เราจะมาเรียนรู้วิธีตั้งค่า request timeout และใช้ AbortController เพื่อยกเลิก request ที่ไม่จำเป็น พร้อมตารางเปรียบเทียบบริการต่างๆ รวมถึง HolySheep AI ที่มี latency ต่ำกว่า 50 มิลลิวินาทีและราคาประหยัดกว่า 85%
ตารางเปรียบเทียบบริการ AI API
| บริการ | Base URL | ราคา (ต่อล้าน tokens) | Latency เฉลี่ย | วิธีการชำระเงิน | Timeout Default |
|---|---|---|---|---|---|
| HolySheep AI | api.holysheep.ai | GPT-4.1: $8 Claude Sonnet 4.5: $15 Gemini 2.5 Flash: $2.50 DeepSeek V3.2: $0.42 |
<50ms | WeChat, Alipay, บัตรเครดิต | 60 วินาที |
| API อย่างเป็นทางการ | ระบุแยกตามผู้ให้บริการ | ราคามาตรฐาน | 100-500ms | บัตรเครดิตเท่านั้น | แตกต่างกันไป |
| บริการรีเลย์อื่นๆ | แตกต่างกันไป | ประหยัด 30-60% | 80-300ms | แตกต่างกันไป | 30-120 วินาที |
ทำไมต้องใช้ Timeout และ Abort Controller
เมื่อเรียกใช้ AI API อย่าง HolySheep AI ที่มีอัตรา ¥1=$1 (ประหยัด 85%+) เราต้องควบคุม request lifecycle อย่างเหมาะสมเพื่อ:
- ป้องกันแอปพลิเคชันค้างเมื่อ API ตอบสนองช้า
- ประหยัดทรัพยากรเซิร์ฟเวอร์และลดค่าใช้จ่าย
- ให้ประสบการณ์ผู้ใช้ที่ดีด้วยการแสดงข้อผิดพลาดที่เหมาะสม
- รองรับการยกเลิก request ที่ผู้ใช้ต้องการ
การตั้งค่า Timeout พื้นฐาน
วิธีที่ง่ายที่สุดคือการใช้ AbortController ร่วมกับ signal สำหรับ timeout
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 วินาที
async function callHolySheepAPI() {
try {
const response = await fetch('https://api.holysheep.ai/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_HOLYSHEEP_API_KEY'
},
body: JSON.stringify({
model: 'gpt-4.1',
messages: [{ role: 'user', content: 'ทักทายฉัน' }]
}),
signal: controller.signal
});
clearTimeout(timeoutId);
const data = await response.json();
console.log(data);
return data;
} catch (error) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
console.error('Request ถูกยกเลิกเนื่องจาก timeout');
}
throw error;
}
}
การสร้าง Utility Function สำหรับ Timeout ที่ใช้ซ้ำได้
เพื่อให้โค้ดสะอาดและนำกลับมาใช้ใหม่ได้ เราควรสร้าง helper function
function fetchWithTimeout(url, options = {}, timeout = 30000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
const fetchOptions = {
...options,
signal: controller.signal
};
return fetch(url, fetchOptions)
.then(response => {
clearTimeout(timeoutId);
return response;
})
.catch(error => {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw new Error(Request timeout หลังจาก ${timeout}ms);
}
throw error;
});
}
// ตัวอย่างการใช้งาน
async function getAIResponse() {
const response = await fetchWithTimeout(
'https://api.holysheep.ai/v1/chat/completions',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_HOLYSHEEP_API_KEY'
},
body: JSON.stringify({
model: 'gpt-4.1',
messages: [{ role: 'user', content: 'อธิบายเรื่อง AI' }]
})
},
15000 // 15 วินาที
);
return response.json();
}
การใช้ AbortController สำหรับการยกเลิก Request
นอกจาก timeout แล้ว AbortController ยังมีประโยชน์สำหรับการยกเลิก request ที่ผู้ใช้ต้องการ
class AIClient {
constructor() {
this.controller = null;
}
async sendMessage(userMessage) {
// ยกเลิก request ก่อนหน้าถ้ามี
this.cancelCurrentRequest();
// สร้าง controller ใหม่
this.controller = new AbortController();
try {
const response = await fetch('https://api.holysheep.ai/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_HOLYSHEEP_API_KEY'
},
body: JSON.stringify({
model: 'deepseek-v3.2',
messages: [{ role: 'user', content: userMessage }]
}),
signal: this.controller.signal
});
const data = await response.json();
return data.choices[0].message.content;
} catch (error) {
if (error.name === 'AbortError') {
return 'Request ถูกยกเลิก';
}
throw error;
}
}
cancelCurrentRequest() {
if (this.controller) {
this.controller.abort();
this.controller = null;
}
}
}
// การใช้งาน
const client = new AIClient();
// ส่งข้อความ
const promise = client.sendMessage('ทดสอบ');
// ยกเลิกภายใน 5 วินาที
setTimeout(() => {
client.cancelCurrentRequest();
}, 5000);
การตั้งค่า Timeout ตามประเภท Request
ควรตั้งค่า timeout แตกต่างกันตามประเภทของ request
const TIMEOUT_CONFIG = {
chat: 30000, // 30 วินาที สำหรับ chat ปกติ
streaming: 60000, // 60 วินาที สำหรับ streaming response
embedding: 15000, // 15 วินาที สำหรับ embedding
image: 120000 // 120 วินาที สำหรับ image generation
};
async function callAPI(endpoint, payload, type = 'chat') {
const timeout = TIMEOUT_CONFIG[type] || 30000;
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(https://api.holysheep.ai/v1/${endpoint}, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_HOLYSHEEP_API_KEY'
},
body: JSON.stringify(payload),
signal: controller.signal
});
clearTimeout(timeoutId);
return await response.json();
} catch (error) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw new Error(${type} request timeout หลังจาก ${timeout}ms);
}
throw error;
}
}
// การใช้งาน
const chatResult = await callAPI('chat/completions', { model: 'gpt-4.1', messages: [] }, 'chat');
const embedResult = await callAPI('embeddings', { model: 'text-embedding-3-small', input: 'ข้อความ' }, 'embedding');
การจัดการ Streaming Response ด้วย Timeout
สำหรับ streaming response ต้องจัดการ timeout อย่างระมัดระวัง
async function* streamWithTimeout(url, options, timeoutMs = 60000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
let lastActivity = Date.now();
try {
const response = await fetch(url, {
...options,
signal: controller.signal
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) {
clearTimeout(timeoutId);
break;
}
lastActivity = Date.now();
yield decoder.decode(value);
// รีเซ็ต timeout ทุกครั้งที่ได้รับข้อมูล
clearTimeout(timeoutId);
const idleTimeoutId = setTimeout(() => controller.abort(), timeoutMs);
}
} catch (error) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
const idleTime = Date.now() - lastActivity;
throw new Error(Streaming timeout: idle ${idleTime}ms);
}
throw error;
}
}
// การใช้งาน streaming
async function testStreaming() {
try {
for await (const chunk of streamWithTimeout(
'https://api.holysheep.ai/v1/chat/completions',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_HOLYSHEEP_API_KEY'
},
body: JSON.stringify({
model: 'gpt-4.1',
messages: [{ role: 'user', content: 'เล่าเรื่องยาวๆ' }],
stream: true
})
},
30000
)) {
process.stdout.write(chunk);
}
} catch (error) {
console.error('Streaming error:', error.message);
}
}
ข้อผิดพลาดที่พบบ่อยและวิธีแก้ไข
1. ปัญหา: Timeout ไม่ทำงานเมื่อใช้ streaming
สาเหตุ: เมื่อใช้ streaming ด้วย fetch API ปกติ timeout จะไม่ถูกยกเลิกเมื่อได้รับข้อมูลแต่ละส่วน
// ❌ วิธีที่ผิด - timeout จะนับจากเริ่ม request เท่านั้น
const response = await fetch(url, { signal: AbortSignal.timeout(30000) });
// ✅ วิธีที่ถูก - รีเซ็ต timeout ทุกครั้งที่ได้รับข้อมูล
class StreamingTimeoutController {
constructor(timeout) {
this.timeout = timeout;
this.abortController = new AbortController();
this.resetTimer();
}
resetTimer() {
if (this.timer) clearTimeout(this.timer);
this.timer = setTimeout(() => {
this.abortController.abort();
}, this.timeout);
}
get signal() {
return this.abortController.signal;
}
destroy() {
if (this.timer) clearTimeout(this.timer);
}
}
2. ปัญหา: Memory leak จาก timeout ที่ไม่ถูก clear
สาเหตุ: เมื่อ request เสร็จสิ้นหรือถูกยกเลิกแต่ timeout ไม่ถูก clear จะทำให้ memory เพิ่มขึ้น
// ❌ วิธีที่ผิด - ไม่มีการ cleanup ที่ดี
async function badRequest() {
setTimeout(() => controller.abort(), 10000);
return fetch(url, { signal: controller.signal });
}
// ✅ วิธีที่ถูก - ใช้ finally เพื่อ cleanup เสมอ
async function goodRequest() {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000);
try {
return await fetch(url, { signal: controller.signal });
} finally {
clearTimeout(timeoutId);
// ยกเลิก controller ด้วยเผื่อมี request ค้าง
controller.abort();
}
}
// หรือใช้ AbortSignal.timeout() ที่มีใน modern browsers
async function modernRequest() {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000);
try {
const response = await fetch(url, {
signal: AbortSignal.timeout(10000)
});
return response;
} catch (error) {
if (error.name === 'TimeoutError') {
console.error('Request timeout');
}
throw error;
} finally {
clearTimeout(timeoutId);
}
}
3. ปัญหา: Race condition เมื่อมี request หลายตัวพร้อมกัน
สาเหตุ: เมื่อส่ง request หลายตัวพร้อมกันและยกเลิกตัวหนึ่ง อาจกระทบ request อื่น
// ❌ วิธีที่ผิด - ใช้ shared controller
let sharedController = new AbortController();
async function makeRequest1() {
return fetch(url1, { signal: sharedController.signal });
}
async function makeRequest2() {
sharedController.abort(); // ยกเลิกทั้งหมด!
sharedController = new AbortController();
return fetch(url2, { signal: sharedController.signal });
}
// ✅ วิธีที่ถูก - แยก controller หรือใช้ AbortSignal.any()
async function independentRequests() {
const controller1 = new AbortController();
const controller2 = new AbortController();
// ส่งทั้งสอง request
const [result1, result2] = await Promise.all([
fetch(url1, { signal: controller1.signal }).catch(e => ({ error: e })),
fetch(url2, { signal: controller2.signal }).catch(e => ({ error: e }))
]);
// สามารถยกเลิกทีละตัวได้
// controller1.abort(); // ยกเลิกเฉพาะ request 1
}
// ✅ หรือใช้ AbortSignal.any() สำหรับกรณีที่ต้องการยกเลิกเมื่อตัวใดตัวหนึ่งเสร็จ
async function raceWithCleanup() {
const controller1 = new AbortController();
const controller2 = new AbortController();
const signal = AbortSignal.any([
controller1.signal,
controller2.signal,
AbortSignal.timeout(30000)
]);
const response = await fetch(url, { signal });
return response;
}
4. ปัญหา: Error จาก AbortError ไม่ถูกจัดการอย่างเหมาะสม
สาเหตุ: การ throw AbortError โดยไม่มีการแยกประเภทว่าเป็น timeout หรือ user cancel
// ❌ วิธีที่ผิด - ไม่แยกประเภท error
try {
await fetchWithTimeout(url);
} catch (error) {
// ไม่รู้ว่าเป็น timeout หรือ user cancel
console.error(error);
}
// ✅ วิธีที่ถูก - แยกประเภท error อย่างชัดเจน
class TimeoutError extends Error {
constructor(timeout) {
super(Request timeout หลังจาก ${timeout}ms);
this.name = 'TimeoutError';
}
}
class UserCancelledError extends Error {
constructor() {
super('Request ถูกยกเลิกโดยผู้ใช้');
this.name = 'UserCancelledError';
}
}
async function fetchWithTypedErrors(url, timeout = 30000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
return await fetch(url, { signal: controller.signal });
} catch (error) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
// ตรวจสอบว่าเป็น timeout หรือ user cancel
if (timeoutId._destroyed) {
throw new UserCancelledError();
}
throw new TimeoutError(timeout);
}
throw error;
}
}
// การใช้งาน
try {
await fetchWithTypedErrors(url, 15000);
} catch (error) {
if (error instanceof TimeoutError) {
console.error('Request ใช้เวลานานเกินไป');
} else if (error instanceof UserCancelledError) {
console.error('ผู้ใช้ยกเลิก request');
} else {
console.error('เกิดข้อผิดพลาดอื่น:', error.message);
}
}
สรุป
การตั้งค่า request timeout และใช้ AbortController อย่างถูกต้องเป็นสิ่งสำคัญสำหรับการพัฒนาแอปพลิเคชันที่ใช้ AI API จุดสำคัญที่ต้องจำ:
- ใช้ AbortController สำหรับทั้ง timeout และการยกเลิกโดยผู้ใช้
- Clear timeout เสมอใน finally block หรือเมื่อ request เสร็จสิ้น
- ตั้งค่า timeout แตกต่างกันตามประเภทของ request
- แยกประเภท error อย่างชัดเจนระหว่าง timeout, user cancel และ error อื่นๆ
- สำหรับ streaming response ต้องรีเซ็ต timeout ทุกครั้งที่ได้รับข้อมูล
เมื่อใช้บริการ HolySheep AI ที่มี latency <50ms คุณสามารถตั้งค่า timeout สั้นลงได้ ทำให้แอปพลิเคชันตอบสนองเร็วขึ้นและประหยัดทรัพยากรมากขึ้น
👉 สมัคร HolySheep AI — รับเครดิตฟรีเมื่อลงทะเบียน