freedzhost.cloud

JavaScript module 3 : l'asynchrone et Node.js

Niveau DébutantTemps estimé 14 hÀ lire avant JavaScript module 2 : les morceaux difficiles, en douceurUn mot inconnu ? Lexique

Quatorze séances pour le virage qui fait de vous un programmeur JavaScript complet : l'asynchrone. Comment le langage attend sans jamais se bloquer, les promesses et async/await, l'appel à des API web avec fetch — puis la sortie du navigateur : Node.js, npm, les fichiers, et un vrai petit serveur HTTP écrit à la main. Méthode : 20 minutes de théorie, 30 de pratique, 10 de synthèse.

Jour 29 — setTimeout et les callbacks : programmer dans le temps

Objectif : comprendre qu'un minuteur ne bloque pas le programme, et que l'ordre d'affichage surprend.

Théorie (20 min)

Jusqu'ici, chaque ligne finissait avant que la suivante commence : du code synchrone. Mais un programme réel doit parfois attendre — un minuteur, une réponse du réseau, un fichier. JavaScript refuse de se figer pendant l'attente : il confie l'attente à l'environnement et continue, puis reprend votre fonction quand l'attente est finie. Cette fonction, donnée pour être rappelée plus tard, est un callback.

console.log("A");
setTimeout(() => {
  console.log("B");
}, 1000);
console.log("C");
// Affiche : A, C, puis B une seconde plus tard

Lisez bien le résultat : B arrive APRÈS C, même si B est écrit avant C. setTimeout(fn, 1000) ne veut pas dire « dors une seconde » mais « dans une seconde au plus tôt, remets fn dans la file ». Le programme continue immédiatement à C. Même avec 0 milliseconde, B passerait après C : un callback de minuteur ne s'exécute jamais avant la fin du code synchrone en cours.

C'est le modèle asynchrone : « fais ceci quand ce sera prêt » au lieu de « attends que ce soit prêt ». Un seul fil d'exécution, qui ne s'arrête jamais — tout le module 3 découle de cette phrase. Les callbacks sont la forme historique ; demain vous verrez la machine qui les orchestre, puis les promesses qui les rendent supportables.

Que va afficher console.log(1); setTimeout(() => console.log(2), 0); console.log(3); ?

  1. 1, 2, 3
  2. 1, 3, 2 — mais seulement avec 1000 ms
  3. 1, 3, 2 — même avec 0 ms
Réponse

1, 3, 2 : le callback rejoint la file, et la file n'est servie qu'après le code synchrone, quel que soit le délai.

Pratique (30 min)

  1. Retapez l'exemple A/B/C dans timers.js, lancez avec node : vérifiez l'ordre A, C, B.
  2. Changez le délai à 0 : l'ordre ne change pas. Notez-le dans le carnet, c'est contre-intuitif et définitif.
  3. setInterval répète : const t = setInterval(() => console.log("tic"), 500); puis arrêtez après 3 tics avec clearTimeout… non : clearInterval(t) depuis un setTimeout de 1600 ms. Faites-le.
  4. Critère de réussite : votre script affiche trois « tic » espacés puis se termine seul (pas de Ctrl+C).
  5. Compte à rebours : un setInterval qui affiche 3, 2, 1, « go », puis s'arrête. Utilisez une variable let dans la closure du callback — vous voyez le jour 16 travailler.

Tips agent : « prédis l'ordre d'affichage de ce script mêlant console.log et setTimeout, ligne par ligne » : bon test pour vérifier VOTRE modèle mental — le sien est souvent juste sur ce point. Piège : ne lui faites pas « réparer » l'ordre en déplaçant des lignes ; comprenez d'abord pourquoi. Preuve : la sortie réelle, exécutée.

Synthèse (10 min)

Trois lignes : synchrone vs asynchrone, ce que setTimeout promet vraiment, pourquoi 0 ms ne veut pas dire « tout de suite ». Sans notes : écrivez le script qui affiche « début », « fin », puis « après » une demi-seconde plus tard.

Jour 30 — La boucle d'événements : la cuisine bien comprise

Objectif : se représenter correctement l'event loop : pile, file des tâches, file des microtâches.

Théorie (20 min)

Image d'ensemble : une cuisine avec UN seul cuisinier. Le cuisinier (le fil JavaScript) ne fait qu'une chose à la fois, et il ne reste jamais planté devant le four : il lance la cuisson, note le plat en attente, et s'occupe d'autre chose. Quand le minuteur du four sonne, il ne s'interrompt pas au milieu d'un geste — il finit son geste en cours, PUIS va chercher le plat.

Traduisons précisément :

  • La pile d'appels (call stack) : le geste en cours du cuisinier — la fonction en train de s'exécuter, et celles qui l'attendent. Tant qu'elle n'est pas vide, rien d'autre ne démarre. C'est pourquoi un while (true) gèle tout : le cuisinier ne lâche jamais son geste.
  • Les API de l'environnement : le four et la minuterie — setTimeout, fetch, lecture de fichier. L'attente se fait HORS de la cuisine du cuisinier ; elle ne consomme pas son temps.
  • La file des tâches (macrotâches : callbacks de setTimeout, événements réseau) : la liste des plats prêts à être servis.
  • La file des microtâches (callbacks de promesses, .then, await) : la liste des gestes urgents glissés ENTRE deux plats. Règle exacte : quand la pile se vide, le moteur vide D'ABORD toute la file des microtâches, PUIS prend une tâche. C'est pourquoi une promesse résolue passe avant un setTimeout 0.
