TW3 — Technologies du Web 3

Asynchrone — Promise, async/await et fetch

Passer des callbacks à Promise, chaîner async/await, paralléliser avec Promise.all et utiliser fetch en pratique.

JavaScript est à thread unique — pour ne pas bloquer l'exécution pendant un accès réseau ou disque, il utilise un modèle asynchrone. Ce bloc reconstruit la chaîne complète : callback (l'approche historique), Promise (l'abstraction standard), et async/await (la syntaxe moderne). On conclut avec fetch et les pièges réels du HTTP dans le navigateur.

⏳ Pourquoi asynchrone ?

Une opération asynchrone est une tâche dont le résultat n'est pas disponible immédiatement — appel réseau, lecture de fichier, timer, requête IndexedDB. Sans mécanisme asynchrone, le thread principal serait bloqué en attendant, gelant toute l'interface.

// ❌ Bloquant (dans un cadre théorique) — le navigateur gèle pendant 2 secondes
function attendreEtCharger() {
  const debut = Date.now()
  while (Date.now() - debut < 2000) { /* attente active */ }
  return chargerDepuisReservation()
}

JavaScript résout cela via l'Event Loop : les opérations asynchrones sont déléguées à des APIs du système (ou du navigateur), et la callback est placée dans une file de tâches qui sera traitée quand le thread est libre.

🤝 Callback → Promise

Historiquement, l'asynchrone s'exprimait avec des callbacks :

function lireFichier(chemin, callback) {
  // simulation
  setTimeout(() => callback(null, "contenu du fichier"), 100)
}

lireFichier("data.txt", (err, contenu) => {
  if (err) return console.error(err)
  console.log(contenu)
})

Le problème : les callbacks s'empilent vite ("callback hell") quand on enchaîne plusieurs opérations dépendantes.

Promise — une valeur future

Une Promise représente une valeur qui sera disponible plus tard. Elle est dans un de trois états : pending (en attente), fulfilled (résolue), ou rejected (échouée).

function lireFichier(chemin) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (Math.random() > 0.2) {
        resolve("contenu simulé")
      } else {
        reject(new Error("Fichier introuvable"))
      }
    }, 100)
  })
}

lireFichier("data.txt")
  .then(contenu => console.log(contenu))
  .catch(err => console.error("Erreur :", err.message))

Chaîner des opérations

lireFichier("config.json")
  .then(config => {
    return lireFichier(`${config.path}/données.json`)
  })
  .then(donnees => {
    return traiter(donnees)
  })
  .then(resultat => {
    console.log("Résultat final :", resultat)
  })
  .catch(err => {
    console.error("Erreur à n'importe quelle étape :", err)
  })

Chaque .then() retourne une nouvelle Promise — si la callback retourne une valeur, la Promise est fulfillée avec cette valeur ; si elle retourne une Promise, on attend sa résolution. Un seul .catch() à la fin capture toutes les erreurs de la chaîne.

catch = capture toutes les erreurs

Si une étape de la chaîne lève une erreur ou retourne une Promise rejetée, l'exécution saute directement au .catch() suivant — aucune étape intermédiaire n'est ignorée. C'est l'erreur .catch() oublié qui cause les "Unhandled Promise Rejection".

🕊️ async/await — syntaxe moderne

async/await est du sucre syntaxique sur les Promises. Une fonction async retourne toujours une Promise, et await suspend l'exécution jusqu'à la résolution de cette Promise.

async function chargerProfil(userId) {
  const response = await fetch(`/api/users/${userId}`)
  const user = await response.json()
  return user
}

Gestion d'erreur avec try/catch

