// Web

L'API Fetch en JavaScript : requêtes HTTP modernes

L’API Fetch est la manière moderne d’envoyer des requêtes HTTP en JavaScript, directement dans le navigateur, sans bibliothèque externe. Récupérer des données d’une API, envoyer un formulaire, télécharger un fichier : tout passe par la fonction globale fetch(). Elle repose sur les Promesses, s’écrit très bien avec async/await et remplace définitivement l’ancien XMLHttpRequest. Ce guide couvre chaque aspect, du simple GET à l’annulation d’une requête, avec un exemple concret à chaque étape.

La requête la plus simple : un GET

Dans sa forme minimale, fetch() prend une URL et renvoie une Promesse qui se résout avec un objet Response. Ce n’est pas encore les données : c’est une enveloppe qu’il faut « ouvrir » avec une méthode comme .json().

fetch("https://jsonplaceholder.typicode.com/todos/1")
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error("Erreur :", error));

Le premier .then reçoit la réponse et la convertit en JSON (ce qui renvoie une nouvelle Promesse), le second reçoit enfin les données exploitables. Cette double étape déroute souvent les débutants : retenez que fetch résout d’abord les en-têtes de la réponse, puis .json() lit le corps.

La version async/await, plus lisible

Enchaîner les .then devient vite illisible. La syntaxe async/await transforme ce code asynchrone en quelque chose qui se lit de haut en bas, comme du code synchrone.

async function chargerTodo() {
  const response = await fetch("https://jsonplaceholder.typicode.com/todos/1");
  const data = await response.json();
  console.log(data);
}

chargerTodo();

C’est la forme que vous utiliserez le plus souvent. Elle suppose de maîtriser le fonctionnement des Promesses et de async/await, sur lequel repose entièrement Fetch. Notez qu’await ne peut s’utiliser qu’à l’intérieur d’une fonction async.

Lire la réponse : json, text, blob

L’objet Response sait interpréter le corps sous plusieurs formats. Vous choisissez la méthode selon ce que renvoie le serveur.

const response = await fetch("/donnees");

// API JSON (le cas le plus fréquent)
const objet = await response.json();

// Texte brut, HTML, CSV…
const texte = await response.text();

// Données binaires : images, fichiers, PDF
const image = await response.blob();

// Données binaires bas niveau
const buffer = await response.arrayBuffer();

Attention : le corps d’une réponse ne peut être lu qu’une seule fois. Appeler response.json() puis response.text() sur la même réponse déclenche une erreur. Choisissez la bonne méthode d’emblée.

Envoyer des données : la requête POST

Pour créer une ressource, on passe un second argument à fetch : un objet d’options. Les trois clés essentielles sont method, headers et body.

async function creerArticle() {
  const response = await fetch("https://jsonplaceholder.typicode.com/posts", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      titre: "Mon article",
      contenu: "Écrit avec Coder Studio",
      userId: 1,
    }),
  });

  const cree = await response.json();
  console.log("Ressource créée :", cree);
}

Trois points cruciaux :

  • method vaut "POST", "PUT", "PATCH" ou "DELETE" selon l’action ;
  • body doit être une chaîne : on sérialise l’objet JavaScript avec JSON.stringify ;
  • l’en-tête Content-Type: application/json prévient le serveur du format envoyé. L’oublier est une cause d’erreur très courante.

Les autres méthodes HTTP

La structure reste identique, seule la valeur de method change. Un DELETE n’a généralement pas de corps :

// Mise à jour complète
await fetch(`/articles/${id}`, {
  method: "PUT",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify(articleModifie),
});

// Suppression
await fetch(`/articles/${id}`, { method: "DELETE" });

Ces verbes forment la base d’une API REST, la convention d’échange standard entre le front-end et le back-end.

Les en-têtes (headers)

Les en-têtes transmettent des métadonnées : format attendu, jeton d’authentification, langue… On les passe dans un objet simple.

const response = await fetch("/api/profil", {
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer " + token,
    "Accept": "application/json",
    "Accept-Language": "fr-FR",
  },
});

L’en-tête Authorization avec un jeton Bearer est le mécanisme d’authentification le plus répandu pour les API. Vous lirez souvent aussi les en-têtes de la réponse :