console.log("1");
setTimeout(() => console.log("2"), 0);
Promise.resolve().then(() => console.log("3"));
console.log("4");
// 1, 4, 3, 2 — synchrone d'abord, microtâches ensuite, tâches enfin

La boucle d'événements est le tourne-en-rond : pile vide ? → vider les microtâches → prendre une tâche → recommencer. Toute la fluidité d'une page web ou d'un serveur Node tient à cette règle simple : jamais de long geste, beaucoup de petits gestes entrelacés.

Que va afficher setTimeout(() => console.log("t"), 0); Promise.resolve().then(() => console.log("p")); console.log("s"); ?

  1. s, p, t
  2. s, t, p
  3. t, p, s
Réponse

s, p, t : synchrone, puis microtâche (promesse), puis macrotâche (minuteur). La règle des deux files.

Pratique (30 min)

  1. Retapez l'exemple 1/4/3/2, exécutez, puis déplacez les lignes pour produire trois ordres différents que vous aurez PRÉDITS par écrit avant chaque lancement. Critère de réussite : trois prédictions exactes sur trois.
  2. Démontrez le blocage : const debut = Date.now(); while (Date.now() - debut < 2000) {} suivi d'un console.log — deux secondes de silence, puis la suite. Pendant ce temps, aucun callback ne passe : le cuisinier ne lâche pas son geste.
  3. Vérifiez qu'un setTimeout arrivant PENDANT le blocage attend la fin : placez un setTimeout à 500 ms avant la boucle de 2000 ms, mesurez son retard réel (il s'affichera vers 2 s, pas 0,5 s).
  4. Dans le carnet, dessinez de mémoire les trois boîtes (pile, microtâches, tâches) et le sens de circulation.

Tips agent : « voici un script mêlant console.log, setTimeout et Promise.then ; donne l'ordre exact et justifie par la règle des deux files » : l'agent est fiable sur ce mécanisme standard. Piège : s'il invoque « le parallélisme », il a tort — un seul fil, jamais deux gestes à la fois. Preuve : la sortie exécutée, collée en retour.

Synthèse (10 min)

Trois lignes : pile / microtâches / macrotâches et leur ordre de service, pourquoi le code synchrone passe toujours en premier, ce qui gèle une page. Sans notes : prédisez l'ordre d'un script combinant les trois, écrit par vous.

Jour 31 — Les promesses : un reçu pour plus tard

Objectif : créer une promesse, et connaître ses trois états.

Théorie (20 min)

Une promesse (Promise) est un objet qui représente un résultat pas encore disponible. Le modèle mental : un reçu de pressing. Vous déposez une chemise, on vous remet un ticket (la promesse), vous continuez votre journée. Plus tard, deux issues possibles : la chemise est prête (la promesse est tenue, fulfilled) ou le pressing vous appelle pour un problème (elle est rejetée, rejected). Entre-temps, elle est en attente (pending). Et une fois tenue ou rejetée, c'est définitif : une promesse ne change jamais deux fois d'état.

const promesse = new Promise((resolve, reject) => {
  setTimeout(() => {
    const ok = true;
    if (ok) {
      resolve("chemise prête");
    } else {
      reject(new Error("tache impossible"));
    }
  }, 1000);
});

Le constructeur prend une fonction exécutée immédiatement, avec deux pouvoirs : resolve(valeur) tient la promesse avec cette valeur, reject(erreur) la rejette. On récupère le résultat avec .then(valeur => ...) et l'erreur avec .catch(err => ...) — demain en détail. Aujourd'hui, l'idée clé : la promesse sépare le moment où on lance le travail du moment où on reçoit le résultat, et les callbacks passent par des canaux propres (valeur d'un côté, erreur de l'autre) — fini les conventions « callback(err, result) » d'antan.

Deux fabriques immédiates : Promise.resolve(42) crée une promesse déjà tenue, Promise.reject(err) déjà rejetée — parfaits pour les essais.

Pratique (30 min)

  1. Retapez la promesse du pressing dans promesse.js, ajoutez promesse.then(v => console.log("tenue :", v)).catch(e => console.log("rejetée :", e.message)); et lancez. « tenue : chemise prête » après une seconde.
  2. Basculez ok à false : l'erreur emprunte le canal catch. Critère de réussite : les deux chemins affichés au moins une fois chacun.
  3. Créez const attente = ms => new Promise(resolve => setTimeout(resolve, ms)); — la promesse qui « dort » ms millisecondes. Testez : attendre(500).then(() => console.log("réveil")); Cette fonction attendre vous servira tout le module.
  4. Jouez avec les états : Promise.resolve(7).then(v => console.log(v * 6)); affiche 42 immédiatement (en microtâche — après le synchrone, vous le savez).
  5. Vérifiez l'unicité : appelez resolve puis reject dans le même constructeur, constatez que seul le premier compte.

Tips agent : « convertis ce code à callbacks (setTimeout) en promesse » : conversion classique et sûre. Piège : l'agent peut oublier le chemin reject — sans lui, une erreur devient un silence infini. Preuve : un essai en échec volontaire doit emprunter le catch.

Synthèse (10 min)

Trois lignes : les trois états et leur irréversibilité, resolve/reject comme deux canaux, l'analogie du reçu. Sans notes : écrivez la fonction attendre(ms) de mémoire et utilisez-la.

