Apprenez en lisant dans l'ordre

fetch — récupérer des données depuis une API

fetch et json() pour récupérer du JSON depuis une URL, response.ok pour repérer un échec, les URL relatives et Promise.all pour charger deux fichiers à la fois.

Jusqu'ici, les exercices écrivaient leurs données directement dans le code. Sur un vrai site, les prix et les stocks changent : la page interroge donc l'API du serveur (une URL que les programmes utilisent pour obtenir des données) à chaque ouverture.

Cet article présente fetch, qui récupère des données depuis une URL, et response.ok, qui indique si la requête a réussi.

Récupérer le JSON d'une API — fetch et json()

Imagine une page d'historique qui doit afficher les commandes enregistrées sur le serveur. Les commandes arrivent sous forme de chaîne JSON : il te faut donc un moyen d'attendre leur arrivée, puis une étape qui retransforme la chaîne en tableau. Dans les exercices, tu récupéreras, à la place d'une vraie API, des fichiers JSON placés dans le dossier fixtures de ce site.

fetch (une fonction qui demande au serveur les données situées à une URL) renvoie une Promise qui passe à fulfilled avec une Response, l'objet qui représente la réponse du serveur. Le code de statut status (un nombre qui vaut 200 en cas de succès) et les en-têtes (des informations sur la réponse, envoyées avant le corps) arrivent en premier, puis le corps suit : pour l'obtenir, tu fais donc un second await, sur response.json().

// Récupère le JSON des commandes stocké sur ce site (l'écriture de l'URL est expliquée dans la section suivante)
const response = await fetch("fixtures/fr/orders.json");
console.log(response.status);          // 200

// Lit tout le corps et convertit le JSON en tableau
const orders = await response.json();
console.log(orders.length);            // 3
console.log(orders[1].total);          // 12600

// À partir d'ici, c'est un tableau ordinaire
const preparing = orders.filter((order) => order.status === "En préparation");
console.log(preparing.map((order) => order.id).join(", "));   // A-3002, A-3003
L'ordre d'arrivée de la réponse et les deux await
fetch(url) envoiela requêteLa Promisereste pendingCode 200 eten-têtes reçusawait fetch(…)renvoie ResponseCorps complet,JSON convertiawait json()donne 3 commandes
Le await de fetch se termine dès que les en-têtes arrivent. Le tableau n'est disponible qu'après le await de json(), qui lit le corps jusqu'au bout.

json() lit le corps jusqu'au bout et applique la même conversion que JSON.parse avant de renvoyer la valeur : tu attends donc le résultat avec await. Une fois converti, orders est un tableau ordinaire, sur lequel filter et map fonctionnent directement.

Afficher une Response donne {}

Dans la console des exercices, console.log(response) affiche {}, comme pour les éléments du DOM. Sans await, response.json() est une Promise, qui s'affiche elle aussi {}. Pour vérifier une Response, affiche son status ; pour le corps, affiche la valeur obtenue avec await sur json().

Sur un écran de gestion des membres, tu récupères la liste des membres depuis le serveur et tu affiches leurs noms. Cette liste se trouve dans fixtures/fr/users.json.

① Récupère la liste des membres et affiche le code de statut de la réponse.

② Transforme le corps en tableau et affiche le nombre de membres.

③ Garde uniquement les noms, sépare-les par « , » et affiche le résultat.

