08

Event Loop & JS Approfondi

Microtasks, macrotasks, generators, Proxy, WeakMap

← Accueil
Pourquoi c'est critique avant les frameworks
React, Vue, Angular sont construits autour de l'asynchrone et des événements. Si tu ne comprends pas l'Event Loop, tu ne comprendras pas pourquoi setState est async, pourquoi un useEffect se déclenche après le render, ou pourquoi tes tests "passent" mais ton UI "lag".

1 Le modèle d'exécution JS

JS est mono-thread : une seule pile d'exécution (Call Stack).

┌─────────────────────────────────────────┐ │ Call Stack │ ← exécution synchrone │ [ main() → fn1() → fn2() ] │ └─────────────────────────────────────────┘ ↕ ┌─────────────────────────────────────────┐ │ Web APIs │ ← setTimeout, fetch, DOM events │ (navigateur gère ça en dehors de JS) │ └─────────────────────────────────────────┘ ↕ ┌──────────────────┐ ┌───────────────────┐ │ Microtask Queue │ │ Macrotask Queue │ │ (Promises) │ │ (setTimeout, │ │ PRIORITAIRE │ │ setInterval) │ └──────────────────┘ └───────────────────┘ ↕ Event Loop surveille et déplace ┌─────────────────────────────────────────┐ │ Call Stack (vide ?) │ └─────────────────────────────────────────┘
💡 Règle d'or : l'Event Loop ne prend une tâche dans la queue QUE si la Call Stack est vide.

2 Microtasks vs Macrotasks

console.log("1 — synchrone");

setTimeout(() => console.log("2 — macrotask (setTimeout)"), 0);

Promise.resolve().then(() => console.log("3 — microtask (Promise)"));

console.log("4 — synchrone");

// Ordre d'affichage :
// 1 — synchrone
// 4 — synchrone
// 3 — microtask (Promise)   ← avant setTimeout même avec 0ms !
// 2 — macrotask (setTimeout)

Microtasks (prioritaires) : .then(), async/await, queueMicrotask()

Macrotasks (après) : setTimeout, setInterval, fetch callbacks, événements DOM

💡 Astuce : les microtasks s'exécutent toutes avant la prochaine macrotask.
Erreur fréquente :
// Croire que setTimeout(fn, 0) s'exécute immédiatement
setTimeout(() => console.log("immédiat ?"), 0);
console.log("ceci s'affiche avant !");

3 Generators & Iterators

Un generator est une fonction qui peut s'interrompre et reprendre.

function* compteur() {
  yield 1;  // s'arrête ici, retourne 1
  yield 2;
  yield 3;
}

const gen = compteur();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: undefined, done: true }

// Utilisable avec for...of
for (const n of compteur()) {
  console.log(n); // 1, 2, 3
}

Cas concret — pagination infinie :

function* paginer(tableau, taillePage) {
  for (let i = 0; i < tableau.length; i += taillePage) {
    yield tableau.slice(i, i + taillePage);
  }
}

const data = [1,2,3,4,5,6,7,8,9,10];
for (const page of paginer(data, 3)) {
  console.log("Page :", page); // [1,2,3], [4,5,6], [7,8,9], [10]
}

4 WeakMap & WeakSet

Comme Map et Set mais les références sont faibles — pas de fuite mémoire.

// Map classique → l'objet reste en mémoire même si plus référencé ailleurs
const map = new Map();
let obj = { nom: "Alice" };
map.set(obj, "données");
obj = null; // obj.nom reste en mémoire car map le retient !

// WeakMap → se nettoie automatiquement quand l'objet n'existe plus
const weak = new WeakMap();
let obj2 = { nom: "Bob" };
weak.set(obj2, "données");
obj2 = null; // garbage collected automatiquement
💡 Quand l'utiliser : données privées liées à des objets DOM, cache léger sans fuite.

5 Proxy — intercepter les accès

Un Proxy enveloppe un objet et intercepte ses opérations.

const handler = {
  get(cible, propriete) {
    console.log(`Accès à : ${propriete}`);
    return cible[propriete];
  },
  set(cible, propriete, valeur) {
    if (typeof valeur !== 'number') throw new TypeError("Seulement des nombres !");
    cible[propriete] = valeur;
    return true;
  }
};

const scores = new Proxy({}, handler);
scores.alice = 95;     // OK
scores.bob = "nul";    // TypeError !
console.log(scores.alice); // "Accès à : alice" puis 95
C'est exactement ce que Vue 3 utilise pour la réactivité (reactive()).

6 Structured Clone & transfert de données

// Copie profonde native (sans JSON.stringify/parse)
const original = { nom: "Alice", date: new Date(), tab: [1, 2, 3] };
const copie = structuredClone(original);
copie.tab.push(4);
console.log(original.tab); // [1, 2, 3] — non modifié

Résumé rapide

ConceptÀ retenir
Event LoopCall Stack vide → prend une tâche
MicrotaskPromises — priorité max
MacrotasksetTimeout — après les microtasks
Generatorfunction* + yield — pause/reprise
WeakMapMap sans fuite mémoire
Proxyintercepte les accès à un objet
structuredClonecopie profonde native
⚙️

Mini-Projet

Visualise l'Event Loop en action avec un projet interactif sur les tâches asynchrones.

Exercices & Solutions

Télécharge les fichiers, ouvre-les dans ton éditeur et travaille directement dedans.

📝 exercices.js

5 exercices sur l'Event Loop, les generators, les microtasks, Proxy et generator + async.

🟢 ×2 Facile 🟡 ×2 Moyen 🔴 ×1 Difficile
⬇ Télécharger
solutions.js

Corrections complètes. À consulter après avoir essayé.

Corrigé complet Best practices
⬇ Télécharger
🧠 Tester mes connaissances