En tant que développeur qui a géré des centaines de milliers d'appels API l'année dernière, je peux vous confirmer que la gestion des annulations de requêtes est un compétence essentielle souvent négligée. Combien de fois avez-vous vu votre application continuer à attendre une réponse alors que l'utilisateur a déjà navigué vers une autre page ? Ou pire, accumuler des coûts sur des appels qui ne seront jamais utilisés ?
Pourquoi l'Annulation d'API est Cruciale en 2026
Les modèles d'IA sont devenus extrêmement puissants, mais aussi coûteux. Voici les tarifs actualisés pour les principaux modèles via HolySheep AI :
- GPT-4.1 : 8 $/MTok (output) — excellent pour les tâches complexes
- Claude Sonnet 4.5 : 15 $/MTok (output) — idéal pour l'analyse approfondie
- Gemini 2.5 Flash : 2,50 $/MTok (output) — parfait pour les réponses rapides
- DeepSeek V3.2 : 0,42 $/MTok (output) — l'option la plus économique
Pour une application traitant 10 millions de tokens par mois, la différence de coût est significative :
| Modèle | Coût mensuel (10M tok) |
|---|---|
| Claude Sonnet 4.5 | 150 $ |
| GPT-4.1 | 80 $ |
| Gemini 2.5 Flash | 25 $ |
| DeepSeek V3.2 | 4,20 $ |
Avec HolySheep AI, le taux de change avantageux (¥1 = $1) permet d'économiser plus de 85% sur les coûts grâce aux prix en yuan. De plus, la latence moyenne inférieure à 50ms optimise chaque requête.
AbortController : L'API Native du Navigateur
AbortController est une API JavaScript native disponible dans tous les navigateurs modernes. Elle permet d'annuler une ou plusieurs requêtes Web, y compris les appels fetch et les requêtes XMLHttpRequest.
Syntaxe de Base
// Création du contrôleur
const abortController = new AbortController();
// Extraction du signal
const { signal } = abortController;
// Utilisation avec fetch
fetch(url, { signal });
// Annulation quand nécessaire
abortController.abort();
Intégration avec HolySheep AI
Voici comment implémenter un système complet de requêtes annulables avec l'API HolySheep :
/**
* Client API HolySheep avec support AbortController
* Développé pour une latence <50ms et gestion des annulations
*/
class HolySheepClient {
constructor(apiKey) {
this.baseUrl = 'https://api.holysheep.ai/v1';
this.apiKey = apiKey;
}
async chatCompletion(messages, options = {}) {
const abortController = new AbortController();
const timeout = options.timeout || 30000;
// Timeout automatique
const timeoutId = setTimeout(() => {
abortController.abort();
}, timeout);
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,
max_tokens: options.maxTokens || 1000
}),
signal: abortController.signal
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(HTTP ${response.status}: ${response.statusText});
}
return await response.json();
} catch (error) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw new Error('Requête annulée ou expirée');
}
throw error;
}
}
// Méthode pour annuler manuellement
createCancellableRequest() {
const controller = new AbortController();
return {
controller,
signal: controller.signal,
cancel: () => controller.abort()
};
}
}
// Utilisation
const client = new HolySheepClient('YOUR_HOLYSHEEP_API_KEY');
const { cancel } = client.createCancellableRequest();
// Lancer une requête
const requestPromise = client.chatCompletion(
[{ role: 'user', content: 'Explain quantum computing' }],
{ model: 'deepseek-v3.2' }
);
// Annuler après 5 secondes
setTimeout(cancel, 5000);
requestPromise
.then(result => console.log('Succès:', result))
.catch(error => console.error('Erreur:', error.message));
Gestion Avancée : Annulation Multi-Requêtes
Dans les applications réelles, vous aurez souvent besoin d'annuler plusieurs requêtes simultanément. Voici une implémentation avancée :
/**
* Gestionnaire de requêtes avec pool d'annulation
* Permet d'annuler toutes les requêtes en cours
*/
class RequestPool {
constructor() {
this.activeControllers = new Map();
this.requestIdCounter = 0;
}
generateRequestId() {
return req_${++this.requestIdCounter}_${Date.now()};
}
async execute(apiClient, messages, options = {}) {
const requestId = this.generateRequestId();
const controller = new AbortController();
// Stocker le contrôleur
this.activeControllers.set(requestId, controller);
const timeout = options.timeout || 30000;
const timeoutId = setTimeout(() => {
controller.abort();
}, timeout);
try {
const result = await apiClient.chatCompletion(messages, {
...options,
signal: controller.signal
});
clearTimeout(timeoutId);
this.activeControllers.delete(requestId);
return { requestId, success: true, data: result };
} catch (error) {
clearTimeout(timeoutId);
this.activeControllers.delete(requestId);
return {
requestId,
success: false,
error: error.name === 'AbortError' ? 'CANCELLED' : error.message
};
}
}
// Annuler une requête spécifique
cancelOne(requestId) {
const controller = this.activeControllers.get(requestId);
if (controller) {
controller.abort();
this.activeControllers.delete(requestId);
return true;
}
return false;
}
// Annuler TOUTES les requêtes en cours
cancelAll() {
let cancelledCount = 0;
for (const [id, controller] of this.activeControllers) {
controller.abort();
cancelledCount++;
}
this.activeControllers.clear();
return cancelledCount;
}
// Nombre de requêtes actives
getActiveCount() {
return this.activeControllers.size;
}
}
// Exemple d'utilisation avec React
function useHolySheepRequests() {
const poolRef = useRef(new RequestPool());
const [results, setResults] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const sendBatch = async (prompts) => {
setIsLoading(true);
setResults([]);
// Lancer toutes les requêtes en parallèle
const promises = prompts.map(prompt =>
poolRef.current.execute(
new HolySheepClient('YOUR_HOLYSHEEP_API_KEY'),
[{ role: 'user', content: prompt }],
{ model: 'gemini-2.5-flash', timeout: 15000 }
)
);
const allResults = await Promise.allSettled(promises);
setResults(allResults.map(r => r.value || r.reason));
setIsLoading(false);
};
const cancelAll = () => {
const count = poolRef.current.cancelAll();
console.log(${count} requêtes annulées);
setIsLoading(false);
};
useEffect(() => {
return () => poolRef.current.cancelAll(); // Cleanup
}, []);
return { sendBatch, cancelAll, isLoading, results, activeCount: poolRef.current.getActiveCount() };
}
Cas d'Usage Pratiques
1. Interface de Chat avec Annulation
/**
* Interface de chat avec bouton d'annulation
* - Latence moyenne HolySheep : <50ms
* - Support WeChat/Alipay pour les paiements
*/
function ChatInterface() {
const [messages, setMessages] = useState([]);
const [input, setInput] = useState('');
const [isGenerating, setIsGenerating] = useState(false);
const [abortController, setAbortController] = useState(null);
const [costEstimate, setCostEstimate] = useState(0);
const client = useMemo(() =>
new HolySheepClient('YOUR_HOLYSHEEP_API_KEY'),
[]);
const generateResponse = async () => {
const controller = new AbortController();
setAbortController(controller);
setIsGenerating(true);
const userMessage = { role: 'user', content: input };
const allMessages = [...messages, userMessage];
// Estimer le coût avant l'envoi
const inputTokens = input.length / 4; // Approximation
const estimatedCost = (inputTokens / 1_000_000) * 0.42 * 15; // DeepSeek output
setCostEstimate(estimatedCost);
setMessages(allMessages);
setInput('');
try {
const response = await client.chatCompletion(allMessages, {
model: 'deepseek-v3.2',
signal: controller.signal,
maxTokens: 2000
});
const assistantMessage = {
role: 'assistant',
content: response.choices[0].message.content
};
setMessages(prev => [...prev, assistantMessage]);
// Mettre à jour le coût réel
const outputTokens = response.usage.completion_tokens;
const actualCost = (outputTokens / 1_000_000) * 0.42;
console.log(Coût réel: ${actualCost.toFixed(4)} $);
} catch (error) {
if (error.name === 'AbortError') {
setMessages(prev => [...prev, {
role: 'assistant',
content: '[Réponse annulée par l\'utilisateur]'
}]);
} else {
console.error('Erreur API:', error);
}
} finally {
setIsGenerating(false);
setAbortController(null);
}
};
const cancelGeneration = () => {
if (abortController) {
abortController.abort();
console.log('Génération annulée - économies réalisées');
}
};
return (
<div className="chat-container">
<div className="messages">
{messages.map((msg, i) => (
<div key={i} className={message ${msg.role}}>
{msg.content}
</div>
))}
</div>
<div className="input-area">
<input
value={input}
onChange={(e) => setInput(e.target.value)}
disabled={isGenerating}
placeholder="Tapez votre message..."
/>
{isGenerating ? (
<button onClick={cancelGeneration} className="cancel-btn">
❌ Annuler
</button>
) : (
<button onClick={generateResponse}>
Envoyer
</button>
)}
</div>
{costEstimate > 0 && (
<div className="cost-display">
Coût estimé: {costEstimate.toFixed(4)} $
</div>
)}
</div>
);
}
Erreurs Courantes et Solutions
1. Erreur : "The user aborted a request"
Symptôme : L'erreur AbortError est propagée et non gérée.
Solution :
// ❌ Mauvaise approche
async function badRequest() {
const controller = new AbortController();
try {
return await fetch(url, { signal: controller.signal });
} catch (error) {
console.error(error); // Affiche "The user aborted a request"
throw error;
}
}
// ✅ Bonne approche
async function goodRequest() {
const controller = new AbortController();
try {
return await fetch(url, { signal: controller.signal });
} catch (error) {
if (error.name === 'AbortError') {
console.log('Requête annulée intentionnellement');
return null; // ou返回一个默认值
}
// Pour les autres erreurs
console.error('Erreur réseau:', error.message);
throw error;
}
}
2. Erreur : "signal is already aborted"
Symptôme : Tentative d'utiliser un signal après l'avoir déjà aborté.
Solution :
// ❌ Mauvaise approche
const controller = new AbortController();
controller.abort();
// Plus tard dans le code...
fetch(url, { signal: controller.signal }); // ERREUR!
// ✅ Bonne approche
class SmartController {
constructor() {
this.controller = null;
}
startRequest() {
if (this.controller) {
this.cancel(); // Annuler la précédente
}
this.controller = new AbortController();
return this.controller.signal;
}
cancel() {
if (this.controller) {
this.controller.abort();
this.controller = null;
}
}
get isActive() {
return this.controller !== null && !this.controller.signal.aborted;
}
}
// Utilisation
const smart = new SmartController();
const signal = smart.startRequest();
await fetch(url, { signal });
// Réutiliser pour une nouvelle requête (annule automatiquement l'ancienne)
smart.startRequest(); // L'ancienne est annulée
await fetch(url2, { signal: smart.controller.signal });
3. Erreur : Timeout non respecté ou mémoire leaks
Symptôme : Les timeouts ne se déclenchent pas ou les listeners restent actifs.
Solution :
// ❌ Mauvaise approche - memory leak potentiel
async function leakyRequest(url, timeout) {
const controller = new AbortController();
setTimeout(() => {
controller.abort(); // Pas de clearTimeout!
}, timeout);
return fetch(url, { signal: controller.signal });
}
// ✅ Bonne approche - gestion complète
class CancellableRequest {
constructor() {
this.timeoutId = null;
this.controller = null;
}
async execute(url, options = {}) {
const timeout = options.timeout || 30000;
// Nettoyer l'état précédent
this.cleanup();
this.controller = new AbortController();
return new Promise((resolve, reject) => {
// Configurer le timeout
this.timeoutId = setTimeout(() => {
this.controller.abort();
reject(new Error('TIMEOUT_EXCEEDED'));
}, timeout);
// Effectuer la requête
fetch(url, { signal: this.controller.signal })
.then(response => {
clearTimeout(this.timeoutId);
resolve(response);
})
.catch(error => {
clearTimeout(this.timeoutId);
if (error.name === 'AbortError') {
reject(new Error('REQUEST_CANCELLED'));
} else {
reject(error);
}
});
});
}
cancel() {
if (this.controller) {
this.controller.abort();
}
this.cleanup();
}
cleanup() {
if (this.timeoutId) {
clearTimeout(this.timeoutId);
this.timeoutId = null;
}
this.controller = null;
}
}
// Utilisation avec cleanup automatique
const request = new CancellableRequest();
try {
const result = await request.execute('https://api.holysheep.ai/v1/chat/completions', {
timeout: 15000
});
} finally {
request.cleanup(); // Toujours nettoyer
}
Bonnes Pratiques et Recommandations
- Définissez toujours un timeout : Les requêtes API peuvent rester suspendues indéfiniment sans timeout.
- Gérez lesAbortError explicitement : Ne laissez pas les annulations se propager comme des erreurs.
- Nettoyez les ressources : Utilisez des blocs finally ou des hooks de cleanup (useEffect return).
- Implémentez un bouton d'annulation : Essentiel pour les interfaces utilisateur réactives.
- Estimez les coûts avant l'envoi : Avec HolySheep, vous pouvez calculer le coût basé sur les tokens d'entrée.
- Utilisez AbortController pour les pools de requêtes : Annulez toutes les requêtes en cours lors du changement de page.
Conclusion
L'implémentation correcte d'AbortController peut vous faire économiser des centaines de dollars par mois en annulant les requêtes inutiles. Combinez cela avec les tarifs compétitifs de HolySheep AI (DeepSeek V3.2 à 0,42 $/MTok, latence <50ms) et vous avez une solution performante et économique pour vos applications JavaScript.
Comme je le dis toujours à mes équipes : une requête annulée à temps vaut mieux qu'un dollar gaspillé en tokens non utilisés.