Récupérer des données avec fetch et async/await
La fonction fetch remplace $.ajax de jQuery. Grâce aux mots-clés async et await, le code qui attend la réponse du serveur se lit comme du code ordinaire, de haut en bas. Comment récupérer des données en JSON, comment les préparer en PHP, comment envoyer des données au serveur et quelles erreurs ne pas oublier.
La réponse du serveur en PHP
Le fichier api/products.php renvoie en JSON les produits de la catégorie choisie (la connexion $pdo et la table products viennent des séries PHP et bases de données et Bases du SQL) :
<?php
header("Content-Type: application/json; charset=utf-8");
$query = $pdo->prepare("SELECT id, name, price FROM products WHERE category = ? ORDER BY name");
$query->execute([$_GET["category"] ?? ""]);
echo json_encode($query->fetchAll(), JSON_UNESCAPED_UNICODE);
Récupérer les données
<button type="button" id="load">Charger les boissons</button> <ul id="results"></ul>
async function loadProducts(category) {
const response = await fetch("/api/products.php?category=" + encodeURIComponent(category));
if (!response.ok) {
throw new Error("Le serveur a renvoyé l’erreur " + response.status);
}
return response.json();
}
document.querySelector("#load").addEventListener("click", async () => {
const list = document.querySelector("#results");
try {
const products = await loadProducts("Boissons");
list.replaceChildren();
products.forEach((product) => {
const item = document.createElement("li");
item.textContent = product.name + " – " + product.price + " €";
list.append(item);
});
} catch (error) {
list.textContent = "Impossible de charger les produits.";
}
});
- async marque une fonction dans laquelle on peut utiliser await. Await attend le résultat (par exemple la réponse du serveur), et pendant ce temps la page continue de réagir.
- fetch ne se termine par une erreur qu’en cas d’erreur réseau. Quand le serveur répond par une erreur (404, 500), fetch se termine normalement – c’est pourquoi il faut vérifier response.ok.
- encodeURIComponent encode la valeur dans l’adresse (espaces, caractères accentués, caractères & et ?).
- L’erreur est interceptée par try/catch, et l’utilisateur reçoit un message compréhensible au lieu d’une panne silencieuse.
- Les données du serveur s’insèrent via textContent, et non via innerHTML (voir l’article sur la modification du contenu).
Envoyer des données en POST
Une requête qui modifie des données doit envoyer le jeton CSRF (voir l’article sur la protection des formulaires). PHP écrit le jeton dans la section head de la page, par exemple <meta name="csrf-token" content="…">, et JavaScript l’envoie dans un en-tête de la requête :
async function saveRating(productId, stars) {
const token = document.querySelector('meta[name="csrf-token"]').content;
const response = await fetch("/api/rating.php", {
method: "POST",
headers: { "Content-Type": "application/json", "X-CSRF-Token": token },
body: JSON.stringify({ product: productId, stars: stars }),
});
if (!response.ok) {
throw new Error("Le serveur a renvoyé l’erreur " + response.status);
}
return response.json();
}
Côté serveur, PHP lit les données JSON depuis l’entrée de la requête et l’en-tête avec le jeton depuis $_SERVER :
<?php
session_start();
header("Content-Type: application/json; charset=utf-8");
$token = $_SERVER["HTTP_X_CSRF_TOKEN"] ?? "";
if(empty($_SESSION["csrf"]) || !hash_equals($_SESSION["csrf"], $token))
{
http_response_code(403);
exit(json_encode(["error" => "Jeton invalide."], JSON_UNESCAPED_UNICODE));
}
$input = json_decode(file_get_contents("php://input"), true);
$stars = (int)($input["stars"] ?? 0);
if($stars < 1 || $stars > 5)
{
http_response_code(422);
exit(json_encode(["error" => "La note doit être comprise entre 1 et 5."], JSON_UNESCAPED_UNICODE));
}
// … enregistrement avec une requête préparée …
echo json_encode(["ok" => true]);
L’en-tête X-CSRF-Token est accessible en PHP sous la forme $_SERVER["HTTP_X_CSRF_TOKEN"]. Pour les requêtes vers le même domaine, fetch envoie automatiquement les cookies (et donc la session).
Délai d’attente
fetch seul n’a pas de délai d’attente. On peut interrompre la requête avec AbortController :
async function loadWithTimeout(url, milliseconds) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), milliseconds);
try {
const response = await fetch(url, { signal: controller.signal });
if (!response.ok) {
throw new Error("Le serveur a renvoyé l’erreur " + response.status);
}
return await response.json();
} finally {
clearTimeout(timer);
}
}
Si le serveur ne répond pas dans le délai indiqué, fetch se termine par l’erreur AbortError, que try/catch intercepte comme les autres erreurs. Le bloc finally annule le minuteur quelle que soit la façon dont la requête se termine.
Articles connexes :
Envoyer un formulaire sans recharger la page
Protéger les formulaires contre le CSRF