Lesson 7/9 8 min

Fetch and APIs

Communicate with servers: fetch(), async/await, JSON, and error handling with try/catch.

FR EN

Qu'est-ce qu'une API ?

Une API (Application Programming Interface) est un moyen pour des programmes de communiquer entre eux. Quand votre site a besoin de données (météo, utilisateurs, produits), il les demande à un serveur via une API.

Dans notre métaphore du bâtiment : l'API, c'est le téléphone. Votre bâtiment appelle le fournisseur pour commander des matériaux, et le fournisseur répond avec ce qu'il a en stock.

Une requête API, c'est :

  1. Vous envoyez une requête (demande) à une URL
  2. Le serveur traite et envoie une réponse (souvent en JSON)
  3. Vous utilisez les données dans votre page

fetch() : faire une requête

fetch() est la méthode JavaScript native pour appeler une API :

fetch("https://jsonplaceholder.typicode.com/users/1")
  .then(function(response) {
    return response.json();
  })
  .then(function(data) {
    console.log(data.name); // "Leanne Graham"
  });

fetch() retourne une Promise : un objet qui représente une opération qui va se terminer plus tard. .then() s'exécute quand la réponse arrive.

fetch() rend tout de suite une Promise en attente ; le code continue, et quand la réponse du serveur arrive, .then() ou await s'exécute. Votre code fetch(url) Serveur (API) requête envoyée Promise : en attente réponse JSON (plus tard)
fetch() rend tout de suite une Promise « en attente ». Le code continue ; quand la réponse arrive, .then() / await s'exécute.

Au fait, c'est quoi une Promise ?

On en parle depuis le début sans ouvrir le capot. Une Promise est un objet qui représente un résultat pas encore disponible. Elle vit dans l'un de trois états :

  • pending (en attente) : l'opération est en cours, on ne sait pas encore.
  • fulfilled (réussie) : elle s'est terminée avec une valeur.
  • rejected (échouée) : elle a échoué avec une erreur.

Une Promise démarre toujours pending, puis bascule une seule fois vers fulfilled ou rejected. Une fois fixée, elle ne change plus jamais.

Les trois états d'une Promise : elle démarre en attente (pending), puis bascule une seule fois vers réussie (fulfilled) via resolve, ou échouée (rejected) via reject. resolve(valeur) reject(erreur) pending en attente fulfilled réussie · une valeur rejected échouée · une erreur .then(valeur) .catch(erreur)
Une Promise part de « pending », puis bascule une seule fois : resolve l'envoie vers fulfilled (lu par .then), reject vers rejected (lu par .catch).

Qui décide du basculement ? Quand on crée une Promise, on lui donne une fonction qui reçoit deux leviers : resolve et reject. On appelle resolve(valeur) quand ça réussit, reject(erreur) quand ça échoue :

const commande = new Promise((resolve, reject) => {
  const cafePret = true;
  if (cafePret) {
    resolve("café chaud");    // → la Promise devient "fulfilled"
  } else {
    reject("plus de grains");  // → la Promise devient "rejected"
  }
});

Et pour récupérer le résultat, deux styles équivalents :

// Style .then / .catch
commande
  .then(valeur => console.log("Reçu :", valeur))    // appelé si resolve()
  .catch(erreur => console.log("Raté :", erreur));  // appelé si reject()

// Style async / await (try/catch pour l'erreur)
try {
  const valeur = await commande;  // rend ce que resolve() a passé
} catch (erreur) {                // attrape ce que reject() a passé
  console.log("Raté :", erreur);
}

C'est exactement ce que fait fetch() en interne : il crée une Promise, appelle resolve quand la réponse du serveur arrive, et reject si le réseau tombe. Toi, tu te contentes de la consommer avec await ou .then().

async/await : la syntaxe moderne

async/await rend le code asynchrone lisible comme du code normal :

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

chargerUtilisateur();

await met le code en pause jusqu'à ce que la Promise soit résolue. async est obligatoire devant la fonction qui utilise await.

Une fonction async renvoie toujours une Promise

Voici le point qui surprend tout le monde : dès que tu mets async devant une fonction, elle renvoie une Promise, quoi que tu fasses à l'intérieur. Même un simple return 42 :

async function f() {
  return 42;
}

f();                  // Promise {<fulfilled>: 42}  ← pas 42 !
const x = await f();  // x = 42  (await déballe la Promise)

En fait, le mot async ne veut pas dire « ça tourne en arrière-plan ». Il veut dire : « cette fonction parle le langage des Promises ». Sa mission, c'est d'emballer automatiquement ta valeur de retour dans une Promise : ton return 42 devient, en coulisses, return Promise.resolve(42).

Et c'est le seul comportement sensé. Une fonction async a le droit de se mettre en pause (à chaque await) et de finir plus tard. Au moment où tu l'appelles, sa valeur finale n'existe peut-être pas encore (le serveur n'a pas répondu...). La seule chose honnête qu'elle peut te rendre tout de suite, c'est un bon de réception : une Promise, qui te livrera la valeur quand elle sera prête.

