Qu’est-ce que l’AJAX ?
AJAX signifie Asynchronous JavaScript and XML, un terme apparu en 2005 lorsque Gmail et Google Maps ont démontré pour la première fois qu’une page pouvait récupérer de nouvelles données sans rechargement complet. La partie XML appartient désormais largement au passé — les API modernes renvoient du JSON — mais l’idée fondamentale reste la même : communiquer avec un serveur en arrière-plan et mettre à jour la page dynamiquement.
L’outil originel était XMLHttpRequest. Il fonctionne toujours, mais son API est maladroite et repose sur des callbacks. Le remplaçant moderne est la Fetch API, une interface basée sur les promesses, disponible dans les navigateurs, Node.js, Deno et les runtimes edge.
Votre première requête fetch
fetch prend une URL et renvoie une promesse pour un Response.
// basic.js
const response = await fetch("https://api.example.com/users");
const users = await response.json();
console.log(users);
C’est tout pour le cas nominal. La subtilité est que fetch se résout même pour les codes de statut d’erreur, donc une erreur 404 ne lève pas d’exception. Vous devez inspecter la réponse vous-même.
L’objet Response
L’objet Response vous indique ce que le serveur a renvoyé.
// response.js
const response = await fetch("/api/users");
response.ok; // true for status 200–299
response.status; // 200, 404, 500, ...
response.statusText;
response.headers.get("content-type");
const data = await response.json(); // parse JSON
const text = await response.text(); // raw text
const blob = await response.blob(); // binary data
const form = await response.formData();
Le corps d’une réponse ne peut être lu qu’une seule fois. Si vous avez besoin à la fois du texte brut et du JSON analysé, appelez response.clone() avant la lecture, ou analysez le texte vous-même. Notez également que response.json() rejette la promesse si le corps n’est pas un JSON valide — une autre raison d’envelopper vos appels dans un bloc try/catch.
Gérer les erreurs correctement
Il existe deux types d’échecs, et ils se comportent différemment.
- Erreurs réseau — absence de connexion, échec DNS, CORS bloqué.
fetchest rejeté, donccatchles gère. - Erreurs HTTP — 404, 401, 500.
fetchest résolu ; vous devez vérifierresponse.okouresponse.status.
// errors.js
async function getUser(id) {
try {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
throw new Error(`Request failed with status ${response.status}`);
}
return await response.json();
} catch (error) {
console.error("Could not load user:", error);
throw error;
}
}
Traiter explicitement ces deux cas est ce qui différencie un code fragile d’un code résilient. Le guide de gestion des erreurs détaille la stratégie globale.
Envoyer des données avec POST, PUT et DELETE
Passez un objet d’options pour modifier la méthode, ajouter des headers et joindre un corps de requête (body).
// create.js
async function createUser(user) {
const response = await fetch("/api/users", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(user),
});
if (!response.ok) throw new Error("Could not create user");
return response.json();
}
await createUser({ name: "Ada", role: "engineer" });
Utilisez PUT pour remplacer une ressource, PATCH pour en mettre à jour une partie, et DELETE pour la supprimer. Pour l’upload de fichiers, créez un objet FormData et passez-le comme body — le navigateur définira automatiquement le type de contenu multipart approprié.
En-têtes et authentification
Les en-têtes transportent des métadonnées sur la requête. Vous pouvez les définir pour chaque requête, et de nombreuses API attendent un jeton d’autorisation.
// auth.js
const response = await fetch("/api/me", {
headers: {
Authorization: `Bearer ${token}`,
Accept: "application/json",
},
});
Ne codez jamais de secrets en dur dans le code côté client — tout ce qui est envoyé au navigateur est public. Les jetons doivent provenir d’un flux de connexion et être stockés avec prudence.
Annuler des requêtes
Une requête qui n’est plus pertinente — parce que l’utilisateur a saisi un autre caractère, a changé de page ou que le composant a été démonté — doit être annulée. AbortController permet de faire précisément cela.
// abort.js
const controller = new AbortController();
fetch("/api/search?q=javascript", { signal: controller.signal })
.then((res) => res.json())
.then(console.log)
.catch((error) => {
if (error.name === "AbortError") return;
console.error(error);
});
// Cancel when it is no longer needed
controller.abort();
Comme fetch ne possède pas d’option de timeout, AbortController est également la méthode standard pour en implémenter un.
// timeout.js
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 5000);
try {
const res = await fetch("/api/slow", { signal: controller.signal });
return await res.json();
} finally {
clearTimeout(timer);
}
CORS et identifiants
Les navigateurs appliquent la politique de même origine (same-origin policy) : JavaScript ne peut pas lire les réponses provenant d’une origine différente, à moins que le serveur ne l’autorise explicitement. Cette autorisation est assurée par le CORS, qui se configure entièrement côté serveur via des en-têtes de réponse tels que Access-Control-Allow-Origin. Si vous rencontrez une erreur CORS, la solution se trouve dans la configuration du serveur, et non dans votre appel fetch.
Par défaut, fetch n’envoie pas de cookies vers des URL d’origines différentes. Pour les inclure, définissez credentials: "include" et assurez-vous que le serveur autorise les requêtes avec identifiants. C’est une source courante de confusion du type « ça fonctionne dans Postman, mais pas dans le navigateur ».
Patterns pratiques
États de chargement, de succès et d’erreur. Reflétez toujours le cycle de vie de la requête dans l’UI.
// state.js
async function loadPosts() {
showSpinner();
try {
const res = await fetch("/api/posts");
if (!res.ok) throw new Error(`HTTP ${res.status}`);
renderPosts(await res.json());
} catch (error) {
showError("Could not load posts. Try again.");
} finally {
hideSpinner();
}
}
Recherche avec debounce. Annulez la requête précédente dès qu’une nouvelle commence, afin que des réponses lentes n’écrasent pas des résultats récents. Associez AbortController à un court délai de debounce.
Requêtes parallèles. Utilisez Promise.all lorsque plusieurs endpoints sont nécessaires simultanément, et Promise.allSettled lorsqu’un résultat partiel reste utile.
Bonnes pratiques
- Vérifiez toujours
response.okavant d’analyser le corps de la requête. - Définissez un header
Acceptexplicite et unContent-Typelors de l’envoi d’un corps de requête. - Enveloppez les requêtes attendues (awaited) dans un bloc
try/catchet affichez un message utile. - Annulez les requêtes obsolètes avec
AbortController. - Regroupez les appels API dans un petit module au lieu de disperser les URLs dans vos composants.
- Ne placez jamais de secrets dans le code côté client.
- Affichez des états de chargement et d’erreur pour que l’UI ne semble jamais plantée.
Erreurs courantes
- Supposer que
fetchlève une exception en cas d’erreur 404 ou 500. - Oublier
JSON.stringifysur le corps d’une requête. - Oublier le header
Content-Typeet recevoir une erreur de parsing du serveur. - Lire le corps d’une réponse deux fois.
- Ignorer le CORS jusqu’à ce que le navigateur bloque la requête en production.
- Ne pas annuler les requêtes et laisser des données obsolètes gagner une course critique (race condition).
Et après ?
Fetch est le pont entre votre front end et le monde extérieur. Combinez-le avec le DOM pour afficher les résultats, async/await pour séquencer vos opérations, et la gestion des erreurs pour gérer les échecs avec élégance. À partir de là, approfondissez vos connaissances sur les méthodes, les codes de statut et les headers, car ils structurent chaque requête que vous envoyez.