Charger des données sans recharger la page est devenu la base de toute interface moderne. Pendant longtemps, cela passait par un objet vieillissant aux réglages obscurs. Une fonction unique l'a remplacé.
Elle tient en un appel, mais elle cache un piège qui surprend tout le monde une fois : une réponse en erreur n'est pas une erreur pour elle.
Définition
fetch() envoie une requête HTTP et rend une Promise qui se règle avec un objet Response. Cet objet porte le code de statut et les en-têtes, mais pas encore le corps : le lire demande un second temps d'attente.
async function chargerProfil(id) {
const reponse = await fetch("/api/profils/" + id);
if (!reponse.ok) {
throw new Error("Statut " + reponse.status);
}
return await reponse.json(); // deuxième attente : le corps
}La propriété ok vaut vrai pour un statut compris entre 200 et 299. Le corps se lit avec json(), text(), blob() ou formData(), selon ce que le serveur a renvoyé.
Ce que fetch appelle une erreur
Le tableau ci-dessous distingue, sur deux colonnes, les situations qui rompent la promesse de celles qui la tiennent malgré une réponse décevante.
| Situation | Comportement de la promesse |
|---|---|
| Réponse 404 ou 500 | Tenue, avec ok à faux |
| Redirection suivie | Tenue, sur la réponse finale |
| Serveur injoignable | Rompue, avec une TypeError |
| Requête refusée par le navigateur | Rompue, sans détail lisible |
| Requête annulée | Rompue, avec le nom AbortError |
La première ligne est le piège : un catch posé autour d'un appel n'attrapera jamais un 404. Le test sur ok n'est pas facultatif.
Envoyer des données
Une écriture demande trois réglages : la méthode, l'en-tête qui annonce le format, et le corps sérialisé.
const reponse = await fetch("/api/factures", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ montant: 49, devise: "EUR" }),
signal: AbortSignal.timeout(8000),
});Le corps doit être une chaîne, ce que produit JSON avec stringify. Passer directement l'objet enverrait la chaîne [object Object], une erreur silencieuse et difficile à voir.
fetch n'abandonne pas tout seul au bout d'un certain temps. Un délai maximal se pose avec un signal, produit par AbortSignal.timeout() ou par un AbortController quand l'annulation doit venir de l'interface.
Questions fréquentes
Pourquoi mon corps de réponse est-il vide au deuxième appel ?
Parce que le corps d'une réponse est un flux, et qu'un flux ne se lit qu'une fois. Un second json() sur le même objet lève une erreur. Si deux parties du code ont besoin du contenu, rangez le résultat dans une variable, ou dupliquez la réponse avec clone() avant la première lecture.
Les cookies partent-ils avec la requête ?
Vers la même origine, oui. Vers un autre domaine, non, sauf si l'option credentials est réglée sur include, et le serveur doit alors l'autoriser explicitement. C'est le point de friction le plus courant entre une interface et une API hébergée ailleurs, décrit en détail par le CORS.
Faut-il encore une bibliothèque pour appeler une API ?
Plus vraiment pour un besoin simple : la fonction est disponible dans tous les navigateurs actuels et dans Node.js sans rien installer. Une bibliothèque garde son intérêt pour les intercepteurs, les nouvelles tentatives automatiques ou la progression d'un envoi. Ce choix, et la couche de gestion d'erreurs qui va avec, se travaille dans la formation JavaScript.