console.log(response.headers.get("Content-Type"));

La gestion des erreurs : le piège classique

Voici le point le plus mal compris de Fetch. Une réponse 404 ou 500 ne déclenche pas le .catch ni le bloc catch d’un try/catch. La Promesse n’est rejetée que sur une erreur réseau (pas de connexion, DNS injoignable, requête bloquée). Une réponse serveur, même en erreur, est considérée comme un succès de communication.

Il faut donc vérifier soi-même le statut avec la propriété response.ok (vraie pour un statut 200–299).

async function chargerDonnees(url) {
  try {
    const response = await fetch(url);

    if (!response.ok) {
      // 404, 500… : à gérer explicitement
      throw new Error(`Erreur HTTP ${response.status} : ${response.statusText}`);
    }

    const data = await response.json();
    return data;
  } catch (error) {
    // Ici : erreur réseau OU erreur HTTP relancée ci-dessus
    console.error("Échec du chargement :", error.message);
    return null;
  }
}

Ce schéma — tester response.ok, lever une erreur, tout envelopper dans try/catch — est le motif à reproduire dans chaque appel. La propriété response.status contient le code numérique (200, 404, 500…) et response.statusText son libellé.

Annuler une requête avec AbortController

Une requête peut devenir inutile avant sa fin : l’utilisateur quitte la page, tape une nouvelle recherche, ou l’attente est trop longue. AbortController permet de l’interrompre proprement.

const controller = new AbortController();

// On passe le signal à fetch
fetch("/api/recherche?q=javascript", { signal: controller.signal })
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => {
    if (error.name === "AbortError") {
      console.log("Requête annulée");
    } else {
      console.error(error);
    }
  });

// Plus tard : on annule
controller.abort();

Cas très pratique : un timeout maison qui annule automatiquement après quelques secondes.

async function fetchAvecTimeout(url, ms = 5000) {
  const controller = new AbortController();
  const id = setTimeout(() => controller.abort(), ms);

  try {
    const response = await fetch(url, { signal: controller.signal });
    return await response.json();
  } finally {
    clearTimeout(id);
  }
}

Sur les navigateurs récents, AbortSignal.timeout(5000) fait la même chose en une ligne, mais la version manuelle reste utile à comprendre.

Les requêtes en parallèle

Quand plusieurs requêtes sont indépendantes, ne les enchaînez pas : lancez-les ensemble avec Promise.all pour gagner un temps précieux.

async function chargerTableauDeBord() {
  const [profil, articles, notifs] = await Promise.all([
    fetch("/api/profil").then(r => r.json()),
    fetch("/api/articles").then(r => r.json()),
    fetch("/api/notifications").then(r => r.json()),
  ]);

  console.log(profil, articles, notifs);
}

Les trois requêtes partent simultanément ; on attend seulement la plus lente au lieu de les additionner.

CORS en bref

Le CORS (Cross-Origin Resource Sharing) est la source d’erreur la plus frustrante pour les débutants. Par sécurité, le navigateur bloque par défaut les requêtes JavaScript vers un domaine différent de celui de votre page, sauf si le serveur cible l’autorise explicitement via des en-têtes (Access-Control-Allow-Origin).

Point essentiel à comprendre : le CORS se règle côté serveur, pas côté client. Aucune option de fetch ne « débloque » le CORS. Si vous voyez une erreur No ‘Access-Control-Allow-Origin’ header, c’est au serveur d’être configuré, ou il faut passer par votre propre back-end comme intermédiaire (proxy). L’option mode: "cors" (par défaut) et l’option credentials: "include" (pour envoyer les cookies) ajustent le comportement, mais ne contournent pas la règle de sécurité.

À retenir

fetch() renvoie une Promesse résolue avec un objet Response qu’il faut « ouvrir » avec .json(), .text() ou .blob(). Pour un POST, sérialisez le corps avec JSON.stringify et déclarez le bon Content-Type. Surtout, souvenez-vous que Fetch ne rejette pas sur les statuts 4xx et 5xx : testez toujours response.ok. Avec async/await, la gestion d’erreurs par try/catch et AbortController pour l’annulation, vous disposez de tout l’outillage HTTP moderne, sans aucune dépendance.

À lire ensuite