Javascript 2


Ces exercices sont ceux de la séance : ils sont intercalés dans les slides, juste après le concept qu’ils font travailler. Cette page les rassemble pour que vous puissiez les reprendre à votre rythme.

Les trois derniers sont marqués Bonus : ils ne sont pas au programme de la séance et servent à ceux qui prennent de l’avance. La correction est là pour tout le monde.

La documentation de Javascript est disponible ici ou là.

Pour aller plus loin

Exercice 1 — La roulette russe

Facile ⏱ 6 min

🎯 Attraper une exception au lieu de la subir.

Sans modifier russianRoulette(), faire afficher You're dead :( quand l’exception est levée, et You survived :D sinon — avec un seul try/catch.

function russianRoulette() {
  if (Math.random() < 0.5) {
    throw new Error("PAN")
  }
}

russianRoulette()

Relancez plusieurs fois : les deux cas doivent apparaître.

Exercice 2 — Première promesse

Facile ⏱ 10 min

🎯 Enchaîner un traitement sur une valeur qui n'est pas encore là.

Récupérer la liste d’utilisateurs et afficher ceux qui habitent en France, avec .then().

const URL =
  "https://raw.githubusercontent.com/JulienUsson/zz3-f2-js/refs/heads/master/api/users.json"

fetch(URL)
  .then((response) => response.json())
  .then((users) => {
    console.log(users)
    // à vous : ne garder que la France
  })

Mettez un console.log avant et après le fetch. Dans quel ordre s’affichent-ils ?

ℹ️ En cours nous utilisons fetch, disponible partout. Sur CodeSandbox vous pouvez aussi utiliser axios.

Exercice 3 — Emballer un callback

Intermédiaire ⏱ 10 min

🎯 Transformer une API à callback en promesse.

setTimeout() est plus vieux que les promesses. Écrire delay(ms) qui retourne une promesse résolue après ms, puis l’utiliser pour jouer ce dialogue :

- Toc toc
   (500 ms)
- Qui est là ?
   (10 s — mettez 1 s pour tester)
- C'est Internet Explorer
function delay(ms) {
  // à vous : un new Promise autour de setTimeout
}

// puis, dans une fonction async, enchaînez les trois répliques

Exercice 4 — Le même, en async/await

Facile ⏱ 8 min

🎯 Voir que async/await n'est que de la syntaxe par-dessus les promesses.

Réécrire l’exercice « Première promesse » avec async/await, sans aucun .then().

const URL =
  "https://raw.githubusercontent.com/JulienUsson/zz3-f2-js/refs/heads/master/api/users.json"

async function showFrenchUsers() {
  // à vous
}

showFrenchUsers()

Comparez les deux versions : laquelle se lit le mieux ?

Exercice 5 — Devinez l'ordre

Facile ⏱ 5 min

🎯 Se construire un modèle mental de la boucle d'événements.

Avant d’exécuter, écrivez sur papier l’ordre dans lequel les six lettres vont s’afficher. Ensuite seulement, lancez le code.

console.log("A")

setTimeout(() => console.log("B"), 0)

async function go() {
  console.log("C")
  await null
  console.log("D")
}
go()

Promise.resolve().then(() => console.log("E"))

console.log("F")

Si votre prédiction est fausse, c’est le but de l’exercice : reprenez le schéma de la boucle d’événements et justifiez chaque position.

Exercice 6 — La chaîne cassée

Intermédiaire ⏱ 10 min

🎯 Comprendre pourquoi il faut *retourner* la promesse dans un `.then()`.

Ces deux fonctions sont censées faire la même chose. L’une remonte bien l’erreur, l’autre ne se termine jamais.

Exécutez, observez, puis réparez versionThen en ajoutant un seul mot.

const fetchUser = async () => ({ id: 1 })
const fetchPosts = async () => { throw new Error("serveur HS") }

async function versionAsync() {
  const user = await fetchUser()
  return await fetchPosts(user)
}

function versionThen() {
  return new Promise((resolve, reject) => {
    fetchUser()
      .then((user) => {
        fetchPosts(user).then((posts) => resolve(posts))
      })
      .catch(reject)
  })
}

versionAsync().catch((e) => console.log("async →", e.message))
versionThen().catch((e) => console.log("then  →", e.message))
setTimeout(() => console.log("--- 1 s plus tard ---"), 1000)

Exercice 7 — Quand la requête échoue

Intermédiaire ⏱ 8 min