L'image du comptoir de café : tu commandes, on ne te tend pas le café mais un bipeur (la Promise). Plus tard ça bipe (la Promise est résolue) et tu récupères ton café (la valeur). Une fonction async te tend toujours un bipeur, même si le café était déjà prêt : comme ça tu la traites toujours pareil, sans avoir à deviner si le résultat est prêt maintenant ou plus tard.

async emballe la valeur renvoyée dans une Promise ; await fait l'inverse et en ressort la valeur. async emballe await déballe À l'intérieur : tu renvoies une valeur return 42 Ce que reçoit l'appelant Promise { 42 } Après await : la vraie valeur 42
async met ta valeur dans une Promise (il l'emballe) ; await fait l'inverse et en ressort la valeur (il la déballe).

Les deux mots-clés sont donc les deux faces d'une même pièce : async produit une Promise, await déballe une Promise (et attend qu'elle soit prête). C'est pour ça qu'on écrit await f() pour récupérer le 42, et jamais f() tout seul.

JSON : le format des API

JSON (JavaScript Object Notation) est le format standard des API. C'est très similaire aux objets JavaScript :

// Objet JavaScript → texte JSON
const obj = { nom: "Alice", age: 25 };
const texte = JSON.stringify(obj); // '{"nom":"Alice","age":25}'

// Texte JSON → objet JavaScript
const obj2 = JSON.parse(texte); // { nom: "Alice", age: 25 }

Gestion des erreurs : try/catch

Les requêtes réseau peuvent échouer (pas d'internet, serveur en panne). Utilisez try/catch pour gérer les erreurs :

async function charger() {
  try {
    const response = await fetch("https://api.exemple.com/data");
    if (!response.ok) {
      throw new Error("Erreur HTTP : " + response.status);
    }
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error("Problème :", error.message);
  }
}

Règle d'or : vérifiez toujours response.ok et entourez fetch de try/catch.

What is an API?

An API (Application Programming Interface) is a way for programs to communicate with each other. When your site needs data (weather, users, products), it requests it from a server via an API.

In our building metaphor: the API is the telephone. Your building calls the supplier to order materials, and the supplier responds with what's in stock.

An API request is:

  1. You send a request to a URL
  2. The server processes it and sends a response (usually JSON)
  3. You use the data in your page

fetch() — making a request

fetch() is the native JavaScript method to call an API:

fetch("https://jsonplaceholder.typicode.com/users/1")
  .then(function(response) {
    return response.json();
  })
  .then(function(data) {
    console.log(data.name); // "Leanne Graham"
  });

fetch() returns a Promise — an object representing an operation that will complete later. .then() runs when the response arrives.

fetch() immediately returns a pending Promise ; the code keeps running, and when the server response arrives, .then() or await runs. Your code fetch(url) Server (API) request sent Promise: pending JSON response (later)
fetch() immediately returns a « pending » Promise. The code keeps going; when the response arrives, .then() / await runs.

By the way, what is a Promise?

We've been talking about it without opening the hood. A Promise is an object representing a result that's not available yet. It lives in one of three states:

  • pending (waiting): the operation is in progress, we don't know yet.
  • fulfilled (succeeded): it finished with a value.
  • rejected (failed): it failed with an error.

A Promise always starts pending, then switches exactly once to fulfilled or rejected. Once settled, it never changes again.

The three states of a Promise: it starts pending, then switches once to fulfilled via resolve, or rejected via reject. resolve(value) reject(error) pending waiting fulfilled succeeded · a value rejected failed · an error .then(value) .catch(error)
A Promise starts at "pending", then switches once: resolve sends it to fulfilled (read by .then), reject to rejected (read by .catch).

Who decides the switch? When you create a Promise, you give it a function that receives two levers: resolve and reject. You call resolve(value) on success, reject(error) on failure:

const order = new Promise((resolve, reject) => {
  const coffeeReady = true;
  if (coffeeReady) {
    resolve("hot coffee");    // → the Promise becomes "fulfilled"
  } else {
    reject("out of beans");   // → the Promise becomes "rejected"
  }
});

And to get the result, two equivalent styles:

// .then / .catch style
order
  .then(value => console.log("Got:", value))     // called if resolve()
  .catch(error => console.log("Failed:", error)); // called if reject()

// async / await style (try/catch for the error)
try {
  const value = await order;  // returns what resolve() passed
} catch (error) {             // catches what reject() passed
  console.log("Failed:", error);
}

That's exactly what fetch() does internally: it creates a Promise, calls resolve when the server response arrives, and reject if the network drops. You just consume it with await or .then().

async/await — the modern syntax

async/await makes asynchronous code read like normal code:

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

loadUser();

await pauses the code until the Promise resolves. async is required before the function that uses await.

An async function always returns a Promise

Here's the point that surprises everyone: the moment you put async in front of a function, it returns a Promise, whatever you do inside. Even a plain return 42:

async function f() {
  return 42;
}

f();                  // Promise {<fulfilled>: 42}  ← not 42 !
const x = await f();  // x = 42  (await unwraps the Promise)

Actually, the word async doesn't mean "this runs in the background". It means: "this function speaks the language of Promises". Its job is to automatically wrap your return value in a Promise: your return 42 becomes, behind the scenes, return Promise.resolve(42).

And it's the only sensible behavior. An async function is allowed to pause (at each await) and finish later. When you call it, its final value may not exist yet (the server hasn't replied...). The only honest thing it can hand you right away is a receipt: a Promise, which will deliver the value once it's ready.

