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 に登録して無料クレジットを獲得