Payload Logo
Développeur de dos devant plusieurs écrans qui affichent des flux de données lumineux bleu cyan
JavaScript

Auteur

Latchere Yoan

Date de publication

Quand on débute avec fetch, tout semble simple : on envoie une requête, on récupère du JSON, on affiche. Puis un jour, la page reste blanche sans aucune erreur dans la console. Ou le serveur reçoit [object Object] à la place de tes données. J'ai croisé ces bugs sur des sites clients comme dans mon propre code, et ce sont presque toujours les mêmes.

Voici les dix pièges que je vois le plus souvent autour de fetch, des promesses et de async/await. Chaque exemple a été exécuté pour de vrai sous Node, avec un petit serveur local : les sorties que je montre sont celles que j'ai obtenues, pas celles que j'imagine.

1. fetch ne plante pas sur une erreur 404 ou 500

C'est le piège numéro un. fetch ne rejette sa promesse que si le réseau tombe. Une réponse 404 ou 500 est, pour lui, une réponse comme une autre.

const res = await fetch(url);
const data = await res.json();

Avec une URL qui renvoie un 404, ce code continue tranquillement et tente de lire une page d'erreur comme si c'était tes données. La correction tient en deux lignes :

const res = await fetch(url);
if (!res.ok) throw new Error(res.status);
const data = await res.json();

Sur ma machine, la version corrigée affiche bien false 404 pour res.ok et res.status, puis lève l'erreur attendue.

2. Deux await à la suite, deux fois plus de temps

const u = await getUser();
const p = await getPosts();

Si les deux appels sont indépendants, le second attend inutilement la fin du premier. Avec deux requêtes d'une seconde chacune, on perd une seconde pour rien. Promise.all les lance en même temps :

const [u, p] = await Promise.all([getUser(), getPosts()]);

Mesuré chez moi : 2 secondes avec deux await, 1 seconde avec Promise.all.

3. Promise { <pending> } : l'await oublié

const res = await fetch(url);
const data = res.json();
console.log(data);

res.json() renvoie lui aussi une promesse. Sans await, tu affiches la promesse elle-même : Promise { <pending> }. Avec await res.json(), tu obtiens enfin ton objet.

4. Un fetch n'a pas de timeout par défaut

Si le serveur ne répond jamais, ta requête attend indéfiniment et l'interface reste figée. Depuis quelques années, il existe une solution simple :

const res = await fetch(url, {
signal: AbortSignal.timeout(3000),
});

Au bout de trois secondes, la promesse est rejetée avec une TimeoutError. À toi de décider ce que tu affiches à l'utilisateur ensuite.

5. Promise.all : une erreur et tout est perdu

Promise.all échoue dès qu'une seule promesse échoue. Si tu charges un utilisateur et des tags, et que les tags plantent, tu perds aussi l'utilisateur. Promise.allSettled attend tout le monde et te dit ce qui a marché :

const r = await Promise.allSettled([getUser(), getTags()]);
// [ 'fulfilled', 'rejected' ]

Tu peux alors afficher ce qui est disponible et signaler proprement ce qui manque.

6. Envoyer du JSON sans le dire au serveur

await fetch(url, { method: "POST", body: user });

Résultat côté serveur : [object Object], avec un Content-Type en text/plain. Il faut sérialiser l'objet et prévenir le serveur :

await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(user),
});

Cette fois, le serveur reçoit {"nom":"Léa"} en application/json.

7. Construire une URL en concaténant

const q = "café & thé";
const url = "/s?q=" + q;

Le & de ta recherche est interprété comme un séparateur de paramètres, et la requête est coupée en deux. URLSearchParams encode tout correctement :

const p = new URLSearchParams();
p.set("q", q);
const url = "/s?" + p; // /s?q=caf%C3%A9+%26+th%C3%A9

8. Les réponses qui arrivent dans le désordre

Sur une recherche en direct, tu tapes « la » puis « lara ». Si la réponse de « la » arrive après celle de « lara », l'écran affiche de vieux résultats. La parade : annuler la requête précédente avec un AbortController.

let ctrl;
async function chercher(q) {
ctrl?.abort();
ctrl = new AbortController();
await fetch(url + q, { signal: ctrl.signal });
}

La première requête est interrompue avec une AbortError (à ignorer dans ton catch), et seule la dernière compte.

9. Une fonction async renvoie toujours une promesse

async function getAge() {
return 28;
}
console.log(getAge()); // Promise { 28 }

Même quand tu retournes un simple nombre, l'appelant reçoit une promesse. Il faut donc await getAge() pour obtenir 28.

10. return sans await : le catch qui n'attrape rien

C'est le plus sournois, parce que le code a l'air correct.

async function charger() {
try {
return getUser();
} catch (e) {
return null;
}
}

Ici, la promesse est renvoyée avant d'être résolue. Si getUser() échoue, l'erreur sort de la fonction sans passer par ton catch. Dans mon test, j'obtenais l'erreur 500 en sortie au lieu de null. Il suffit d'écrire return await getUser() pour que le catch fasse son travail.

Ce qu'il faut retenir

La plupart de ces bugs ont la même origine : on suppose que fetch se comporte comme une fonction classique alors qu'il est asynchrone et très permissif. Trois réflexes règlent l'essentiel : vérifier res.ok, ne jamais oublier un await, et toujours prévoir ce qui se passe quand la requête échoue ou traîne.

Je publie ces astuces en vidéos courtes de moins d'une minute sur TikTok, Instagram et YouTube. Si tu veux comprendre comment je les fabrique, lis mon article sur les Shorts de code. Et si tu as un site qui souffre de ce genre de bugs, tu peux me contacter : je suis développeur web freelance à Orléans.