Jour 32 — then, catch, finally : chaîner les promesses

Objectif : enchaîner les étapes asynchrones proprement, et propager les erreurs.

Théorie (20 min)

La force des promesses : .then() renvoie une NOUVELLE promesse, tenue avec ce que votre callback a retourné. Les étapes s'enchaînent donc :

attendre(300)
  .then(() => "étape 1")
  .then(texte => texte + " → étape 2")
  .then(chaine => console.log(chaine));
// "étape 1 → étape 2"

Trois règles font tout le chaînage. (1) Ce que le callback retourne devient la valeur de l'étape suivante — un return oublié, et l'étape d'après reçoit undefined (le retour du piège du jour 12). (2) Si le callback renvoie une PROMESSE, la chaîne l'attend : c'est ainsi qu'on enchaîne les vraies opérations lentes, sans imbrication. (3) Un throw (ou un rejet) saute toutes les étapes jusqu'au prochain catch — exactement comme try…catch, mais à travers le temps.

attendre(100)
  .then(() => { throw new Error("panne à l'étape 2"); })
  .then(() => console.log("jamais exécuté"))
  .catch(err => console.log("rattrapé :", err.message))
  .finally(() => console.log("dans tous les cas"));

finally s'exécute succès ou échec — pour le ménage (fermer, remettre à zéro). L'enfer des callbacks imbriqués (callback hell) était l'empilement de fonctions dans des fonctions ; le chaînage le déplie en une liste verticale d'étapes, chacune lisible. Retenez la forme : une ligne par étape, un seul catch en bas, qui voit toutes les pannes.

Pratique (30 min)

  1. Retapez les deux exemples du cours, exécutez, vérifiez les affichages annoncés.
  2. Construisez la chaîne : attendre 200 ms → rendre 21 → le doubler → afficher 42. Critère de réussite : « 42 » s'affiche, et si vous retirez le return du doublage, l'étape suivante reçoit undefined — vérifiez-le exprès, puis réparez.
  3. Propagez l'erreur : trois then, un throw dans le deuxième, un catch en bas. Constatez que le troisième then est sauté.
  4. Remettez un then APRÈS le catch : il s'exécute (le catch a « absorbé » l'erreur et rendu une promesse tenue). Notez la nuance dans le carnet : catch avale, finally observe.
  5. Mini-scénario réaliste : simulez « télécharger puis traiter puis sauvegarder » avec trois attendre chaînés et des console.log d'étape.

Tips agent : « déplie ce callback hell en chaîne de then » : un agent aplati bien. Piège : il peut garder des promesses non retournées — chaque étape qui attend quelque chose doit RETURN la promesse. Preuve : les console.log d'étape s'affichent dans l'ordre, pas tous en même temps.

Synthèse (10 min)

Trois lignes : then renvoie une promesse, la règle du return, le saut-au-catch. Sans notes : écrivez la chaîne « attendre, rendre un texte, l'allonger, l'afficher ».

Jour 33 — async / await : l'asynchrone qui se lit comme du synchrone

Objectif : écrire de l'asynchrone avec async/await, et attraper ses erreurs avec try…catch.

Théorie (20 min)

async/await (2017) est la forme moderne et définitive. Deux mots-clés : async devant une fonction la fait renvoyer une promesse ; await devant une promesse, À L'INTÉRIEUR d'une fonction async, suspend cette fonction jusqu'au résultat — sans bloquer personne d'autre.

async function preparerCafe() {
  console.log("eau sur le feu");
  await attendre(1000);          // pause locale, cuisine pas bloquée
  console.log("café versé");
  return "prêt";
}
preparerCafe().then(msg => console.log(msg));

Ce qui se passe vraiment (et vous le savez depuis le jour 30) : au await, la fonction se suspend et rend la main ; sa suite est rangée comme une microtâche qui repartira quand la promesse sera tenue. Le code se LIT de haut en bas comme du synchrone, mais s'exécute en pointillés. C'est du sucre sur les promesses : async/await n'ajoute aucune mécanique nouvelle, il rend la même mécanique lisible.

Les erreurs retrouvent try…catch, le confort du jour 21 :

async function mission() {
  try {
    const a = await etape1();
    const b = await etape2(a);
    return b;
  } catch (err) {
    console.log("échec :", err.message);
  }
}