The coffee counter image: you order, they don't hand you the coffee but a buzzer (the Promise). Later it buzzes (the Promise is resolved) and you get your coffee (the value). An async function always hands you a buzzer, even if the coffee was already ready: that way you treat it the same every time, without guessing whether the result is ready now or later.

async wraps the returned value in a Promise; await does the opposite and takes the value back out. async wraps await unwraps Inside: you return a value return 42 What the caller receives Promise { 42 } After await: the real value 42
async puts your value into a Promise (it wraps it); await does the opposite and takes the value back out (it unwraps it).

So the two keywords are two sides of the same coin: async produces a Promise, await unwraps a Promise (and waits for it to be ready). That's why you write await f() to get the 42, never f() on its own.

JSON — the API format

JSON (JavaScript Object Notation) is the standard format for APIs. It's very similar to JavaScript objects:

// JavaScript object → JSON text
const obj = { name: "Alice", age: 25 };
const text = JSON.stringify(obj); // '{"name":"Alice","age":25}'

// JSON text → JavaScript object
const obj2 = JSON.parse(text); // { name: "Alice", age: 25 }

Error handling: try/catch

Network requests can fail (no internet, server down). Use try/catch to handle errors:

async function loadData() {
  try {
    const response = await fetch("https://api.example.com/data");
    if (!response.ok) {
      throw new Error("HTTP error: " + response.status);
    }
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error("Problem:", error.message);
  }
}

Golden rule: always check response.ok and wrap fetch in try/catch.

Essayez vous-même
Avec l'IA

Copiez ce prompt dans Claude ou ChatGPT :

Explique-moi les Promises en JavaScript comme si j'étais au restaurant : la commande, l'attente, le plat servi ou l'erreur. Puis montre un exemple avec fetch et async/await.
Ré-explique sans regarder

Sans relire la réponse de l'IA : reprends la métaphore du restaurant et explique les trois états d'une Promise. Qu'est-ce qui la fait passer de l'un à l'autre ?

Une bonne explication dit : une Promise démarre pending (la commande est passée, le plat n'arrive pas tout de suite) ; elle bascule une seule fois vers fulfilled via resolve(valeur) (le plat servi, lu par .then ou await) ou vers rejected via reject(erreur) (la commande ratée, lue par .catch ou try/catch). Une fois fixée, elle ne change plus jamais. fetch() fait exactement ça en interne.
Exercice : Corrigez le code de l'IA

L'IA a oublié d'appeler .json() sur la réponse. Les données ne sont pas parsées. Ajoutez await response.json().

Corrigez le code
Accepter ou rejeter le code de l'IA

L'IA te propose cette fonction pour récupérer un utilisateur. Ton rôle de relecteur : l'accepter telle quelle ou la rejeter, et dire pourquoi.

async function getUser(id) {
  const response = await fetch("https://api.exemple.com/users/" + id);
  const data = await response.json();
  return data.name;
}
À rejeter. Il manque les deux garde-fous de la règle d'or : aucun try/catch (si le réseau tombe, la Promise est rejetée et l'erreur remonte sans être gérée) et surtout aucun contrôle de response.ok. Subtilité importante : fetch() ne rejette pas sur un 404 ou un 500. Un response.json() sur une page d'erreur renverra autre chose que prévu, et data.name sera undefined sans le moindre avertissement. Le réflexe pro : if (!response.ok) throw new Error(...) avant de parser, le tout dans un try/catch.
Rappel libre

Sans remonter dans la leçon : que renvoie fetch(url) immédiatement, et pourquoi faut-il deux await pour obtenir les données JSON ?

fetch(url) renvoie tout de suite une Promise (en attente), pas les données. Premier await : on attend l'objet Response (les en-têtes, le statut HTTP). Le corps n'est pas encore lu. Deuxième await : response.json() lit le flux du corps et le parse en objet JavaScript, ce qui est lui aussi asynchrone, d'où le second await.
Que retourne fetch() ?
Que fait JSON.parse() ?
Pourquoi utiliser try/catch avec fetch ?
Que signifie async devant une fonction ?
Next step

You can fetch data from an API and display it. But the moment you reload the page, everything vanishes. The next lesson keeps memory inside the browser: LocalStorage, put to work in a real mini-project that ties together everything you've learned.

Lesson 8: LocalStorage and mini-project →
Besoin d'un développeur pour votre projet ?

Réponse sous 24h · Sans engagement