async function chargerProfil(userId) {
  try {
    const response = await fetch(`/api/users/${userId}`)
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`)
    }
    const user = await response.json()
    return user
  } catch (err) {
    console.error("Impossible de charger le profil :", err.message)
    throw err  // relever si l'appelant doit le gérer
  }
}

Paralléliser avec Promise.all

Quand plusieurs opérations sont indépendantes, les exécuter en parallèle au lieu d'enchaîner :

async function chargerTout() {
  const [users, posts, config] = await Promise.all([
    fetch("/api/users").then(r => r.json()),
    fetch("/api/posts").then(r => r.json()),
    fetch("/api/config").then(r => r.json()),
  ])
  return { users, posts, config }
}

Avec Promise.all, si une Promise échoue, tout échoue (la première à rejeter). Utiliser Promise.allSettled() quand on veut récupérer tous les résultats (succès ET échecs) :

const results = await Promise.allSettled([
  fetch("/a").then(r => r.json()),
  fetch("/b").then(r => r.json()),
])

results.forEach(r => {
  if (r.status === "fulfilled") console.log("OK:", r.value)
  else console.error("Échec:", r.reason)
})

all vs allSettled vs race

  • Promise.all : parallele, échoue à la première Promise rejetée.
  • Promise.allSettled : parallele, retourne tous les résultats (succès/échec).
  • Promise.race : retourne la première Promise qui se termine (succès ou échec).
  • Promise.any (ES2021) : retourne la première Promise fulfillée (ignore les rejets, sauf si toutes échouent).

🌐 fetch en pratique

fetch est l'API native du navigateur pour les requêtes HTTP. Il retourne une Promise qui se résout avec un objet Response.

GET simple

async function getUsers() {
  const response = await fetch("/api/users")
  const users = await response.json()
  return users
}

POST avec un body JSON

async function creerUser(nom, email) {
  const response = await fetch("/api/users", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ nom, email }),
  })

  if (!response.ok) {
    throw new Error(`Création échouée : ${response.status}`)
  }

  return response.json()  // le serveur renvoie l'user créé
}

Piège critique : fetch ne rejette pas les erreurs HTTP

Contrairement à Axios ou XMLHttpRequest, fetch ne rejette pas la Promise sur un 404 ou un 500. Il résout toujours avec un Response dont .ok est false.

const response = await fetch("/api/produit/introuvable")
console.log(response.ok)  // false pour un 404

// ❌ Sans vérification — on essaie de parser un corps d'erreur comme du JSON
const data = await response.json()  // peut échouer si le serveur renvoie du HTML d'erreur

// ✅ Vérifier response.ok avant de parser
if (!response.ok) {
  throw new Error(`HTTP ${response.status}`)
}
const data = await response.json()

fetch ne rejette pas les 4xx/5xx

C'est le piège #1 avec fetch. Toujours vérifier response.ok (ou response.status) avant de faire .json() ou .text(). Une erreur réseau (pas de connexion, DNS échoué) par contre rejette bien la Promise fetch.

Timeout manuel (fetch n'a pas de timeout natif)

async function fetchAvecTimeout(url, timeoutMs = 5000) {
  const controller = new AbortController()
  const timeout = setTimeout(() => controller.abort(), timeoutMs)

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

🛠️ Exercice

Promise, async/await et fetch - pratique

Maîtriser les Promises, async/await et l'API fetch

Vous allez créer des fonctions asynchrones pour gérer des délais, paralléliser des opérations et effectuer des requêtes HTTP avec vérification des erreurs.

  • Créer une Promise qui se résout après un délai donné
  • Paralléliser des Promises avec Promise.all
  • Utiliser fetch avec vérification du statut HTTP

Partie A — attendre(ms)

Créez une fonction attendre(ms) qui retourne une Promise qui se résout après ms millisecondes. Utilisez setTimeout pour implémenter le délai.

Partie B — attendreTout(msList)

Créez une fonction attendreTout(msList) qui utilise Promise.all pour attendre tous les délais d'un tableau en parallèle et retourne un tableau des résolutions.

Indice pour Promise.all

Promise.all prend un tableau de Promesses et retourne une Promise qui se résout avec un tableau des valeurs. Utilisez .map() sur msList pour transformer chaque délai en Promise via attendre(), puis passez le résultat à Promise.all().

Partie C — fetchWithCheck(url)

Créez une fonction fetchWithCheck(url) qui effectue un fetch(url), vérifie response.ok (lève une Error contenant le status HTTP si non OK), et retourne response.json().

Piège fetch : les erreurs HTTP ne rejettent pas

Contrairement à d'autres bibliothèques, fetch ne rejette pas la Promise sur un statut 404 ou 500. La Promise se résout toujours avec un objet Response dont la propriété ok est false. Vous devez vérifier response.ok manuellement avant d'appeler .json() — c'est l'objet de la Partie C.

Code à valider

javascript

🧩 Synthèse du bloc

Quiz de fin de bloc

Quiz du chapitre

3 questions
0 / 3 répondue0%
  1. 1Que retourne une fonction async ?
  2. 2Quel mot-clé attend la résolution d'une Promise ?
  3. 3Une Promise en état de succès renvoie :
Répondez à toutes les questions pour valider.

JavaScript avancé

Terminez le quiz du chapitre pour le marquer comme complété.

Tableau de bord

On this page