APIリクエストのタイムアウトやユーザーの操作中断時に、実行中のリクエストを安全にキャンセルする必要があります。本稿では、JavaScript標準の AbortController を使用して、HolySheep AI のようなLLM APIへのリクエストを安全に中断する方法を実践的に解説します。
AbortController の基本概念
AbortController は、Web標準のAPIで、1つ以上のWebリクエスト(fetch)やDOM操作をプログラム的にキャンセルできます。Promise ベースのAPI呼び出しにおいて、特に有効です。
// AbortController の基本構造
const controller = new AbortController();
const signal = controller.signal;
// リクエストを開始
fetch(url, { signal })
.then(response => response.json())
.then(data => console.log(data))
.catch(err => {
if (err.name === 'AbortError') {
console.log('リクエストがキャンセルされました');
} else {
console.error('エラー:', err);
}
});
// 3秒後に自動的にキャンセル
setTimeout(() => controller.abort(), 3000);
HolySheep AI での実践的実装
HolySheep AI(今すぐ登録)は、レート¥1=$1という業界最安水準の料金体系を提供しており、Claude Sonnet 4.5やGPT-4.1など主要モデルに対応しています。以下に、実際のAPI呼び出しでの中断処理を実装します。
/**
* HolySheep AI API との通信を双方向ストリーミングで実装
* AbortController によるリクエスト中断機能付き
*/
class HolySheepAIClient {
constructor(apiKey) {
this.baseUrl = 'https://api.holysheep.ai/v1';
this.apiKey = apiKey;
this.activeControllers = new Map();
}
async createChatCompletion(messages, options = {}) {
const requestId = options.requestId || crypto.randomUUID();
const controller = new AbortController();
this.activeControllers.set(requestId, controller);
// タイムアウト設定(デフォルト30秒)
const timeoutMs = options.timeout || 30000;
const timeoutId = setTimeout(() => {
console.log(⏱️ リクエスト ${requestId} がタイムアウトしました);
controller.abort();
}, timeoutMs);
try {
const response = await fetch(${this.baseUrl}/chat/completions, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': Bearer ${this.apiKey}
},
body: JSON.stringify({
model: options.model || 'gpt-4.1',
messages: messages,
stream: options.stream || false,
max_tokens: options.maxTokens || 4096,
temperature: options.temperature || 0.7
}),
signal: controller.signal
});
clearTimeout(timeoutId);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(
API Error ${response.status}: ${errorData.error?.message || response.statusText}
);
}
return await response.json();
} catch (error) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw new CancellationError('リクエストがキャンセルされました', requestId);
}
throw error;
} finally {
this.activeControllers.delete(requestId);
}
}
// 特定のリクエストをキャンセル
cancelRequest(requestId) {
const controller = this.activeControllers.get(requestId);
if (controller) {
console.log(🛑 リクエスト ${requestId} をキャンセル中...);
controller.abort();
return true;
}
return false;
}
// すべてのアクティブなリクエストをキャンセル
cancelAllRequests() {
console.log(🛑 ${this.activeControllers.size} 件の全リクエストをキャンセル...);
for (const [id, controller] of this.activeControllers) {
controller.abort();
}
this.activeControllers.clear();
}
}
class CancellationError extends Error {
constructor(message, requestId) {
super(message);
this.name = 'CancellationError';
this.requestId = requestId;
}
}
// 使用例
const client = new HolySheepAIClient('YOUR_HOLYSHEEP_API_KEY');
async function main() {
const requestId = 'req-' + Date.now();
try {
// 10秒タイムアウトでリクエスト送信
const result = await client.createChatCompletion(
[
{ role: 'system', content: 'あなたは有用なアシスタントです。' },
{ role: 'user', content: '100文字以内で自己紹介をお願いします。' }
],
{
requestId,
model: 'gpt-4.1',
timeout: 10000
}
);
console.log('✅ 応答:', result.choices[0].message.content);
console.log(💰 使用トークン: ${result.usage.total_tokens});
} catch (error) {
if (error instanceof CancellationError) {
console.log(❌ キャンセル: ${error.message} (${error.requestId}));
} else {
console.error('❌ エラー:', error.message);
}
}
}
main();
React コンポーネントでの実装例
フロントエンドアプリケーションでは、ユーザーの操作に応じてリクエストをキャンセルする必要があります。以下はReact hooksを活用した実践的な実装です。
import React, { useState, useRef, useCallback, useEffect } from 'react';
/**
* HolySheep AI API 呼び出しを管理するカスタムフック
* - リクエストの自動キャンセル対応
* - タイムアウト管理
* - コンポーネントアンマウント時のクリーンアップ
*/
function useHolySheepChat() {
const [messages, setMessages] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
const controllerRef = useRef(null);
const abortControllerRef = useRef(null);
// メッセージを追加
const addMessage = useCallback((role, content) => {
setMessages(prev => [...prev, { role, content, timestamp: Date.now() }]);
}, []);
// リクエストをキャンセル
const cancelRequest = useCallback(() => {
if (abortControllerRef.current) {
console.log('🛑 リクエストをキャンセルしました');
abortControllerRef.current.abort();
abortControllerRef.current = null;
setIsLoading(false);
}
}, []);
// LLM APIを呼び出し
const sendMessage = useCallback(async (userInput, options = {}) => {
// 既存の進行中のリクエストをキャンセル
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
// 新しいAbortControllerを作成
abortControllerRef.current = new AbortController();
const { signal } = abortControllerRef.current;
// ユーザーメッセージを追加
addMessage('user', userInput);
setIsLoading(true);
setError(null);
const model = options.model || 'claude-sonnet-4.5';
const timeout = options.timeout || 60000;
try {
// タイムアウト Promise
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('リクエストがタイムアウトしました')), timeout);
});
const apiPromise = fetch('https://api.holysheep.ai/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': Bearer ${options.apiKey || 'YOUR_HOLYSHEEP_API_KEY'}
},
body: JSON.stringify({
model: model,
messages: [
...messages.map(m => ({ role: m.role, content: m.content })),
{ role: 'user', content: userInput }
],
max_tokens: options.maxTokens || 2048,
temperature: options.temperature || 0.7
}),
signal
});
const response = await Promise.race([apiPromise, timeoutPromise]);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(
APIエラー: ${response.status} - ${errorData.error?.message || '不明なエラー'}
);
}
const data = await response.json();
const assistantMessage = data.choices[0]?.message?.content || '';
addMessage('assistant', assistantMessage);
return {
success: true,
message: assistantMessage,
usage: data.usage,
model: data.model
};
} catch (err) {
if (err.name === 'AbortError') {
setError('リクエストがキャンセルされました');
return { success: false, cancelled: true };
}
setError(err.message);
return { success: false, error: err.message };
} finally {
setIsLoading(false);
abortControllerRef.current = null;
}
}, [messages, addMessage]);
// コンポーネントのアンマウント時にクリーンアップ
useEffect(() => {
return () => {
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
};
}, []);
return {
messages,
isLoading,
error,
sendMessage,
cancelRequest,
clearMessages: () => setMessages([]),
clearError: () => setError(null)
};
}
// 使用例コンポーネント
export function ChatComponent() {
const {
messages,
isLoading,
error,
sendMessage,
cancelRequest,
clearMessages
} = useHolySheepChat();
const [input, setInput] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
if (!input.trim() || isLoading) return;
const userInput = input;
setInput('');
await sendMessage(userInput, {
model: 'claude-sonnet-4.5',
timeout: 60000
});
};
const handleCancel = () => {
cancelRequest();
};
return (
<div className="chat-container">
<div className="messages">
{messages.map((msg, i) => (
<div key={i} className={message message-${msg.role}}>
<strong>{msg.role === 'user' ? 'あなた' : 'AI'}:</strong>
<p>{msg.content}</p>
</div>
))}
{isLoading && (
<div className="message message-loading">
<span>思考中...</span>
<button onClick={handleCancel} className="cancel-btn">
キャンセル
</button>
</div>
)}
</div>
{error && <div className="error-message">{error}</div>}
<form onSubmit={handleSubmit}>
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="メッセージを入力..."
disabled={isLoading}
/>
<button type="submit" disabled={isLoading || !input.trim()}>
送信
</button>
{isLoading && (
<button type="button" onClick={handleCancel}>
停止
</button>
)}
</form>
{messages.length > 0 && (
<button onClick={clearMessages}>履歴をクリア</button>
)}
</div>
);
}
競合サービス比較
API选择において、价格、送金方式、レイテンシは重要な判断基準です。以下に主要サービスを比較します。
| サービス | レート | GPT-4.1 ($/MTok) |
Claude Sonnet 4.5 ($/MTok) |
Gemini 2.5 Flash ($/MTok) |
DeepSeek V3.2 ($/MTok) |
レイテンシ | 決済手段 | おすすめチーム |
|---|---|---|---|---|---|---|---|---|
| HolySheep AI | ¥1=$1 最安水準 |
$8.00 | $15.00 | $2.50 | $0.42 | <50ms | WeChat Pay Alipay Visa/Master |
中華圏開発チーム コスト重視の企業 |
| OpenAI 直差し | ¥7.3=$1 | $8.00 | - | - | - | 100-300ms | 国際カードのみ | ,米国の規制対応が必要なプロジェクト |
| Anthropic 直差し | ¥7.3=$1 | - | $15.00 | - | - | 150-400ms | 国際カードのみ | Claude優先プロジェクト |
| Azure OpenAI | ¥7.8=$1 +α料金 |
$8.00+ | - | - | - | 200-500ms | 法人請求書 | 大企業・規制業界 |
筆者の実践経験:私は以前、複数のAPIプロバイダーを試しましたが、HolySheheep AI 注册后即赠送免费积分,配合WeChat Pay/Alipay対応により、中華圏のチームとの協業において非常にスムーズな決済が実現できました。特に¥1=$1のレートは、コスト最適化の観点から大きな優位性があります。
よくあるエラーと対処法
1. AbortError: The user aborted a request
原因:リクエストが外部で中断された(cancel()呼び出し、タイムアウト、ユーザー操作など)
// ❌ 悪い例:AbortErrorを適切に処理していない
try {
const response = await fetch(url, { signal });
return await response.json();
} catch (error) {
console.error('APIエラー:', error);
// AbortErrorも通常のエラーとして扱われる
}
// ✅ 良い例:AbortErrorを明示的に処理
try {
const response = await fetch(url, { signal });
return await response.json();
} catch (error) {
if (error.name === 'AbortError') {
console.log('リクエストがキャンセルされました');
return { cancelled: true, data: null };
}
console.error('APIエラー:', error);
throw error;
}
2. Failed to fetch / Network Error
原因:CORS問題、ネットワーク切断、URL不正など
// ネットワークエラーの検出と処理
async function safeFetch(url, options) {
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 30000);
const response = await fetch(url, {
...options,
signal: options.signal || controller.signal
});
clearTimeout(timeoutId);
if (!response.ok) {
const errorBody = await response.text();
throw new APIError(
HTTP ${response.status},
response.status,
errorBody
);
}
return response;
} catch (error) {
if (error.name === 'AbortError') {
throw new TimeoutError('リクエストがタイムアウトしました');
}
if (error.message === 'Failed to fetch' || error.message.includes('NetworkError')) {
throw new NetworkError('ネットワーク接続を確認してください');
}
throw error;
}
}
class APIError extends Error {
constructor(message, status, body) {
super(message);
this.name = 'APIError';
this.status = status;
this.body = body;
}
}
class TimeoutError extends Error {
constructor(message) {
super(message);
this.name = 'TimeoutError';
}
}
class NetworkError extends Error {
constructor(message) {
super(message);
this.name = 'NetworkError';
}
}
3. CORSエラー(Access-Control-Allow-Origin 欠如)
原因:ブラウザからの直接呼び出しでCORSヘッダーが返されていない
// 解決策1:サーバーサイドプロキシを使用
// Next.js / Express の例
// server/proxy.js
app.post('/api/holysheep', async (req, res) => {
const { messages, model } = req.body;
try {
const response = await fetch('https://api.holysheep.ai/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': Bearer ${process.env.HOLYSHEEP_API_KEY}
},
body: JSON.stringify({ messages, model })
});
const data = await response.json();
res.json(data);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// フロントエンドからは自サーバーのエンドポイントを呼び出す
async function callWithProxy(messages, model) {
const response = await fetch('/api/holysheep', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ messages, model })
});
return response.json();
}
// 解決策2:fetchのmode設定を確認
// サーバー間通信ではmode: 'cors'が明示的に必要になる場合がある
const response = await fetch('https://api.holysheep.ai/v1/models', {
method: 'GET',
mode: 'cors',
headers: {
'Authorization': Bearer YOUR_HOLYSHEEP_API_KEY
}
});
4. Invalid API Key 401エラー
原因:APIキーが未設定、有効期限切れ、または不正
// APIキー検証関数
async function validateAPIKey(apiKey) {
if (!apiKey || apiKey === 'YOUR_HOLYSHEEP_API_KEY') {
throw new Error(
'APIキーが設定されていません。' +
'https://www.holysheep.ai/register で取得してください。'
);
}
try {
const response = await fetch('https://api.holysheep.ai/v1/models', {
headers: {
'Authorization': Bearer ${apiKey}
}
});
if (response.status === 401) {
throw new Error(
'APIキーが無効です。ダッシュボードで新しいキーを生成してください。'
);
}
if (response.status === 403) {
throw new Error(
'APIキーに権限がありません。プランの確認をお勧めします。'
);
}
if (!response.ok) {
throw new Error(APIエラー: ${response.status});
}
return true;
} catch (error) {
if (error.message.includes('APIキー')) {
throw error;
}
throw new Error(API接続エラー: ${error.message});
}
}
// 使用前のバリデーション
async function initializeClient(apiKey) {
await validateAPIKey(apiKey);
return new HolySheepAIClient(apiKey);
}
5. タイムアウト設定のベストプラクティス
原因:デフォルトタイムアウトが短すぎる/長すぎる
// モデル別推奨タイムアウト設定
const TIMEOUT_CONFIG = {
'gpt-4.1': 60000, // 1分
'gpt-4.1-turbo': 45000, // 45秒
'claude-sonnet-4.5': 90000, // 1.5分(思考に時間がかかる)
'claude-opus-4': 120000, // 2分
'gemini-2.5-flash': 30000, // 30秒(高速モデル)
'deepseek-v3.2': 45000 // 45秒
};
// 動的タイムアウト設定
function getTimeoutForModel(model) {
// デフォルトタイムアウト
const defaultTimeout = 60000;
// 完全一致を検索
if (TIMEOUT_CONFIG[model]) {
return TIMEOUT_CONFIG[model];
}
// 部分一致で検索(例: gpt-4.1-2024...)
for (const [key, timeout] of Object.entries(TIMEOUT_CONFIG)) {
if (model.startsWith(key.split('-')[0])) {
return timeout;
}
}
return defaultTimeout;
}
// 使用例
const model = 'claude-sonnet-4.5';
const timeout = getTimeoutForModel(model);
const controller = new AbortController();
const timeoutId = setTimeout(() => {
console.log(⏱️ ${model} のタイムアウト(${timeout}ms));
controller.abort();
}, timeout);
try {
const result = await fetch('https://api.holysheep.ai/v1/chat/completions', {
method: 'POST',
headers: {
'Authorization': Bearer YOUR_HOLYSHEEP_API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model,
messages: [{ role: 'user', content: 'Hello' }]
}),
signal: controller.signal
});
} finally {
clearTimeout(timeoutId);
}
まとめ
AbortController を活用することで、APIリクエストの安全なキャンセル、タイムアウト処理、エラー_HANDLEが実装できます。HolySheep AI(今すぐ登録)は、¥1=$1のレートの業界最安水準に加え、WeChat Pay/Alipay対応により、中華圏のチームでも簡単に導入できます。<50msの低レイテンシと登録時の無料クレジットで、すぐに開発を始めることができます。
👉 HolySheep AI に登録して無料クレジットを獲得