(Si tout s'exécute correctement, une explication apparaîtra.)

Éditeur JavaScript / TypeScript

Exécuter le code pour voir le résultat

Écrire une URL à partir de la page — les URL relatives

Dans la section précédente, il suffisait d'écrire "fixtures/fr/orders.json" pour récupérer le fichier. Une URL complète qui commence par https:// inclut le domaine (la partie après https://, comme runner.desktechlearn.com, qui identifie le site) : il faudrait donc la réécrire chaque fois que le site change d'adresse.

Une URL relative (une URL écrite par rapport à l'URL de la page courante) part du dossier de la page quand elle ne commence pas par /, et de la racine du domaine quand elle commence par /. Le code des exercices s'exécute dans runner.html, une page invisible hébergée sur runner.desktechlearn.com, et non dans la page de l'article.

// URL de la page où s'exécute ce code (la console des exercices)
// https://runner.desktechlearn.com/tools/js-editor/runner.html

// Sans / au début : on part de js-editor, le dossier qui contient runner.html
const fromPage = await fetch("fixtures/fr/orders.json");
console.log(fromPage.url);   // url est l'URL réellement demandée
// https://runner.desktechlearn.com/tools/js-editor/fixtures/fr/orders.json

// Avec / au début : on part de la racine du domaine
const fromRoot = await fetch("/tools/js-editor/fixtures/fr/orders.json");
console.log(fromRoot.url === fromPage.url);   // true (les deux désignent le même fichier)

// Si tu oublies fixtures/fr/, la recherche se fait directement dans js-editor
const noFolder = await fetch("orders.json");
console.log(noFolder.url);
// https://runner.desktechlearn.com/tools/js-editor/orders.json
Où se trouvent runner.html et fixtures
runner.desktechlearn.com — racine du domaine (/)
  • Les URL qui commencent par / partent d'ici
Dossier /tools/js-editor/
  • runner.html — la page où s'exécute le code des exercices
  • Les URL sans / au début partent d'ici
Dossier fixtures/fr
  • Contient orders.json, users.json et products.json
fixtures se trouve dans js-editor, le même dossier que runner.html. Le / initial décide de l'endroit où commence la recherche.

Une URL qui commence par / désigne le même fichier, quel que soit le dossier de la page. Une URL sans / continue de fonctionner si tu déplaces la page et fixtures ensemble dans un autre dossier. Le tableau ci-dessous indique, pour chaque URL, son point de départ et si elle trouve le fichier.

URLPoint de départFichier trouvé ?
fixtures/fr/orders.jsonjs-editorOui
/tools/js-editor/fixtures/fr/orders.jsonRacine du domaineOui (même fichier)
orders.jsonjs-editorNon (absent de js-editor)

Dans les DevTools, l'URL part de la page de l'article

Si tu ouvres la Console des DevTools sur la page de l'article et que tu exécutes fetch("fixtures/fr/orders.json"), le point de départ est l'URL de la page de l'article : la requête n'atteint donc jamais fixtures. Le serveur de la page de l'article renvoie le HTML de la page avec le code 200, même pour un chemin qui n'existe pas : la requête peut sembler avoir réussi, mais ce que tu obtiens n'est pas du JSON.

Sur l'écran d'administration d'une boutique d'articles pour la maison, tu additionnes les prix des produits. brokenPath contient l'URL de la liste des produits, mais il y manque les dossiers fixtures/fr/.

① Lance une requête sur brokenPath et affiche si le code de statut vaut 200 (true / false).

② Corrige l'URL pour qu'elle parte du dossier de la page, récupère-la et affiche le nombre de produits.

③ Récupère la liste avec une URL qui part de la racine du domaine, et affiche le total des prix.

Éditeur JavaScript / TypeScript

Exécuter le code pour voir le résultat

Repérer une requête qui a échoué — response.ok et status

Si tu fais une faute de frappe dans l'URL ou que le fichier a été supprimé, le serveur répond qu'il ne l'a pas trouvé. Une réponse arrive quand même, donc fetch passe à fulfilled avec une Response. Même dans un try / catch, l'exécution ne saute pas dans le catch : elle continue à la ligne suivante, celle qui lit le corps.

response.ok vaut true quand status est compris entre 200 et 299. 200 n'est pas le seul code de succès (201 en est un aussi) : utilise donc ok pour savoir si la requête a réussi. Pour un fichier absent, les serveurs renvoient en général 404 (Not Found), mais le serveur de la console des exercices renvoie 403 (Forbidden).

// Une URL avec une faute de frappe : order au lieu de orders
const response = await fetch("fixtures/fr/order.json");

// Même si le fichier est introuvable, fetch passe à fulfilled et renvoie une Response
console.log(response.ok);       // false
console.log(response.status);   // 404 ou 403 (selon le serveur ; la console des exercices renvoie 403)

// Convertit le corps en tableau seulement si ok vaut true
if (response.ok) {
  const orders = await response.json();
  console.log(`Commandes : ${orders.length}`);
} else {
  console.log("Impossible de charger vos commandes");   // Impossible de charger vos commandes
}
Trois cas : avec ou sans test de ok
await fetch(url)passe à fulfilledorders.jsonstatus 200order.jsonstatus 403, etc.order.jsonstatus 403, etc.ok vaut trueon appelle json()ok vaut falsepas de json()sans tester ok,on appelle json()tableau de3 commandesaffiche unmessage d'erreurSyntaxErrorcorps non JSON
fetch passe à fulfilled même si le fichier n'existe pas. Appeler json() sur un corps qui n'est pas du JSON lève une SyntaxError.

Dans la colonne de droite, le corps est une description de l'erreur et non du JSON (du XML, dans la console des exercices) : json() lève donc une SyntaxError sur cette ligne. La colonne du milieu teste d'abord ok et affiche un message sans lire le corps : la requête échouée se termine par un message à l'écran plutôt que par une erreur.

Tu charges les produits et les avis à afficher sur une page produit. Les avis ne sont pas encore publiés.

① Récupère fixtures/fr/products.json et affiche si la requête a réussi (true / false).

② Récupère fixtures/fr/reviews.json et affiche si la requête a réussi.

③ Si ② a échoué, affiche « Aucun avis pour l'instant » ; sinon, affiche le nombre d'avis sous la forme « Avis : 5 ».

④ Si ① a réussi, transforme le corps en tableau et affiche le nom du produit dont le stock est à 0.

Éditeur JavaScript / TypeScript

Exécuter le code pour voir le résultat

Récupérer deux fichiers à la fois — fonctions async et Promise.all

Un écran d'administration qui affiche côte à côte le nombre de membres et le nombre de commandes a besoin de deux fichiers JSON, et aucun ne dépend de l'autre : tu les charges donc en parallèle (lancer plusieurs tâches en même temps, puis attendre), comme dans l'article sur async / await. Mais si tu appelles fetch deux fois et que tu passes les résultats à Promise.all, tu récupères deux Response, pas les corps.

Si tu regroupes tout, de fetch jusqu'à json(), dans une seule fonction async, la Promise renvoyée par son appel passe à fulfilled avec la valeur du corps. Si tu appelles cette fonction deux fois et que tu attends les deux appels avec Promise.all, tu obtiens un tableau des valeurs des corps, dans l'ordre où tu les as passés.

// Regroupe fetch jusqu'à json() et renvoie la valeur du corps (null si introuvable)
async function loadJson(path) {
  const response = await fetch(path);
  if (!response.ok) {
    return null;
  }
  return await response.json();
}

// Si tu passes directement les appels à fetch, tu récupères des Response
const responses = await Promise.all([
  fetch("fixtures/fr/products.json"),
  fetch("fixtures/fr/orders.json"),
]);
console.log(responses[0].length);   // undefined (ce n'est pas un tableau)

// Appeler loadJson deux fois lance les deux requêtes en même temps, et Promise.all rassemble les tableaux des corps
const [products, orders] = await Promise.all([
  loadJson("fixtures/fr/products.json"),
  loadJson("fixtures/fr/orders.json"),
]);
console.log(`Produits : ${products.length} / Commandes : ${orders.length}`);   // Produits : 4 / Commandes : 3
Les résultats de fetch ne contiennent pas les corps
2 fetch()à Promise.allFini à l'arrivéedes 2 ResponseOn obtient2 Responseresponses[0].lengthvaut undefined2 loadJson()à Promise.allChacun attendjusqu'à json()On obtient2 tableauxproducts.lengthvaut 4
La ligne du haut se termine dès que les Response arrivent. Pour rassembler les corps, passe à Promise.all les appels d'une fonction qui attend json().

Si une seule des Promises passées est rejected, Promise.all échoue sans transmettre aucun des autres résultats. loadJson passe à fulfilled avec null quand ok vaut false : si un fichier manque, tu récupères quand même l'autre tableau.

Sur un écran de synthèse des ventes, tu charges en même temps les membres, les commandes et les avis. Le tableau de chemins paths est déjà déclaré, et les avis ne sont pas encore publiés.

① Écris une fonction async loadList qui renvoie la valeur du corps en cas de succès et un tableau vide en cas d'échec.

② Passe chaque chemin de paths à loadList, attends-les tous ensemble, et affiche une ligne comme « Membres : 4 / Commandes : 3 / Avis : 0 ».

③ Retrouve grâce à userId le membre qui a passé la première commande, et affiche son nom.

Éditeur JavaScript / TypeScript

Exécuter le code pour voir le résultat
QUIZ

Vérification des connaissances

Répondez à chaque question une par une.

Question 1Après l'exécution de const response = await fetch(url);, que contient response ?

Question 2Avec un chemin qui n'existe pas, quel est l'état juste après l'exécution de const res = await fetch(url); ?

Question 3Dans du code qui s'exécute dans /tools/js-editor/runner.html, où fetch("users.json") va-t-il chercher le fichier ?