Deux pièges à connaître dès aujourd'hui : await hors d'une fonction async est une SyntaxError (sauf au niveau racine d'un module ESM — jour 43) ; et oublier await devant une promesse vous laisse manipuler la promesse au lieu de sa valeur — si votre « résultat » s'affiche Promise { <pending> }, c'est ça.

const p = preparerCafe(); console.log(p); affiche quoi ?

  1. "prêt"
  2. Promise { <pending> }
  3. une erreur
Réponse

Une fonction async renvoie une promesse, toujours. Il faut await ou .then pour voir "prêt".

Pratique (30 min)

  1. Reprenez attendre du jour 31. Réécrivez le scénario « télécharger, traiter, sauvegarder » du jour 32 en async/await, avec console.log d'étape. Comparez la lisibilité des deux versions dans le carnet.
  2. Critère de réussite : les étapes s'affichent en séquence, espacées, et la fonction async renvoie un résumé que vous affichez avec .then.
  3. Provoquez une erreur dans l'étape 2 (throw), attrapez-la avec try…catch autour des await. Le programme survit et le dit.
  4. Oubliez volontairement un await : affichez la valeur, constatez le Promise { <pending> }, réparez. C'est l'erreur que vous croiserez le plus cette année.
  5. Enrobez l'appel : une fonction main() async qui orchestre tout, lancée par main().catch(...) en dernière ligne — le patron standard des scripts Node.

Tips agent : « réécris cette chaîne de then en async/await » : conversion fiable. Piège : l'agent peut sérialiser ce qui pouvait partir en parallèle (demain) — trois await d'affilée, c'est trois files d'attente. Preuve : le comportement observé (ordre et durée totale) est identique.

Synthèse (10 min)

Trois lignes : async renvoie une promesse, await suspend sans bloquer, try…catch retrouvé. Sans notes : écrivez une fonction async qui attend 500 ms puis renvoie votre prénom, et affichez-le.

Jour 34 — Promise.all et ses frères : plusieurs tâches à la fois

Objectif : lancer des opérations en parallèle et choisir le bon combinateur.

Théorie (20 min)

Trois await d'affilée, c'est de la file d'attente : chaque tâche attend que la précédente finisse. Mais si les tâches sont indépendantes (trois requêtes réseau sans lien entre elles), on les lance EN MÊME TEMPS et on attend le groupe. Les combinateurs :

  • Promise.all([p1, p2, p3]) : réussit quand TOUTES ont réussi, avec le tableau des résultats dans l'ordre des promesses (pas celui d'arrivée). Si UNE échoue, all échoue immédiatement avec cette erreur.
  • Promise.allSettled([...]) : attend TOUT LE MONDE, succès comme échecs, et rend un tableau d'objets { status: "fulfilled", value } ou { status: "rejected", reason }. Le bon choix quand chaque résultat compte individuellement (un tableau de bord, des sauvegardes).
  • Promise.race([...]) : la première terminée gagne, succès ou échec. Usage : les timeouts.
  • Promise.any([...]) : la première RÉUSSIE gagne ; n'échoue que si toutes échouent. Usage : plusieurs miroirs d'une même donnée.
const [meteo, news] = await Promise.all([getMeteo(), getNews()]);
// les deux requêtes sont parties ensemble ; l'attente totale = la plus lente, pas la somme

La distinction clé à l'écriture : const p1 = f(); const p2 = g(); await Promise.all([p1, p2]) lance d'abord, attend ensuite — alors que await f(); await g(); enchaîne. Lancer, PUIS attendre : c'est toute la différence, et elle se voit au chronomètre.

Pratique (30 min)

  1. Avec votre attendre : mesurez le temps de deux await de 500 ms en série (≈ 1 s), puis de Promise.all([attendre(500), attendre(500)]) (≈ 0,5 s). Utilisez Date.now() avant/après. Critère de réussite : les deux mesures écrites au carnet prouvent le parallélisme.
  2. Vérifiez l'ordre des résultats : all avec une promesse lente d'abord, une rapide ensuite — le tableau des valeurs respecte l'ordre d'écriture.
  3. Provoquez un échec : all avec une rejetée parmi trois. all échoue ; remplacez par allSettled et explorez le tableau de statuts : deux fulfilled, un rejected.
  4. Écrivez un timeout réutilisable : const timeout = (p, ms) => Promise.race([p, attendre(ms).then(() => { throw new Error("trop lent"); })]); Testez-le sur une attente courte puis longue.
  5. Scénario : trois « capteurs » simulés (attendre aléatoire + valeur), affichez les trois relevés ensemble quand tout est là, puis le premier arrivé avec race.

Tips agent : « ces quatre appels peuvent-ils partir en parallèle ? sinon lesquels dépendent de quoi ? » : l'analyse de dépendances est un bon usage. Piège : l'agent peut paralléliser des appels dont l'un a besoin du résultat de l'autre. Preuve : la durée totale mesurée chute, et les résultats restent corrects.

Synthèse (10 min)

Trois lignes : lancer puis attendre, all vs allSettled vs race vs any, l'ordre des résultats de all. Sans notes : réécrivez le timeout avec Promise.race.

Jour 35 — fetch : aller chercher des données sur le web

Objectif : appeler une API publique avec fetch et en extraire le JSON.

Théorie (20 min)

fetch(url) demande une ressource au réseau et renvoie une promesse de réponse. Il est disponible dans les navigateurs ET dans Node 18+ : le même code des deux côtés. Deux étapes asynchrones, deux await :

const reponse = await fetch("https://api.open-meteo.com/v1/forecast?latitude=45.76&longitude=4.83&current=temperature_2m");
const donnees = await reponse.json();
console.log(donnees.current.temperature_2m);

Pourquoi deux étapes : le premier await attend les en-têtes de la réponse (statut, type) ; le corps arrive encore par le réseau, et .json() est une seconde promesse qui le lit en entier et le parse (le JSON.parse du jour 22, en asynchrone). Cet exemple appelle Open-Meteo, une API météo gratuite, sans clé ni compte : latitude/longitude de Lyon en paramètres, température actuelle en retour.

Deux disciplines dès maintenant. (1) Une URL porte ses paramètres dans la query string (?cle=valeur&cle2=valeur2) — construisez-la proprement, on verra URLSearchParams demain. (2) Une API se lit dans sa documentation : quels paramètres, quelle forme de réponse. La réponse Open-Meteo est un objet avec une clé current, elle-même contenant temperature_2m : explorez-la dans la console avec Object.keys avant d'écrire des chemins d'accès à l'aveugle.