🎯 Ne jamais laisser une promesse rejetée faire tomber le programme.

Afficher Users loaded si la requête aboutit, Something went wrong si elle échoue — sans planter — et Request finished dans tous les cas.

// Cassez volontairement l'URL pour tester le cas d'erreur
const URL = "https://raw.githubusercontent.com/JulienUsson/CASSE/users.json"

async function loadUsers() {
  // try / catch / finally
}

loadUsers()

À faire dans les deux styles : .then().catch().finally() puis try/catch/finally.

Exercice 8 — Deux requêtes en parallèle

Intermédiaire ⏱ 8 min

🎯 Lancer deux requêtes en même temps plutôt que l'une après l'autre.

Récupérer les deux listes d’utilisateurs, les fusionner et afficher le total.

const BASE = "https://raw.githubusercontent.com/JulienUsson/zz3-f2-js/refs/heads/master/api"

async function countAllUsers() {
  // Promise.all, puis fusionner les deux tableaux
}

countAllUsers()

Mesurez avec performance.now(), comme sur la slide « Mesurer la différence », puis comparez avec deux await à la suite. Combien de temps gagnez-vous ?

Exercice 9 — Abandonner au bout de N secondes

Intermédiaire ⏱ 10 min

🎯 Faire courir deux promesses et ne garder que la première.

Écrire fetchWithTimeout(promise, ms) qui échoue si la promesse met plus de ms à se résoudre.

function delay(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms))
}

function fetchWithTimeout(promise, ms) {
  // Indice : faites courir `promise` contre une promesse qui rejette après ms
}

fetchWithTimeout(delay(3000).then(() => "fini"), 1000)
  .then(console.log)
  .catch(() => console.log("Timeout"))

Que devient la promesse perdante ? Est-elle annulée ?

Exercice 10 — Compter les succès et les échecs

Intermédiaire ⏱ 8 min

🎯 Attendre toutes les promesses, même celles qui échouent.

Lancer trois promesses — deux qui réussissent, une qui échoue — puis afficher le nombre de succès et le nombre d’échecs.

const ok = (value) => Promise.resolve(value)
const ko = (reason) => Promise.reject(new Error(reason))

async function report() {
  // Promise.allSettled, puis compter par `status`
}

report()

Refaites le même essai avec Promise.all : que se passe-t-il ?

Exercice 11 — Réessayer en cas d'échec

Difficile ⏱ 12 min Bonus

🎯 Composer vos propres utilitaires à partir des promesses.

Écrire retry(fn, attempts) qui exécute fn (retournant une promesse), réessaie tant qu’elle échoue, et abandonne après attempts tentatives.

let calls = 0
function flaky() {
  calls++
  return calls < 3 ? Promise.reject(new Error("boom")) : Promise.resolve("ok")
}

async function retry(fn, attempts) {
  // à vous
}

retry(flaky, 5)
  .then((value) => console.log("Success", value, "après", calls, "appels"))
  .catch(() => console.log("Failed after 5 attempts"))

Exercice 12 — Espacer les tentatives

Difficile ⏱ 10 min Bonus

🎯 Ne pas marteler un serveur déjà en difficulté.

Améliorer retry pour attendre entre les tentatives : 1 s avant la 2ᵉ, 2 s avant la 3ᵉ, 4 s avant la 4ᵉ.

const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms))

async function retry(fn, attempts) {
  // reprenez votre version, et intercalez un delay qui double à chaque tour
}

Affichez l’horodatage à chaque tentative pour vérifier les écarts. Pourquoi doubler plutôt qu’attendre toujours la même durée ?

Exercice 13 — Limiter la concurrence

Difficile ⏱ 15 min Bonus

🎯 Le morceau de bravoure de la séance.

Écrire parallelLimit(tasks, limit) : un tableau de fonctions retournant des promesses, dont jamais plus de limit ne s’exécutent en même temps.

const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms))

let running = 0
let peak = 0
const task = (n) => async () => {
  running++; peak = Math.max(peak, running)
  await delay(200)
  running--
  return n
}

async function parallelLimit(tasks, limit) {
  // à vous
}

const tasks = [1, 2, 3, 4, 5, 6].map(task)
parallelLimit(tasks, 2).then((r) => console.log(r, "| pic de parallélisme :", peak))

✅ Attendu : [1, 2, 3, 4, 5, 6] dans l’ordre, et un pic de 2.

ℹ️ Promise.race sait vous dire quelle tâche vient de finir.