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
- Comprendre les promesses en js [FR]
- Javascript event loop explained [EN]
- Concurrence et boucle d’événements [FR]
- Async/await [FR]
Exercice 1 — La roulette russe
🎯 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
🎯 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
🎯 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
🎯 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
🎯 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
🎯 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
🎯 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
🎯 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
🎯 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
🎯 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
🎯 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
🎯 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
🎯 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.