Pratique (30 min)

  1. Dans un fichier meteo.js, enveloppez le code du cours dans une fonction async main() lancée par main().catch(e => console.error("échec :", e.message)); Lancez : la température actuelle de Lyon s'affiche.
  2. Critère de réussite : un nombre s'affiche, différent d'hier probablement — c'est du réseau réel. Vérifiez sa plausibilité (entre -20 et 45, disons).
  3. Explorez la réponse : affichez Object.keys(donnees) puis donnees.current en entier. Repérez l'unité dans donnees.current_units.
  4. Changez la ville : les coordonnées de votre ville (cherchez « nom de ville latitude longitude »), relancez, comparez.
  5. Demandez une donnée de plus dans la query : &current=temperature_2m,relative_humidity_2m (virgule entre les champs), affichez les deux.

Tips agent : collez un extrait de la réponse JSON réelle et demandez « écris le chemin d'accès à tel champ ». Piège : sans l'extrait, l'agent invente la structure — il « connaît » l'API d'il y a deux ans. Preuve : le chemin exécuté sur la réponse du jour donne une valeur, pas undefined.

Synthèse (10 min)

Trois lignes : les deux await de fetch, la query string, pourquoi explorer la réponse avant d'écrire les chemins. Sans notes : écrivez l'appel météo complet (fetch → json → afficher la température).

Jour 36 — fetch en pratique : erreurs HTTP et timeouts

Objectif : gérer les trois façons dont un fetch peut mal tourner — et construire des URL propres.

Théorie (20 min)

Un fetch peut échouer de trois façons distinctes, et la première surprend tout le monde :

  1. Le réseau est en panne (pas de connexion, domaine inexistant) : la promesse REJETTE. Votre catch la voit.
  2. Le serveur répond une erreur (404, 500) : la promesse RÉUSSIT quand même ! fetch ne rejette pas sur un statut HTTP d'erreur — c'est à vous de tester reponse.ok (true pour les statuts 200-299) ou reponse.status. Oublier ce test est LE bug fetch classique : on parse le corps d'une page d'erreur et on plante plus loin, mystérieusement.
  3. Le serveur ne répond jamais : fetch attend indéfiniment par défaut. La parade : AbortController, un minuteur qui annule la requête.
const controle = new AbortController();
const minuterie = setTimeout(() => controle.abort(), 5000);
try {
  const reponse = await fetch(url, { signal: controle.signal });
  clearTimeout(minuterie);
  if (!reponse.ok) {
    throw new Error(`HTTP ${reponse.status}`);
  }
  const donnees = await reponse.json();
} catch (err) {
  console.log("échec :", err.message);
}

Pour les URL, URLSearchParams encode les paramètres correctement (espaces, accents, &) au lieu de les coller à la main :

const params = new URLSearchParams({ latitude: "45.76", longitude: "4.83", current: "temperature_2m" });
const url = `https://api.open-meteo.com/v1/forecast?${params}`;

Pratique (30 min)

  1. Reprenez meteo.js du jour 35 et ajoutez le test reponse.ok avec le message d'erreur du cours.
  2. Provoquez un 404 : remplacez le chemin par /v1/forecasttt. Critère de réussite : votre programme affiche « échec : HTTP 404 » proprement, au lieu de planter sur du JSON absent.
  3. Provoquez une panne réseau simulée : un domaine inexistant (https://api.inexistant.example/). Constatez que le catch reçoit une erreur réseau, différente du 404 — les deux canaux du cours, vus en vrai.
  4. Ajoutez l'AbortController à 5000 ms, puis réglez-le à 1 ms : l'erreur d'abandon (AbortError) arrive immédiatement. Remettez une valeur raisonnable.
  5. Refactorisez l'URL avec URLSearchParams et ajoutez un paramètre timezone: "Europe/Paris".

Tips agent : « écris une fonction getJSON(url) avec test de statut et timeout à 5 s » : un agent produit ce patron proprement. Piège : il peut omettre le test reponse.ok — c'est le bug le plus courant du code généré. Preuve : exigez et exécutez le test du 404 volontaire.

Synthèse (10 min)

Trois lignes : les trois modes d'échec et qui les détecte, AbortController, URLSearchParams. Sans notes : écrivez getJSON(url) complet, de mémoire.

Jour 37 — Node.js : exécuter un script, process.argv

Objectif : sortir définitivement du navigateur : scripts Node, arguments de la ligne de commande, process.

Théorie (20 min)

Depuis le jour 1, vous lancez des fichiers avec node fichier.js — c'est déjà Node. Aujourd'hui, ce qui change : votre code accède à la machine. L'objet global process représente le programme en cours : process.argv contient les mots de la ligne de commande (tableau : le chemin de node, le chemin du script, puis VOS arguments), process.env les variables d'environnement, process.exit(1) termine en signalant un échec.

// salut.js
const nom = process.argv[2] ?? "inconnu";
console.log(`Bonjour ${nom} !`);
$ node salut.js Ada
Bonjour Ada !

Les deux premiers éléments d'argv sont toujours le moteur et le script : vos arguments commencent à l'index 2. D'où l'idiome process.argv.slice(2) pour n'avoir que les vôtres. Un argument absent vaut undefined — pensez aux valeurs par défaut avec ?? (jour 10).

Node apporte aussi des modules intégrés (la bibliothèque standard) : node:fs pour les fichiers, node:http pour le réseau, node:path pour les chemins, node:test pour les tests (module 4). Le préfixe node: dit explicitement « c'est du intégré, pas un paquet ». Importez-les avec la syntaxe moderne ESM : import { readFile } from "node:fs/promises"; — elle fonctionne dans un fichier .mjs, ou dans un .js d'un projet déclaré ESM (jour 38 et 43).

Pratique (30 min)

  1. Écrivez salut.js du cours, lancez-le avec votre prénom, puis sans argument : la valeur par défaut prend le relais. Critère de réussite : les deux lancements affichent un message correct.
  2. Écrivez somme.js qui additionne tous ses arguments numériques : node somme.js 10 20 12 affiche 42. Pensez à Number() (jour 4 : argv est fait de chaînes) et à slice(2).
  3. Faites échouer proprement : sans argument, somme.js affiche un message d'usage (usage : node somme.js nombres…) et termine avec process.exit(1). Vérifiez le code de sortie : echo $? (Linux/macOS) affiche 1.
  4. Explorez process : affichez process.version, process.platform, et une variable d'environnement (process.env.HOME sous Linux/macOS).
  5. Renommez un de vos scripts en .mjs et importez-y node:fs/promises en haut — juste pour valider que la syntaxe ESM passe (le contenu arrive au jour 39).

Tips agent : « écris un CLI Node qui prend deux arguments et fait X ; usage affiché et exit 1 si un argument manque » : patron bien connu des agents. Piège : le code généré peut oublier Number() sur argv — tout y est chaîne. Preuve : node somme.js 1 2 doit afficher 3, pas "12".

Synthèse (10 min)

Trois lignes : argv et son index 2, exit(1) comme signal d'échec, les modules node: et l'import ESM. Sans notes : écrivez le script qui salue par le nom passé en argument, avec défaut.

Jour 38 — npm et package.json

Objectif : initialiser un projet, comprendre package.json, et installer un paquet en connaissance de cause.

Théorie (20 min)

npm est le gestionnaire de paquets installé avec Node : un magasin de plus d'un million de bibliothèques, et l'outil qui les range chez vous. Le cœur d'un projet est son package.json : la carte d'identité (nom, version, type de modules) et la liste des dépendances — les paquets dont le projet a besoin.

mkdir outils && cd outils
npm init -y

npm init -y crée un package.json par défaut. Ajoutez-y "type": "module" : il déclare que vos .js utilisent la syntaxe ESM (import/export), la moderne — sans lui, Node attend l'ancienne syntaxe require. npm install le-paquet télécharge le paquet dans node_modules/ (le dossier à ne JAMAIS versionner) et l'inscrit dans dependencies ; package-lock.json fige les versions exactes pour que toute réinstallation donne le même contenu.

La discipline qui évite 90 % des malheurs npm : chaque dépendance est du code d'un inconnu qui tourne sur votre machine. Avant d'installer : le paquet est-il maintenu (dernière mise à jour ?), très téléchargé, et surtout nécessaire ? La moitié des besoins courants (dates, fichiers, tests, HTTP) est couverte par les modules intégrés — ce parcours n'installe presque rien, et c'est un choix. La commande honnête à connaître : npm audit liste les vulnérabilités connues de vos dépendances.

Pratique (30 min)

  1. Créez le projet outils avec npm init -y, ouvrez package.json, ajoutez "type": "module". Notez dans le carnet le rôle de chaque champ visible.
  2. Installez un paquet minuscule et utile pour l'exercice : npm install cowsay (oui, la vache qui parle — un classique inoffensif pour apprendre le geste).
  3. Utilisez-le dans vache.mjs : import cowsay from "cowsay"; puis console.log(cowsay.say({ text: "npm fonctionne" })); Lancez. Critère de réussite : la vache ASCII dit « npm fonctionne ».
  4. Lisez package.json après l'installation : cowsay est apparu dans dependencies, package-lock.json existe, node_modules pèse quelques mégaoctets.
  5. Prouvez la reproductibilité : supprimez node_modules, puis npm install (sans nom) restaure tout depuis le lock. Relancez la vache.
  6. Faites npm audit et lisez la sortie, même vide.

Tips agent : « j'ai besoin de X ; le module intégré de Node suffit-il, ou faut-il un paquet ? » : exigez d'abord la réponse « intégré ». Piège : un agent suggère volontiers un paquet par réflexe. Preuve : la page de la doc Node officielle qui couvre le besoin.

Synthèse (10 min)

Trois lignes : package.json / package-lock / node_modules et leurs rôles, "type": "module", pourquoi une dépendance est une décision de confiance. Sans notes : créez un projet, déclarez-le ESM, installez un paquet.

Jour 39 — Lire et écrire des fichiers avec fs/promises

Objectif : lire, écrire et ranger des fichiers avec les versions asynchrones de node:fs.

Théorie (20 min)

node:fs/promises donne les opérations fichiers en promesses — compatibles avec votre async/await du jour 33 :

import { readFile, writeFile, mkdir } from "node:fs/promises";

const texte = await readFile("notes.txt", "utf8");
await writeFile("copie.txt", texte.toUpperCase(), "utf8");
await mkdir("archives", { recursive: true });

Trois détails font la différence entre un script qui marche et un script fiable. (1) Toujours l'encodage "utf8" quand vous lisez du texte : sans lui, readFile renvoie un Buffer (des octets bruts) et vos méthodes de chaînes ne s'y appliquent pas. (2) Les erreurs sont des rejets : fichier absent → ENOENT, droits refusés → EACCES. Enrobez d'un try…catch et affichez err.code : c'est plus précis que le message. (3) Les chemins : votre script résout les chemins relatifs depuis le dossier où vous LANCEZ node, pas depuis le dossier du script — lancez toujours depuis la racine du projet, ou construisez les chemins avec node:path.

Le réflexe JSON s'assemble tout seul maintenant : JSON.parse(await readFile("data.json", "utf8")) lit une donnée structurée, writeFile("out.json", JSON.stringify(objet, null, 2)) la sauvegarde lisible. Vous tenez la moitié des scripts d'automatisation réels.

Pratique (30 min)

  1. Dans le projet outils (déjà ESM), créez notes.txt avec trois lignes de texte, puis lire.mjs qui le lit et affiche son nombre de lignes (split("\n").length).
  2. Critère de réussite : le nombre affiché est exact ; puis renommez le fichier et relancez : votre catch affiche ENOENT proprement au lieu d'une pile de crash.
  3. Écrivez sauvegarde.mjs : un objet de votre choix → JSON indenté écrit dans archives/donnee.json (mkdir recursive d'abord), relu et affiché pour preuve.
  4. Compteur de mots : lisez notes.txt, découpez en mots, comptez les occurrences avec votre reduce du jour 19, écrivez le résultat en JSON. Votre premier vrai script utilitaire.
  5. Testez le piège du dossier de lancement : lancez le script depuis le parent avec node outils/lire.mjs et observez l'ENOENT — le chemin relatif change de sens.

Tips agent : « écris un script qui lit data.json et affiche X » : trivial pour un agent. Piège : il peut utiliser readFileSync « pour simplifier » — exigez la version async, c'est l'habitude à prendre. Preuve : le script tourne, et l'ENOENT volontaire est attrapé.

Synthèse (10 min)

Trois lignes : utf8 ou Buffer, err.code vs message, les chemins relatifs au dossier de lancement. Sans notes : écrivez le script qui copie un fichier texte en majuscules.

Jour 40 — Un mini serveur HTTP avec node:http

Objectif : écrire un serveur qui écoute un port et répond à chaque requête.

Théorie (20 min)

Un serveur web est un programme qui écoute un port de la machine (une porte numérotée ; 3000 par convention en développement) et exécute VOTRE fonction à chaque requête entrante. Node le fait sans rien installer, avec node:http :

import { createServer } from "node:http";

const serveur = createServer((req, res) => {
  res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
  res.end("Bonjour depuis mon serveur !\n");
});

serveur.listen(3000, () => {
  console.log("En écoute sur http://localhost:3000");
});

Décortiquons : createServer prend le callback qui sera appelé pour CHAQUE requête — la boucle d'événements du jour 30 en pleine action : le serveur attend sans bloquer, et votre fonction est rappelée à chaque visite. req décrit la requête (méthode dans req.method, chemin dans req.url) ; res construit la réponse : les en-têtes avec writeHead, le corps avec end (qui termine la réponse — un end oublié et le navigateur attend indéfiniment). localhost désigne votre propre machine ; http://localhost:3000 n'est visible que chez vous.

Pendant que le serveur tourne, le terminal est occupé — c'est normal, il écoute. On l'arrête avec Ctrl+C. Pour le tester : un autre onglet de navigateur, ou curl http://localhost:3000 dans un second terminal.

Pratique (30 min)

  1. Écrivez serveur.mjs du cours, lancez-le, ouvrez http://localhost:3000 dans le navigateur. Critère de réussite : le message s'affiche dans le navigateur, et chaque rafraîchissement est une requête de plus.
  2. Ajoutez un compteur de visites : une variable let en haut, incrémentée dans le callback, affichée dans la réponse. Rafraîchissez : le compteur monte — votre serveur a un état en mémoire.
  3. Affichez req.method et req.url dans la console du terminal, puis visitez http://localhost:3000/chemin/test?x=1 : voyez arriver les détails.
  4. Répondez différemment selon le chemin : / salue, /heure donne l'heure (new Date().toISOString()), tout le reste reçoit un 404 avec res.writeHead(404). Testez les trois.
  5. Oubliez volontairement res.end, rechargez : le navigateur tourne indéfiniment. Réparez — le réflexe est gravé.

Tips agent : « écris un serveur node:http avec deux routes, sans framework » : un agent connaît ce patron. Piège : il peut sortir un exemple avec require() (ancienne syntaxe) ou avec Express non demandé. Preuve : le fichier tourne avec node, en ESM, sans dépendance.

Synthèse (10 min)

Trois lignes : port, requête/callback/réponse, le rôle de res.end. Sans notes : réécrivez le serveur bonjour de mémoire et visitez-le.

Jour 41 — Servir du JSON : routes et réponses

Objectif : transformer le serveur en mini-API : routes propres, JSON, codes de statut.

Théorie (20 min)

Votre serveur d'hier répondait du texte. Une API répond des données structurées — du JSON, vous savez faire depuis le jour 22. Le patron est toujours le même : analyser req.url, aiguiller (c'est le routage), produire un objet, le sérialiser :

import { createServer } from "node:http";

const taches = [
  { id: 1, titre: "apprendre reduce", faite: true },
  { id: 2, titre: "écrire une API", faite: false }
];

const serveur = createServer((req, res) => {
  const url = new URL(req.url, "http://localhost");
  if (url.pathname === "/taches") {
    res.writeHead(200, { "Content-Type": "application/json" });
    res.end(JSON.stringify(taches));
    return;
  }
  res.writeHead(404, { "Content-Type": "application/json" });
  res.end(JSON.stringify({ erreur: "route inconnue" }));
});

serveur.listen(3000);

Trois points de soin. (1) new URL(req.url, ...) parse proprement : url.pathname donne le chemin sans la query, url.searchParams.get("x") les paramètres — le même URLSearchParams qu'au jour 36, côté serveur. (2) Le Content-Type: application/json dit au client ce qu'il reçoit. (3) Le statut fait partie de la réponse : 200 succès, 404 route inconnue, 400 requête mal formée. Un client bien élevé (votre fetch du jour 36, avec son test ok) s'appuie dessus : vous voyez la boucle bouclée, vous êtes maintenant des deux côtés.

Pour le routage, une condition par route et un return après chaque réponse suffisent à ce niveau. Les frameworks (Express, Fastify…) ne font que mécaniser ce que vous venez d'écrire à la main — vous les comprendrez le jour venu.

Pratique (30 min)

  1. Écrivez l'API tâches du cours, lancez-la. curl http://localhost:3000/taches affiche le JSON ; curl -i http://localhost:3000/autre montre le 404.
  2. Critère de réussite : les deux réponses portent le bon statut ET le bon Content-Type — vérifiez avec curl -i, qui montre les en-têtes.
  3. Ajoutez la route /taches?id=1 : searchParams, find (jour 24), et 404 si l'id n'existe pas.
  4. Ajoutez /stats : { total: 2, faites: 1 } calculé sur le tableau avec filter — votre pipeline du module 2 sert une API.
  5. Appelez votre propre API avec fetch depuis un second script Node (async main, test ok, json) : le client et le serveur sont à vous.

Tips agent : « ajoute une route /stats à ce serveur, en suivant le style existant » : un agent prolonge bien un patron visible. Piège : il peut introduire un framework « léger » au passage — refusez, l'exercice est de rester à node:http. Preuve : curl -i sur chaque route, statuts vérifiés.

Synthèse (10 min)

Trois lignes : routage par pathname, Content-Type et statut, la symétrie serveur/client. Sans notes : écrivez l'API à une route qui rend l'heure en JSON.

Jour 42 — Bilan du module 3

Objectif : consolider l'asynchrone et Node avant le module des tests et du projet final.

Théorie (20 min)

Le module 3 vous a changé de statut : vous écrivez maintenant des programmes qui attendent sans geler, parlent au réseau des deux côtés, et touchent aux fichiers. Auto-test oral : « le code synchrone passe toujours en premier », « les microtâches vident avant la prochaine tâche », « une promesse est un reçu à trois états », « await suspend sans bloquer », « fetch ne rejette pas sur un 404 », « lancer puis attendre », « argv commence à l'index 2 », « toujours utf8 ».

Le module 4 répond à la question qui suit naturellement : comment être SÛR que tout ce code fait ce qu'il dit ? Réponse : des tests automatisés, écrits par vous, exécutés en une commande. Puis on assemble tout — langage, asynchrone, Node, tests — dans le projet final : un outil en ligne de commande qui interroge une vraie API et résume sa réponse, testé et documenté.

Un collègue écrit : const rep = await fetch(url); const data = await rep.json(); return data.current.temperature_2m; — sans test de rep.ok. Que risque-t-il ?

  1. rien, fetch rejette sur les erreurs HTTP
  2. sur un 500, il parse un corps d'erreur et plante sur undefined plus loin, loin de la cause
Réponse

fetch ne rejette pas sur les statuts d'erreur : le test ok avant json() est obligatoire.

Pratique (30 min)

  1. L'examen des ordres : écrivez un script qui mélange console.log, setTimeout 0, Promise.resolve().then et un await ; prédissez l'ordre par écrit, exécutez, vérifiez. Critère de réussite : prédiction exacte.
  2. Réécrivez de mémoire : la fonction attendre(ms), getJSON(url) avec test de statut, le serveur node:http à deux routes. Trois fichiers, trois exécutions réussies.
  3. Mini-défi intégrateur (15 min) : un script qui lit une liste de villes depuis un fichier JSON, appelle Open-Meteo pour chacune EN PARALLèle (Promise.all + vos coordonnées en dur dans le fichier), et écrit un rapport JSON trié par température. Tout est déjà appris ; assemblez.
  4. Rangez : js/module-3/, carnet à jour, et listez ce qui demande encore une relecture.

Tips agent : « relis mon mini-défi et liste les erreurs potentielles : await manquant, ok non testé, chemin relatif » : donnez-lui une checklist, pas une correction. Piège : une réécriture complète vous prive de la leçon. Preuve : vous corrigez vous-même chaque point qu'il signale, puis vous relancez.

Synthèse (10 min)

Trois lignes : le mécanisme de l'event loop en une phrase, les trois modes d'échec de fetch, ce que vous voulez prouver avec les tests du module 4. La dernière ligne droite commence.

Utile ? Dites-nous ce qu'il faut mesurer ensuite.

Commentaires

Ni compte ni traceur : un commentaire est lu par un humain avant d'apparaître.