JavaScript asynchrone
await. Gère les échecs avec catch, try et finally, et exécute des tâches indépendantes en même temps avec Promise.all et les autres combinateurs de promesses.DébutantUnité 7 sur 9 de la formation Formation : JavaScript : DOM, événements et code asynchrone
Ce que tu vas apprendre
- Distingue le code synchrone du code asynchrone, suis le fonctionnement de la boucle d’événements avec la pile d’appels et ses files, et planifie des opérations avec des minuteries.
- Distingue le code synchrone du code asynchroneExplique pourquoi JavaScript exécute une seule partie de ton code à la fois, pourquoi un code long à exécuter fige une page et comment les opérations asynchrones permettent à un programme de continuer à travailler pendant l’attente.
- Suis le fonctionnement de la boucle d’événementsSuis le rôle de la pile d’appels, de la file de tâches et de la file de microtâches dans l’ordre d’exécution du code, et prévois les résultats d’un code qui combine minuteries et promesses.
- Planifie l’exécution de code avec des minuteriesExécute du code une fois après un délai ou à plusieurs reprises avec setTimeout et setInterval, annule des minuteries et explique pourquoi leur délai est une attente minimale plutôt qu’une durée exacte.
- Distingue le code synchrone du code asynchrone
- Passe des fonctions à des opérations asynchrones, transmets les erreurs selon la convention de l’erreur en premier et comprends pourquoi les fonctions de rappel profondément imbriquées sont difficiles à lire et à maintenir.
- Passe des fonctions de rappel à des opérations asynchronesÉcris des fonctions qui acceptent une fonction de rappel, distingue les fonctions de rappel synchrones des fonctions asynchrones et transmets un résultat asynchrone au code qui en a besoin.
- Gère les erreurs dans les fonctions de rappel et évite l’enfer des fonctions de rappelUtilise la convention de la fonction de rappel avec erreur en premier, explique pourquoi try et catch ne peuvent pas intercepter une erreur levée plus tard dans une fonction de rappel, et réduis l’imbrication du code utilisant des fonctions de rappel.
- Passe des fonctions de rappel à des opérations asynchrones
- Crée des promesses, réagis à leurs résultats avec then, catch et finally, et enchaîne des étapes asynchrones dans une séquence lisible.
- Explique les états d’une promesse et crée une promesseDécris les états possibles d’une promesse, crée des promesses avec le constructeur Promise et encapsule une minuterie dans une promesse.
- Réagis aux résultats des promesses avec then, catch et finallyEnregistre des gestionnaires qui s’exécutent lorsqu’une promesse est tenue, rejetée ou acquittée, et utilise-les pour mettre une page à jour au bon moment.
- Enchaîne des promessesRenvoie des valeurs et des promesses depuis les gestionnaires pour construire des chaînes sans imbrication d’étapes dépendantes, et évite les erreurs d’oubli de return et d’imbrication.
- Explique les états d’une promesse et crée une promesse
- Écris des fonctions asynchrones, suspends-les avec await jusqu’à l’acquittement des promesses, réécris des chaînes de promesses et des API à fonctions de rappel sous cette forme, et évite les erreurs courantes du code asynchrone.
- Écris des fonctions asynchrones et utilise awaitDéclare des fonctions asynchrones, explique ce qu’elles renvoient et utilise await pour suspendre une fonction jusqu’à l’acquittement d’une promesse sans bloquer la page.
- Réécris des chaînes de promesses et des API à fonctions de rappel avec async et awaitTransforme des chaînes de promesses en fonctions asynchrones, encapsule des fonctions utilisant des fonctions de rappel dans des promesses pour pouvoir les attendre, et choisis un style clair.
- Évite les erreurs courantes avec async et awaitRepère et corrige les oublis de await, les fonctions de rappel asynchrones dans forEach, les promesses utilisées comme conditions et les appels asynchrones dont les erreurs ne sont pas gérées.
- Écris des fonctions asynchrones et utilise await
- Intercepte les échecs et prévois une récupération dans les chaînes de promesses et avec try et catch, effectue le nettoyage avec finally et assure-toi qu’aucun échec ne passe inaperçu.
- Gère les rejets dans les chaînes de promessesSuis la propagation d’un rejet dans une chaîne de promesses, récupère après l’erreur ou transmets-la, et place les gestionnaires catch là où ils traiteront les échecs concernés.
- Utilise try, catch et finally avec awaitUtilise les objets Error et l’instruction try, catch et finally pour gérer les échecs des promesses attendues, effectuer le nettoyage de façon fiable et proposer des solutions de repli utiles.
- Rends les échecs visibles et prévois une récupération adaptéeÉvite les rejets non gérés et les erreurs passées sous silence, ajoute du contexte aux erreurs et choisis entre une nouvelle tentative, une solution de repli et une information à l’utilisateur.
- Gère les rejets dans les chaînes de promesses
- Lance des tâches indépendantes en même temps, attends leurs résultats ensemble et choisis le combinateur de promesses adapté aux situations où il faut tout ou rien, obtenir les résultats disponibles, attendre la première tâche terminée ou la première réussite.
- Lance des tâches ensemble et attends leurs résultats avec Promise.allExécute des tâches asynchrones indépendantes en même temps, combine leurs résultats avec Promise.all et explique les délais et le comportement en cas d’échec.
- Choisis entre Promise.allSettled, Promise.race et Promise.anyUtilise allSettled lorsque chaque issue compte, race pour la première tâche acquittée, par exemple pour un délai d’expiration, et any pour la première réussite ; compare les quatre combinateurs.
- Lance des tâches ensemble et attends leurs résultats avec Promise.all
JavaScript asynchrone : cours
1Explique comment JavaScript exécute du code asynchrone
JavaScript doit souvent attendre qu’une personne clique ou qu’une minuterie arrive à son terme. Pendant cette attente, la page doit rester réactive. Représente-toi ce qui s’exécute maintenant, ce qui attend dans l’environnement et ce qui rejoint une file pour plus tard. Utilise ensuite cette représentation pour suivre les résultats dans la console et planifier des minuteries. L’essentiel est de suivre le moment où une fonction est appelée, et pas seulement l’endroit où son nom apparaît dans le code.
1.1 Distingue le code synchrone du code asynchrone
Le code synchrone termine l’instruction en cours avant de passer à la suivante. Le JavaScript de ta page s’exécute sur un seul thread principal : deux parties de ce code ne s’exécutent donc pas au même instant. C’est facile à suivre jusqu’à ce qu’une instruction lance une opération dont le résultat arrivera plus tard.
Commencer maintenant, traiter le résultat plus tard
Une opération asynchrone commence maintenant et transmet son résultat ensuite, par une fonction de rappel ou une promesse. Le navigateur ou Node.js est l’environnement hôte. Il gère l’attente pendant que ton programme continue. Les minuteries, les actions de l’utilisateur et le chargement d’images fonctionnent tous de cette façon.
console.log("Opening");
setTimeout(() => console.log("Ready"), 700);
console.log("Waiting");
Lis l’appel à setTimeout comme « enregistre cette fonction ». Il se termine sans attendre 700 ms. Le script en cours affiche donc d’abord « Opening » et « Waiting ». Après environ 700 ms, la fonction de rappel peut s’exécuter et afficher « Ready ». Le délai exact dépend aussi du moment où le thread principal se libère.
Il y a ici deux activités distinctes : l’environnement suit la minuterie et JavaScript finit par appeler la fonction qui affiche le résultat. L’attente se superpose à d’autres opérations ; la fonction de rappel s’exécute toujours sur le thread principal.
Pourquoi une page se fige
Une longue boucle synchrone occupe le thread. Pendant son exécution, les gestionnaires de clic et les fonctions de rappel des minuteries ne peuvent pas s’exécuter, et la page ne peut pas mettre son affichage à jour par ce thread. Enregistrer une minuterie avec un délai nul ne l’autorise pas à interrompre la boucle.
Supposons qu’une minuterie arrive à son terme pendant qu’un calcul est encore en cours. L’environnement place sa fonction de rappel dans une file. C’est la fin du calcul qui libère le thread ; l’expiration du délai de la minuterie ne peut pas le faire à elle seule. Une fonction de rappel qui effectue un autre long calcul peut aussi figer la page.
Pour réduire l’effet d’un calcul sur la réactivité, divise-le en petites parties qui laissent d’autres tâches s’exécuter entre elles, ou déplace les opérations qui s’y prêtent dans un Web Worker, un thread distinct en arrière-plan fourni par le navigateur. Placer simplement une grande boucle dans une fonction asynchrone laisse cette boucle sur le thread principal.
Trois façons de recevoir un résultat
Une fonction de rappel transmet l’action suivante sous forme de fonction. Une promesse représente une issue future et permet d’ajouter des gestionnaires de réussite et d’échec. Une fonction asynchrone utilise des promesses, tandis que await permet de suspendre son exécution à un endroit précis.
Ces formes coexistent. Une minuterie reçoit toujours une fonction de rappel, même lorsque le code qui l’entoure utilise des promesses. Une fonction asynchrone peut attendre une promesse produite par une fonction enveloppante utilisant une fonction de rappel. Choisis la forme qui permet de suivre facilement le parcours du résultat.
Lorsque tu lis du code asynchrone, repère d’abord les instructions qui s’exécutent pendant l’appel en cours. Repère ensuite les fonctions enregistrées pour plus tard. Une ligne située dans une fonction de rappel enregistrée ne s’exécute pas simplement parce que l’exécution a atteint la ligne qui crée cette fonction.
Points clés
- Le JavaScript synchrone occupe le thread principal jusqu’à la fin du travail en cours.
- L’environnement hôte gère l’attente et planifie le code qui traite le résultat.
- Les fonctions de rappel asynchrones s’exécutent plus tard sur le thread principal.
- Une fonction asynchrone peut toujours bloquer la page avec un long calcul.
1.2 Suis le fonctionnement de la boucle d’événements
La boucle d’événements coordonne les opérations prêtes à s’exécuter. Pour prévoir les résultats, distingue trois choses : les fonctions qui s’exécutent maintenant, les fonctions de rappel qui attendent comme tâches et les opérations liées aux promesses qui attendent comme microtâches.
Suivre la pile et les files
La pile d’appels contient les appels de fonctions actifs. Appeler une fonction y ajoute un cadre ; le retour de cette fonction retire le cadre. La fonction en cours s’exécute jusqu’à ce qu’elle se termine ou appelle une autre fonction.
Une tâche peut être un script, une fonction de rappel de minuterie ou un gestionnaire de clic. Les tâches s’exécutent une par une. Les navigateurs peuvent avoir plusieurs files de tâches : ne suppose donc pas que toutes les sources d’opérations partagent une seule file universelle où le premier arrivé est le premier servi.
La file de microtâches contient les gestionnaires de promesses de then, catch et finally, les continuations après await et les fonctions de rappel passées à queueMicrotask. À un point de contrôle des microtâches après le travail en cours, la file se vide avant la tâche suivante. Les microtâches ajoutées pendant ce traitement s’exécutent aussi avant la tâche suivante.
Suivre un script
console.log("begin");
setTimeout(() => console.log("timer"), 0);
Promise.resolve().then(() => console.log("promise"));
console.log("finish");
- Le script affiche « begin ».
- Il enregistre une fonction de rappel de minuterie ; il ne l’appelle pas.
- Il place le gestionnaire de la promesse tenue dans la file comme microtâche.
- Il affiche « finish » et termine le travail en cours.
- La microtâche affiche « promise ».
- La tâche de la minuterie affiche « timer ».
Une promesse déjà tenue planifie tout de même l’exécution de son gestionnaire pour plus tard. Un délai de 0 ms planifie tout de même une tâche de minuterie.
Inclure les opérations ajoutées en cours de route
Promise.resolve()
.then(() => console.log("first"))
.then(() => console.log("third"));
queueMicrotask(() => console.log("second"));
console.log("now");
Le script affiche « now ». La file de microtâches contient d’abord le premier gestionnaire de promesse, puis la fonction de rappel de queueMicrotask. Lorsque le premier gestionnaire affiche « first » et se termine, il rend le gestionnaire de promesse suivant prêt à s’exécuter. Ce nouveau gestionnaire rejoint la fin de la file. La suite des résultats est « second », puis « third ».
Achèvement et réactivité
Une fois qu’une tâche ou une microtâche commence, une opération ajoutée à une file de façon asynchrone ne peut pas l’interrompre. Ce fonctionnement d’exécution jusqu’à achèvement permet de raisonner sur une modification locale sans qu’une minuterie intervienne soudainement au milieu de la fonction.
Une fois les microtâches terminées, le navigateur peut mettre l’écran à jour lorsqu’une occasion de rendu se présente. Il n’a pas à redessiner la page après chaque tâche. Un flux infini de microtâches peut empêcher les tâches suivantes et le rendu : chaque nouvelle microtâche prolonge le traitement de la file en cours. Reporter une opération dans une microtâche ne garantit donc pas que la page aura l’occasion de réagir.
Pour suivre l’exécution, écris d’abord les résultats immédiats, traite les microtâches dans l’ordre jusqu’à vider leur file, puis passe aux tâches de minuterie prêtes. Ajoute les opérations nouvellement planifiées à la file appropriée au moment où le code les planifie effectivement.
Suivre une minuterie créée par une microtâche
setTimeout(() => console.log("outer timer"), 0);
Promise.resolve().then(() => {
console.log("job one");
setTimeout(() => console.log("inner timer"), 0);
}).then(() => console.log("job two"));
console.log("script");
Le script enregistre la minuterie externe et le premier gestionnaire de promesse, puis affiche « script ». La première microtâche affiche « job one » et enregistre la minuterie interne. Le retour de ce gestionnaire place « job two » dans la file ; cette microtâche s’exécute aussi avant toute tâche de minuterie. Les résultats se terminent par « outer timer », puis « inner timer » : les deux minuteries ont le même délai et la minuterie externe a été enregistrée en premier.
Créer une minuterie pendant une microtâche ne fait pas de sa fonction de rappel une microtâche. Classe l’opération selon l’API de planification qui la reçoit, puis ajoute-la à la file appropriée.
Points clés
- La pile d’appels enregistre les appels actifs.
- Les gestionnaires de promesses et les continuations après await s’exécutent comme des microtâches.
- Traite les microtâches nouvellement ajoutées avant de prendre une autre tâche.
- Les fonctions de rappel en attente ne peuvent pas interrompre le code JavaScript en cours d’exécution.
- Le rendu est possible après les microtâches, mais il n’est pas garanti après chaque tâche.
1.3 Planifie l’exécution de code avec des minuteries
Les minuteries planifient l’exécution ultérieure de fonctions de rappel. Elles mesurent un délai en millisecondes : 1 000 ms correspondent à 1 s. Le délai détermine à partir de quand une fonction de rappel peut être exécutée ; d’autres opérations et les limites du navigateur peuvent retarder son exécution.
Planifier et annuler un appel
function remind(topic) {
console.log("Review " + topic);
}
const reminder = setTimeout(remind, 2000, "promises");
// To cancel before it runs:
clearTimeout(reminder);
Les arguments supplémentaires placés après le délai sont transmis à la fonction de rappel. Un délai omis est considéré comme égal à 0. Conserve l’identifiant de minuterie renvoyé si tu peux avoir besoin de l’annuler. Les navigateurs renvoient un identifiant numérique ; Node.js renvoie un objet Timeout. Les deux fonctionnent avec clearTimeout. Annuler une minuterie terminée ou inexistante n’a aucun effet.
Passe une fonction : setTimeout(remind, 2000, "promises"). Écrire setTimeout(remind("promises"), 2000) appelle remind immédiatement et passe sa valeur de retour à la minuterie. Une fonction fléchée est une autre façon de différer un appel avec des arguments : setTimeout(() => remind("promises"), 2000).
Répéter avec une minuterie périodique ou une nouvelle temporisation
let ticks = 0;
const clock = setInterval(() => {
ticks++;
console.log("Tick " + ticks);
if (ticks === 3) clearInterval(clock);
}, 1000);
La première exécution a lieu après environ une seconde. La troisième fonction de rappel arrête les exécutions suivantes. Conserve l’identifiant de la minuterie périodique et utilise clearInterval lorsque l’opération répétée doit prendre fin.
Une fonction de rappel peut aussi planifier sa propre prochaine temporisation. Lorsqu’elle appelle setTimeout à la fin de son travail synchrone, le délai suivant commence après ce travail. Cela crée une pause entre les exécutions et permet de modifier le délai à chaque fois. Une minuterie périodique continue de demander des exécutions à intervalles fixes ; une opération lente peut laisser peu de pause. Si une exécution lance une opération asynchrone, attends aussi la fin de celle-ci avant de planifier la prochaine exécution lorsque leur chevauchement poserait problème.
Les limites des délais
Un thread occupé retarde les fonctions de rappel prêtes à s’exécuter. Les navigateurs ralentissent aussi les minuteries dans les onglets en arrière-plan, souvent à environ une vérification par seconde ou moins. Lorsque le niveau d’imbrication des minuteries dépasse cinq, la règle HTML sur les minuteries porte les délais inférieurs à 4 ms à 4 ms. Les très grands délais demandent aussi de l’attention : la limite habituelle d’un entier signé sur 32 bits est 2147483647 ms, soit environ 24,8 jours ; les valeurs supérieures peuvent provoquer un dépassement et déclencher la minuterie bien plus tôt.
Capturer la bonne valeur de boucle
for (let i = 1; i <= 3; i++) {
setTimeout(() => console.log(i), 100);
}
Chaque itération avec let possède sa propre liaison : la console affiche donc « 1 », « 2 », « 3 ». Avec var, toutes les fonctions de rappel partagent la même variable. Après la boucle, elle vaut 4 : chacune affiche donc « 4 ».
Attendre une période sans activité
L’anti-rebond annule une temporisation en attente chaque fois qu’un nouvel événement arrive. Une nouvelle temporisation planifie alors l’action après le dernier événement. Avec un délai de 300 ms, une frappe toutes les 100 ms continue de reporter l’action ; elle s’exécute environ 300 ms après la fin de la saisie. Stocke l’identifiant hors du gestionnaire pour que l’appel suivant puisse annuler la minuterie précédente.
Construire un gestionnaire avec anti-rebond
let pendingSave;
function scheduleSave(text) {
clearTimeout(pendingSave);
pendingSave = setTimeout(() => {
console.log("Saved: " + text);
}, 300);
}
scheduleSave("n");
scheduleSave("no");
scheduleSave("note");
Ces appels ont lieu dans le même script. Chacun annule la minuterie associée à l’identifiant précédent avant de le remplacer : seul « Saved: note » s’affiche donc, après environ 300 ms. La dernière fonction de rappel capture le paramètre de texte de cet appel. L’identifiant existe hors de la fonction afin que chaque appel puisse annuler la temporisation planifiée précédemment.
Si l’identifiant était déclaré à nouveau dans chaque appel, l’appel suivant ne conserverait pas l’identifiant de la minuterie précédente. Si un gestionnaire planifiait une temporisation sans annuler la précédente, il produirait les trois messages. L’anti-rebond a besoin à la fois de l’identifiant partagé et de l’annulation avant son remplacement.
Points clés
- Passe une fonction de rappel et mesure les délais en millisecondes.
- Utilise l’identifiant renvoyé pour annuler une minuterie.
- Les délais des minuteries ne garantissent pas des instants d’exécution exacts.
- Une minuterie périodique se répète jusqu’à son annulation.
- Utilise let pour obtenir des liaisons de boucle distinctes.
- L’anti-rebond redémarre un délai après chaque événement.
2Écris et organise du code utilisant des fonctions de rappel
Une fonction de rappel permet au code appelant de fournir la prochaine opération sous forme de fonction. Cette idée simple est à la base des minuteries, des événements et de nombreuses API plus anciennes. Tu vas écrire une petite opération utilisant une fonction de rappel, suivre le résultat jusqu’au code qui en a besoin et transmettre les échecs par un paramètre d’erreur. Tu verras aussi comment des étapes dépendantes créent du code imbriqué et pourquoi une gestion cohérente des erreurs compte à chaque niveau.
2.1 Passe des fonctions de rappel à des opérations asynchrones
Une fonction de rappel est une fonction que tu passes à une autre fonction pour que celle-ci l’appelle. Le code de la fonction de rappel décrit ce qui doit être fait d’un résultat. Son exécution pendant l’appel en cours ou plus tard dépend de l’API qui la reçoit.
Une fonction de rappel peut s’exécuter de façon synchrone
const doubled = [2, 5].map(n => n * 2);
console.log(doubled);
map appelle sa fonction de rappel pour chaque élément avant de renvoyer le nouveau tableau. Le résultat est [4, 10]. La fonction fléchée est une fonction de rappel, mais ce code ne planifie aucune opération future. Tu peux utiliser doubled à la ligne suivante car le tableau est déjà complet.
Transmettre un résultat différé
function prepareLabel(name, callback) {
setTimeout(() => {
callback("Label: " + name);
}, 200);
}
prepareLabel("Mira", label => {
console.log(label);
});
console.log("Label requested");
prepareLabel lance une minuterie et se termine. Le script affiche « Label requested ». Après environ 200 ms, la fonction de rappel de la minuterie construit le libellé et appelle la fonction de rappel fournie, qui affiche « Label: Mira ». L’opération choisit quand appeler la fonction ; le code appelant choisit quoi faire du résultat.
Placer les opérations dépendantes là où le résultat existe
let label;
prepareLabel("Mira", value => {
label = value;
});
console.log(label);
Ce code affiche undefined : l’affectation n’a pas encore eu lieu. Déplace console.log(value) dans la fonction de rappel. Ajouter une temporisation indépendante plus longue avant de lire label reviendrait seulement à deviner quand le résultat sera disponible ; utiliser la fonction de rappel du résultat lie l’opération à sa fin effective.
Un return dans une fonction de rappel différée ne peut pas non plus renvoyer la valeur depuis la fonction qui l’englobe. L’appel à cette dernière est déjà terminé. Dans l’exemple, return "Label: Mira" dans la fonction de rappel de la minuterie renverrait la valeur au mécanisme de la minuterie, laissant le code qui a appelé prepareLabel sans le libellé différé.
Passer l’action suivante sans l’appeler
Une fonction de rappel nommée fonctionne aussi :
function showLabel(label) {
console.log(label);
}
prepareLabel("Mira", showLabel);
showLabel est la fonction elle-même. showLabel() l’appellerait immédiatement sans libellé et passerait sa valeur de retour à la place. Utilise une fonction fléchée enveloppante lorsque l’action ultérieure a besoin d’un argument particulier ou de plusieurs instructions.
Pour deux étapes asynchrones dépendantes, lance la seconde dans la fonction de rappel de la première. Par exemple, après avoir reçu un utilisateur, passe cet utilisateur à une opération qui prépare ses paramètres. Lancer les deux opérations en dehors de leurs fonctions de rappel n’établirait pas cette dépendance.
La fonction qui reçoit la fonction de rappel décide du nombre de fois où elle l’appelle. Certaines fonctions de rappel, comme les gestionnaires d’événements, sont volontairement appelées de nombreuses fois. Une opération qui produit un seul résultat doit appeler sa fonction de rappel de résultat une fois. Une promesse donne à une telle opération un résultat qui ne peut s’acquitter qu’une seule fois.
Points clés
- Le moment où une fonction de rappel s’exécute dépend de l’API qui l’appelle.
- map utilise des fonctions de rappel synchrones ; une fonction de rappel de minuterie s’exécute plus tard.
- Utilise un résultat différé dans sa fonction de rappel ou dans du code appelé par celle-ci.
- Le retour d’une fonction de rappel ne termine pas la fonction qui l’englobe.
2.2 Gère les erreurs dans les fonctions de rappel et évite l’enfer des fonctions de rappel
Une opération asynchrone peut aussi bien échouer que réussir. Une API à fonctions de rappel a besoin d’un moyen clair de transmettre chaque issue et d’un endroit précis où le code appelant peut la traiter.
Vérifier d’abord l’erreur
La convention de l’erreur en premier utilise callback(err, result). Un appel réussi passe null pour l’erreur. Un appel échoué passe un objet Error et ne fournit pas de résultat utilisable.
function readLabel(id, callback) {
setTimeout(() => {
if (id <= 0) {
callback(new Error("Invalid label id"));
return;
}
callback(null, { id: id, text: "Draft" });
}, 200);
}
readLabel(4, (err, label) => {
if (err) {
console.log(err.message);
return;
}
console.log(label.text);
});
Le code qui produit le résultat s’arrête après avoir signalé un identifiant non valide : il ne peut donc pas aussi signaler une réussite. Le code qui consomme le résultat s’arrête après avoir constaté une erreur : il ne peut donc pas essayer de lire label.text dans un résultat absent. Avec l’identifiant 4, la console affiche « Draft ». Avec l’identifiant 0, elle affiche « Invalid label id ».
Les deux retours sont importants. Sans le retour du code qui consomme le résultat, une seconde erreur concernant une valeur non définie pourrait masquer le message initial utile. Sans le retour du code qui produit le résultat, une opération pourrait appeler deux fois la fonction de rappel. Pour une API qui produit un seul résultat, signale exactement une issue.
Le bloc try autour de l’appel est déjà terminé
try {
setTimeout(() => {
throw new Error("Late failure");
}, 0);
} catch (err) {
console.log("Handled");
}
Le try planifie la minuterie et se termine. La fonction de rappel lève une exception dans une tâche ultérieure, lorsque ce try n’est plus actif : « Handled » ne s’affiche donc pas. Intercepte une éventuelle exception dans la fonction de rappel ou transmets l’échec par le paramètre d’erreur de l’API. Une fonction enveloppante renvoyant une promesse devra ensuite convertir explicitement cette erreur de fonction de rappel en rejet.
Garder les étapes dépendantes lisibles
Le code utilisant des fonctions de rappel devient difficile à suivre lorsque chaque étape suivante apparaît dans la fonction de rappel de l’étape précédente. Lire un profil, puis ses projets, puis les tâches d’un projet crée plusieurs niveaux d’imbrication. Chaque opération a aussi besoin de sa propre vérification d’erreur. Ce schéma est souvent appelé enfer des fonctions de rappel ou pyramide de malheur.
Des fonctions nommées peuvent donner à ces étapes des noms qui ont un sens et réduire l’imbrication visible. Garde une transmission des erreurs cohérente entre elles : une étape ultérieure peut échouer tout autant que la première. Les promesses et les fonctions asynchrones offrent une façon moins imbriquée d’exprimer la même séquence tout en maintenant la propagation des échecs.
Toutes les API à fonctions de rappel n’utilisent pas un paramètre d’erreur. Certains objets du navigateur disposent d’événements distincts pour la réussite et l’échec. Pour une image, définis les gestionnaires onload et onerror avant d’affecter une valeur à src, afin que les deux issues aient un gestionnaire au début du chargement. Lis le contrat de l’API avant de supposer qu’un paramètre nommé err existe.
Points clés
- Une fonction de rappel avec erreur en premier reçoit err avant le résultat.
- Termine la fonction après avoir signalé ou traité une erreur.
- Les opérations qui produisent un seul résultat doivent appeler leur fonction de rappel exactement une fois.
- Un try externe ne peut pas intercepter une exception levée plus tard par une fonction de rappel.
- Des étapes nommées ou du code utilisant des promesses réduisent les imbrications profondes.
3Crée et utilise des promesses
Une promesse donne à une opération un objet partagé pour son résultat futur. Elle rend explicites la réussite et l’échec et permet de relier des étapes dépendantes sans imbriquer chacune dans la précédente. Commence par construire une promesse autour d’une minuterie. Ajoute ensuite des gestionnaires, suis les nouvelles promesses qu’ils renvoient et vérifie que chaque étape asynchrone reste reliée à la chaîne qui en dépend.
3.1 Explique les états d’une promesse et crée une promesse
Une promesse représente une issue future. Une fonction peut la renvoyer immédiatement, ce qui permet au code appelant de relier des opérations à un résultat qui n’existe pas encore.
États et acquittement
Une promesse est en attente jusqu’à ce qu’elle soit tenue avec une valeur ou rejetée avec une raison. Les promesses tenues et rejetées sont acquittées ; leur issue ne peut plus changer.
« Résolue » a un sens plus précis que celui d’un synonyme courant de « réussie ». Une promesse résolue avec une autre promesse en attente suit l’issue future de cette dernière, y compris son éventuel rejet. Elle peut donc être résolue tout en étant encore en attente.
Créer une promesse autour d’une opération
function wait(ms) {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
}
wait(200).then(() => console.log("Pause finished"));
new Promise appelle immédiatement sa fonction exécutrice. Celle-ci enregistre une minuterie. Lorsque la minuterie appelle resolve, la promesse est tenue avec undefined et le gestionnaire then enregistré s’exécute plus tard comme microtâche. Cette fonction utilitaire fournit une pause réutilisable sans bloquer le thread.
Une fonction exécutrice peut recevoir à la fois resolve et reject :
function reserve(quantity) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (quantity <= 8) resolve(8 - quantity);
else reject(new Error("Not enough stock"));
}, 200);
});
}
reserve(3).then(left => console.log(left));
reserve(9).catch(err => console.log(err.message));
Pour ces quantités positives, le premier appel est tenu avec 5 et le second est rejeté avec « Not enough stock ». La promesse permet au code appelant de recevoir l’une ou l’autre issue par le même objet renvoyé.
La création lance la fonction exécutrice
console.log("before");
const result = new Promise(resolve => {
console.log("creating");
resolve(12);
});
console.log("after");
result.then(value => console.log(value));
La console affiche « before », « creating », « after », « 12 ». Ajouter then ne lance pas cette fonction exécutrice. Le constructeur l’a déjà exécutée ; then enregistre le code qui utilisera son résultat.
Seul le premier appel à resolve ou reject détermine le sort de la promesse. Les appels suivants ne peuvent pas le modifier. Cela s’applique aussi lorsque la première résolution adopte une autre promesse dont le résultat est encore en attente. Appeler resolve n’arrête pas le code ordinaire de la fonction exécutrice : utilise donc return lorsqu’une branche doit se terminer.
Faire en sorte que chaque chemin se termine correctement
Une exception levée de façon synchrone dans la fonction exécutrice rejette automatiquement la promesse. Une exception levée plus tard dans une fonction de rappel de minuterie se produit en dehors de cet appel et n’est pas convertie en rejet. Utilise reject(new Error("...")) à cet endroit ; sinon, l’erreur peut ne pas être interceptée tandis que la promesse reste en attente.
Une branche qui n’appelle aucune des deux fonctions peut laisser le code appelant attendre indéfiniment. Utilise des objets Error comme raisons de rejet afin que les gestionnaires puissent lire un message et que les outils de débogage puissent afficher des informations utiles sur l’erreur.
Promise.resolve(value) crée une promesse tenue pour une valeur ordinaire ou adopte l’issue d’une autre promesse. Promise.reject(error) crée une promesse rejetée. Ajoute un gestionnaire de rejet lorsque tu utilises cette dernière.
Suivre une issue adoptée
const inner = new Promise(resolve => {
setTimeout(() => resolve("packed"), 250);
});
const outer = new Promise((resolve, reject) => {
resolve(inner);
reject(new Error("Too late to replace it"));
});
outer.then(value => console.log(value));
La fonction exécutrice interne lance sa minuterie pendant la construction. La fonction exécutrice externe se résout ensuite avec inner, ce qui lie définitivement outer à l’issue future de inner. L’appel ultérieur à reject ne peut pas modifier cette décision. Outer reste en attente jusqu’à ce que inner soit tenue, puis son gestionnaire affiche « packed » après environ 250 ms.
Si inner était rejetée, outer suivrait ce rejet. Le premier appel à resolve a choisi l’issue à suivre ; il n’a pas garanti une réussite. Cette distinction compte lorsqu’une fonction enveloppante transmet une autre promesse au lieu de fournir une valeur ordinaire déjà disponible.
Points clés
- Les promesses en attente deviennent tenues ou rejetées ; une fois acquittées, leur issue reste fixe.
- La résolution peut adopter l’issue future d’une autre promesse.
- La fonction exécutrice s’exécute de façon synchrone pendant la construction.
- Utilise resolve et reject pour relier les fonctions de rappel ultérieures à la promesse.
- Une exception levée plus tard dans une fonction de rappel ne rejette pas automatiquement la promesse qui l’englobe.
3.2 Réagis aux résultats des promesses avec then, catch et finally
Les gestionnaires de promesses relient du code à une réussite, à un échec ou aux deux issues possibles. Chaque enregistrement d’un gestionnaire renvoie aussi une nouvelle promesse, ce qui permet au code suivant de recevoir ce que produit ce gestionnaire.
Utiliser le gestionnaire adapté à l’issue
p.then(onFulfilled) reçoit la valeur d’une promesse tenue. p.catch(onRejected) reçoit la raison d’un rejet ; c’est une forme abrégée de p.then(undefined, onRejected). Les gestionnaires sont des fonctions et s’exécutent comme des microtâches, même si p est déjà acquittée.
Promise.resolve("draft")
.then(text => console.log(text));
console.log("registered");
Le script affiche « registered » avant que le gestionnaire n’affiche « draft ». Une valeur immédiatement disponible ne rend pas le gestionnaire synchrone.
Il est essentiel de passer une fonction. then(console.log("draft")) affiche « draft » pendant l’enregistrement et passe undefined comme gestionnaire. Utilise then(() => console.log("draft")) lorsque le message doit s’afficher plus tard. then(console.log) passe la fonction d’affichage elle-même, qui reçoit donc la valeur.
Effectuer le nettoyage après une réussite ou un échec
finally est utile pour masquer un indicateur de chargement ou réactiver un bouton après l’acquittement d’une opération. Sa fonction de rappel ne reçoit ni valeur ni erreur en argument.
Promise.resolve(4)
.finally(() => console.log("Cleanup"))
.then(value => console.log(value));
La console affiche « Cleanup », puis « 4 ». Renvoyer une valeur ordinaire depuis la fonction de rappel de nettoyage ne remplace pas 4. Une promesse renvoyée peut retarder l’étape suivante jusqu’à la fin du nettoyage. Si le nettoyage lève une erreur ou renvoie une promesse rejetée, cet échec devient la nouvelle issue de la chaîne.
Sur une page, on définit généralement un état de chargement avant de commencer, on traite le résultat dans then, on signale l’échec dans catch et on réinitialise l’état de chargement dans finally. Place les opérations qui dépendent du résultat dans then ou catch, où l’argument nécessaire existe.
Distinguer les abonnements d’une chaîne
const p = Promise.resolve(4);
p.then(value => value * 2);
p.then(value => console.log(value));
Ce sont deux abonnements distincts à p. Tous deux reçoivent 4. La multiplication appartient à la nouvelle promesse renvoyée par le premier then ; elle ne modifie pas p et ne transmet rien au second abonnement.
Relie-les pour transmettre la valeur transformée :
p.then(value => value * 2)
.then(value => console.log(value));
Le dernier gestionnaire reçoit maintenant 8. La différence tient à la promesse à laquelle chaque gestionnaire est associé.
L’objet renvoyé reste une promesse
Dans const next = p.then(value => value * 2), next est une nouvelle promesse, et non le nombre 8. Le gestionnaire peut s’exécuter après la fin du script qui l’entoure et peut lui-même renvoyer une opération différée. Utilise un autre gestionnaire ou await pour obtenir la valeur. Traiter immédiatement next comme un nombre fait perdre cette information sur le moment où la valeur sera disponible.
Points clés
- Utilise then pour les valeurs, catch pour les rejets et finally pour le nettoyage.
- Passe des fonctions comme gestionnaires.
- Chaque then renvoie une nouvelle promesse.
- Des gestionnaires distincts associés à une même promesse reçoivent son issue initiale.
- Un nettoyage réussi dans finally conserve l’issue précédente ; un nettoyage qui échoue la remplace.
3.3 Enchaîne des promesses
Une chaîne de promesses transmet le résultat d’un gestionnaire au suivant. Chaque maillon doit renvoyer la valeur ou la promesse dont les maillons suivants ont besoin.
Transmettre des valeurs dans la chaîne
Promise.resolve(3)
.then(count => count * 4)
.then(total => total + 2)
.then(total => console.log(total));
Le premier gestionnaire reçoit 3 et renvoie 12. Le deuxième reçoit 12 et renvoie 14. Le dernier affiche « 14 ». Une fonction fléchée dont le corps est une expression renvoie automatiquement cette expression ; un corps de fonction entre accolades a besoin d’un return explicite pour transmettre une valeur.
Renvoyer une promesse pour une étape dépendante
function wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function makeCode() {
return wait(100).then(() => "P4");
}
function makeLabel(code) {
return wait(200).then(() => "Parcel " + code);
}
makeCode()
.then(code => makeLabel(code))
.then(label => console.log(label));
makeCode fournit « P4 » après environ 100 ms. Le gestionnaire suivant renvoie la promesse de makeLabel. La chaîne suit cette promesse, attend environ 200 ms de plus et transmet « Parcel P4 » au dernier gestionnaire. Le code appelant reçoit un libellé, et non une promesse imbriquée dans la valeur.
Repérer un maillon rompu
Compare ce gestionnaire intermédiaire avec celui-ci :
.then(code => {
makeLabel(code);
})
Il lance makeLabel mais ne renvoie rien. Sa nouvelle promesse est tenue avec undefined sans attendre le libellé. Le gestionnaire suivant reçoit donc undefined après l’étape du code, tandis que la préparation du libellé se poursuit séparément. Les erreurs de cette opération séparée ne se propagent plus non plus dans la chaîne externe.
Corrige-le avec return makeLabel(code); entre les accolades ou retire les accolades pour renvoyer l’expression. La même règle s’applique lorsqu’un gestionnaire lance une pause : .then(() => wait(200)) attend ; .then(() => { wait(200); }) n’attend pas.
Réduire l’imbrication et conserver les valeurs nécessaires
Placer un autre then profondément dans le premier gestionnaire peut recréer l’imbrication des fonctions de rappel. Renvoie la promesse interne et place le gestionnaire suivant au niveau externe. Cela conserve un seul parcours pour les délais, les valeurs et les erreurs.
Chaque gestionnaire possède sa propre portée locale. Si une étape ultérieure a besoin à la fois du code et du libellé, renvoie un objet qui les contient :
makeCode()
.then(code => makeLabel(code).then(label => ({ code, label })))
.then(pair => console.log(pair.code + ": " + pair.label));
Ce petit gestionnaire interne construit la valeur combinée. La promesse issue de son enregistrement est renvoyée et reste donc reliée à la chaîne. Une fonction asynchrone peut aussi conserver les deux résultats dans des variables locales ordinaires.
Un rejet ignore les gestionnaires de réussite suivants jusqu’à atteindre un gestionnaire de rejet. Un catch final peut gérer les échecs de toutes les étapes précédentes reliées à la chaîne. Les opérations dépendantes ont leur place dans cette séquence ; les opérations indépendantes peuvent être lancées ensemble avec Promise.all.
Points clés
- Renvoie la valeur dont le gestionnaire suivant a besoin.
- Renvoyer une promesse oblige la chaîne à attendre son issue.
- Le corps d’une fonction fléchée entre accolades a besoin de return.
- Garde les opérations dépendantes et leurs erreurs reliées à la chaîne externe.
- Transmets les données combinées lorsque des étapes ultérieures ont besoin de valeurs locales précédentes.
4Écris du code asynchrone avec async et await
Les fonctions asynchrones permettent d’écrire les étapes utilisant des promesses dans l’ordre où tu veux qu’elles se déroulent. Les valeurs peuvent rester dans des variables nommées et await indique les endroits où la fonction se suspend. Découvre l’effet de cette pause sur le code appelant, convertis du code plus ancien sous cette forme et repère les erreurs qui laissent une promesse là où une valeur est nécessaire. Le comportement des boucles demande une attention particulière : une fonction de rappel asynchrone ne fait pas attendre la fonction qui l’appelle.
4.1 Écris des fonctions asynchrones et utilise await
Une fonction asynchrone renvoie toujours une promesse. À l’intérieur, await suspend cette fonction jusqu’à ce qu’une valeur soit disponible, ce qui permet de lire les étapes dépendantes de haut en bas.
Renvoyer une valeur par une promesse
async function getCount() {
return 6;
}
getCount().then(count => console.log(count));
Appeler getCount() donne une promesse tenue avec 6. Le gestionnaire affiche « 6 ». Si la fonction lève une erreur, la promesse qu’elle renvoie est rejetée. Si elle renvoie une autre promesse, la fonction asynchrone suit l’issue future de cette promesse.
Tu peux déclarer une fonction asynchrone avec async function name(), une fonction fléchée asynchrone comme const name = async () => {}, ou une méthode d’objet asynchrone. Dans tous les cas, elle renvoie une promesse.
Suspendre l’exécution avec await
function wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function showSteps() {
console.log("Step one");
await wait(300);
console.log("Step two");
}
showSteps();
console.log("Caller continues");
La console affiche d’abord « Step one », puis « Caller continues ». Environ 300 ms plus tard, elle affiche « Step two ». L’appel de la fonction exécute son corps de façon synchrone jusqu’au premier await. La pause rend le contrôle au code appelant. Une fois la promesse tenue, le reste de showSteps reprend par la file de microtâches.
Await suspend sa fonction, pas le thread entier. D’autres tâches prêtes peuvent s’exécuter pendant l’attente. Un long calcul synchrone avant ou après await peut toujours bloquer le thread pendant son exécution.
Lire une valeur ou recevoir une erreur
Une promesse tenue fournit sa valeur : const count = await getCount(); affecte 6 à count. Une promesse rejetée lève sa raison à la ligne de await. Entoure l’opération attendue de try et catch lorsque cette fonction doit gérer l’échec.
await accepte aussi des valeurs ordinaires. Dans const count = await 6;, le résultat est toujours 6, mais la continuation de la fonction est différée. Elle ne devient pas synchrone simplement parce qu’aucune opération n’avait besoin d’être attendue.
Vérifier où placer await
Utilise await dans la fonction asynchrone dont le corps le contient. Il est aussi autorisé au niveau supérieur d’un module JavaScript. Le corps d’une fonction ordinaire ne peut pas contenir await ; une fonction asynchrone externe ne rend pas asynchrone une fonction de rappel ordinaire imbriquée.
Le code appelant reçoit toujours une promesse. Pour utiliser la valeur finale, attends-la depuis une autre fonction asynchrone ou ajoute then. Par exemple, const total = getCount(); stocke une promesse ; cette instruction ne stocke pas 6.
Un gestionnaire d’événements peut lui-même être asynchrone. Il peut attendre une étape de préparation avant de mettre une page à jour. Prévois une gestion des erreurs propre à cette action, car le déclenchement de l’événement n’attend pas la promesse renvoyée par le gestionnaire. Au niveau supérieur d’un programme, un appel comme showSteps().catch(console.error) associe un gestionnaire à un échec imprévu.
Points clés
- Une fonction asynchrone renvoie une promesse correspondant à sa valeur de retour ou à l’erreur qu’elle lève.
- Le code avant le premier await s’exécute pendant l’appel.
- Après await, l’exécution reprend plus tard avec une valeur ou lève la raison d’un rejet.
- Await s’utilise dans une fonction asynchrone ou au niveau supérieur d’un module.
4.2 Réécris des chaînes de promesses et des API à fonctions de rappel avec async et await
Une chaîne de promesses peut devenir une fonction asynchrone sans changer ses dépendances. Chaque résultat reçoit une variable locale et chaque étape attendue ne commence qu’une fois disponible le résultat précédent dont elle a besoin.
Remplacer les gestionnaires par des affectations avec attente
function wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function getUser(id) {
return wait(100).then(() => ({ id, name: "Nia" }));
}
function getOrders(user) {
return wait(200).then(() => [user.name + "-1", user.name + "-2"]);
}
async function showOrders(id) {
const user = await getUser(id);
const orders = await getOrders(user);
console.log(user.name + ": " + orders.length + " orders");
}
showOrders(8).catch(console.error);
Le premier await fournit un utilisateur. Alors seulement, getOrders(user) peut commencer. Après environ 300 ms au total, la fonction affiche « Nia: 2 orders ». user et orders sont tous deux accessibles dans la portée de cette dernière ligne. Dans une chaîne, le paramètre de chaque gestionnaire appartiendrait à une portée locale distincte.
La chaîne correspondante renverrait getOrders(user) depuis le premier gestionnaire. La réécrire avec await rend cette attente explicite tout en conservant l’ordre. Un catch final de chaîne devient un try/catch autour des étapes attendues ; finally devient un bloc de nettoyage.
Convertir une API à fonctions de rappel en API à promesses
Ajouter async à une fonction enveloppante ne fait pas de la valeur différée d’une API à fonctions de rappel sa valeur de retour. Relie la fonction de rappel à l’acquittement de la promesse :
function readTag(id, callback) {
setTimeout(() => {
if (id <= 0) callback(new Error("Invalid tag"));
else callback(null, "Tag " + id);
}, 100);
}
function readTagAsync(id) {
return new Promise((resolve, reject) => {
readTag(id, (err, tag) => {
if (err) {
reject(err);
return;
}
resolve(tag);
});
});
}
Dans une fonction asynchrone, const tag = await readTagAsync(8); donne « Tag 8 ». L’appeler avec 0 lève à la place « Invalid tag » au niveau de await. Une réussite résout la promesse de la fonction enveloppante ; un échec la rejette. L’adaptateur change la façon dont l’issue est transmise, pas ce que fait l’opération sous-jacente.
Cette conversion s’appelle la promessification. Pour les API ordinaires qui produisent un seul résultat et suivent la convention de l’erreur en premier, Node.js fournit aussi util.promisify, et de nombreux modules intégrés disposent déjà de variantes utilisant des promesses, comme fs/promises.
Garder un parcours clair
Les minuteries deviennent des pauses grâce à une fonction utilitaire wait qui renvoie une promesse. Une boucle asynchrone peut afficher une étape, attendre la pause, puis commencer l’itération suivante. Avec des fonctions de rappel, il faudrait planifier l’étape suivante dans la fonction de rappel précédente.
Les promesses, les chaînes et les fonctions asynchrones fonctionnent ensemble : une fonction asynchrone peut attendre une chaîne et le code appelant peut ajouter then à la promesse qu’elle renvoie. Dans une fonction, utilise la forme qui permet de suivre facilement les dépendances et la gestion des erreurs. Un constructeur Promise supplémentaire est utile pour adapter une fonction de rappel, mais les opérations qui renvoient déjà des promesses peuvent être attendues directement.
Points clés
- Chaque étape dépendante d’une chaîne peut devenir une affectation avec attente.
- Les variables locales permettent de retrouver les résultats précédents plus loin dans la fonction asynchrone.
- Promessifie les fonctions de rappel en associant les erreurs à reject et les résultats à resolve.
- Les API existantes qui renvoient des promesses peuvent être attendues directement.
4.3 Évite les erreurs courantes avec async et await
Le code asynchrone peut ressembler à du code synchrone tout en transmettant des promesses entre les fonctions. De nombreux bogues viennent d’une lecture trop précoce de la valeur future ou de la supposition qu’une fonction utilitaire attend des fonctions de rappel qu’elle ignore.
Attendre la valeur avant de l’utiliser
async function getQuantity() {
return 18;
}
async function total() {
const quantity = await getQuantity();
console.log(quantity * 3);
}
total().catch(console.error);
Ce code affiche « 54 ». Sans await, quantity contiendrait une promesse. Multiplier cette promesse par 3 produit NaN, et non un calcul différé qui se met automatiquement à jour.
Une condition présente le même piège. Une vérification asynchrone qui renvoie false renvoie tout de même un objet promesse, et les objets sont des valeurs vraies. Utilise if (await allowed()) dans une fonction asynchrone lorsque la branche dépend du booléen futur. Tester if (allowed()) revient à tester l’objet lui-même.
forEach ne collecte pas les promesses des fonctions de rappel
function wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function labelAll(ids) {
ids.forEach(async id => {
await wait(100);
console.log("Label " + id);
});
console.log("Labels finished");
}
labelAll([4, 9]).catch(console.error);
« Labels finished » apparaît avant les libellés. forEach appelle les deux fonctions de rappel, ignore les promesses qu’elles renvoient et termine sa propre itération synchrone. Même await ids.forEach(...) attendrait undefined, et non le travail des fonctions de rappel. Un échec dans ces promesses ignorées se situe aussi hors de la promesse renvoyée par labelAll.
Pour un traitement ordonné, écris une boucle ordinaire :
for (const id of ids) {
await labelOne(id);
}
console.log("Labels finished");
Ici, labelOne est une opération qui renvoie une promesse. Chaque élément commence après la fin du précédent. Pour des éléments indépendants, utilise await Promise.all(ids.map(id => labelOne(id))); avant le dernier affichage. map collecte les promesses renvoyées et Promise.all donne au code appelant une seule promesse pour leur achèvement.
Distinguer l’indépendance de la syntaxe
Attendre immédiatement chaque opération rend les attentes séquentielles. Pour des attentes indépendantes de 1 000, 2 000 et 1 500 ms, cela prend environ 4 500 ms. Lancer les trois et les combiner prend environ 2 000 ms. Une étape qui a besoin d’un résultat précédent doit toujours l’attendre.
Garder les échecs reliés
Une fonction imbriquée a besoin de son propre mot-clé async pour utiliser await, mais ce mot-clé seul ne fait pas attendre le résultat par le code appelant. Vérifie les deux côtés : la fonction de rappel renvoie-t-elle une promesse et le code appelant l’utilise-t-il ?
Pour une opération lancée volontairement sans attendre son achèvement, ajoute un gestionnaire de rejet comme backgroundTask().catch(console.error). Vérifie aussi les retours dans les fonctions enveloppantes asynchrones : async function prepare() { await wait(100); } est tenue avec undefined. Attendre une étape et renvoyer son résultat sont deux décisions distinctes.
Renvoyer les résultats du groupe corrigé
function wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function prepareItem(id) {
await wait(50);
return "Item " + id;
}
async function prepareAll(ids) {
return await Promise.all(ids.map(id => prepareItem(id)));
}
prepareAll([6, 2]).then(items => console.log(items.join(", ")));
Chaque fonction de rappel de map renvoie une promesse et map conserve ces promesses dans un tableau. Promise.all attend les deux et fournit ["Item 6", "Item 2"]. La fonction prepareAll renvoie ce tableau par sa propre promesse : le code appelant affiche donc « Item 6, Item 2 » après la fin du groupe.
Retirer le return de prepareAll attendrait toujours le groupe, mais le code appelant recevrait undefined. Remplacer map par forEach ferait perdre le tableau de promesses. Vérifie séparément la collecte, l’attente et le retour lorsque tu corriges une action portant sur plusieurs éléments.
Points clés
- Attends un nombre ou un booléen futur avant de l’utiliser.
- forEach ignore les promesses renvoyées par les fonctions de rappel asynchrones.
- Utilise for...of pour les opérations séquentielles ou map avec Promise.all pour les opérations indépendantes.
- Une fonction de rappel imbriquée a besoin de son propre mot-clé async.
- Associe un gestionnaire de rejet aux opérations que tu choisis de ne pas attendre.
5Gère les erreurs dans le code asynchrone
Une opération qui échoue fait partie du déroulement du programme. Le code doit décider s’il récupère après l’erreur, fait une nouvelle tentative, signale l’échec ou le transmet à un appelant capable de décider. Suis les échecs dans les chaînes de promesses et dans les appels attendus, puis utilise du code de nettoyage qui s’exécute quelle que soit l’issue. Enfin, place la gestion des erreurs à la limite d’une action afin que l’utilisateur reçoive un message utile et que tu conserves la cause technique.
5.1 Gère les rejets dans les chaînes de promesses
Le rejet d’une promesse suit la chaîne à laquelle elle est reliée jusqu’à ce qu’un gestionnaire de rejet le traite. Comprendre ce que ce gestionnaire renvoie permet de savoir si la chaîne a récupéré après l’erreur ou si elle reste en échec.
Suivre un rejet au-delà des gestionnaires de réussite
Promise.resolve(8)
.then(value => {
throw new Error("Count rejected");
})
.then(value => console.log("Unused: " + value))
.catch(err => {
console.log(err.message);
return 2;
})
.then(value => console.log("Using " + value));
L’exception rejette la nouvelle promesse renvoyée par le premier then. Le gestionnaire de réussite suivant est ignoré : « Unused » ne s’affiche donc jamais. Le catch affiche « Count rejected » et renvoie 2. Ce retour tient le maillon suivant et le dernier gestionnaire affiche donc « Using 2 ».
Une promesse explicitement rejetée et une exception levée dans un gestionnaire empruntent le même chemin de rejet. C’est pourquoi un seul catch final peut gérer les échecs de plusieurs étapes précédentes reliées à la chaîne.
Choisir entre récupération et propagation
Un gestionnaire de rejet qui renvoie une valeur récupère après l’erreur avec cette valeur. Ne rien renvoyer permet aussi de récupérer, mais avec undefined. Consigner un échec ne revient donc pas à laisser la chaîne rejetée :
Promise.reject(new Error("Label missing"))
.catch(err => console.log(err.message))
.then(value => console.log(value));
Le dernier gestionnaire reçoit undefined. Si le code suivant exige un libellé réel, cette récupération silencieuse est probablement incorrecte. Renvoie une solution de repli utilisable ou relance l’erreur.
function reportAndRethrow(err) {
console.error(err);
throw err;
}
Utiliser cette fonction dans catch consigne l’erreur et laisse la promesse suivante rejetée avec la même raison. Un appelant situé plus loin dans la chaîne peut décider quoi faire.
Placer le gestionnaire après les opérations qu’il protège
Un catch gère les échecs qui le précèdent. S’il récupère après l’erreur, les étapes suivantes s’exécutent normalement et leurs nouveaux échecs ont besoin d’un gestionnaire ultérieur. Un gestionnaire placé au milieu ne peut pas intercepter des erreurs qui ne se sont pas encore produites.
Il existe aussi une différence importante entre .then(success, failure) et .then(success).catch(failure). Les deux gestionnaires d’un même then réagissent à l’issue de la promesse reçue. Son gestionnaire d’échec n’intercepte pas une erreur levée par son propre gestionnaire de réussite. Cette erreur appartient à la nouvelle promesse renvoyée par l’appel : un catch ultérieur est donc nécessaire.
Conserver les erreurs utiles
Rejette les promesses avec des objets Error pour que les gestionnaires puissent lire err.message et err.name. Lever une chaîne ou rejeter une promesse avec une chaîne est autorisé, mais le code qui attend les champs d’un objet Error ne les recevra pas. Conserve l’erreur initiale lors de la propagation d’un échec, sauf si l’ajout de contexte aide le code appelant à comprendre quelle opération a échoué.
Points clés
- Les rejets ignorent les gestionnaires de réussite jusqu’à atteindre un gestionnaire de rejet.
- Un retour depuis catch permet de récupérer après l’erreur ; sans valeur renvoyée, la récupération se fait avec undefined.
- Relance l’exception pour que l’échec continue de se propager.
- Un catch protège les étapes reliées qui le précèdent.
- Un gestionnaire de rejet de then ne peut pas intercepter une exception levée par le gestionnaire de réussite du même then.
5.2 Utilise try, catch et finally avec await
try, catch et finally permettent à une fonction asynchrone de gérer les échecs à l’endroit où elle les attend. Utilise cette structure pour regrouper le traitement du résultat, la récupération et le nettoyage.
Une erreur transfère le contrôle
Un objet Error décrit un échec. new Error("Label missing") a pour message « Label missing » et pour nom « Error » ; les outils de débogage peuvent aussi afficher sa pile. Les erreurs intégrées ont des types comme TypeError.
throw arrête l’exécution normale à cet endroit et transfère le contrôle au catch applicable le plus proche. Le code situé après le traitement peut encore s’exécuter lorsque la fonction intercepte sa propre erreur. Lève des objets Error pour que les gestionnaires suivants reçoivent des champs utiles.
Attendre dans le bloc protégé
async function run() {
console.log("Working");
try {
await Promise.reject(new Error("Device busy"));
console.log("Complete");
} catch (err) {
console.log("Failed: " + err.message);
} finally {
console.log("Controls enabled");
}
}
run().catch(console.error);
La promesse rejetée lève une erreur à la ligne de await. Le reste de try est ignoré. La console affiche « Working », « Failed: Device busy », puis « Controls enabled ». Une opération réussie atteindrait « Complete » et exécuterait tout de même le même nettoyage.
Utilise finally pour un indicateur de chargement, l’état d’un bouton ou une autre ressource qui doit être réinitialisée quelle que soit l’issue. Il s’exécute même lorsque try ou catch contient une instruction return. Évite que le nettoyage lève une erreur, sauf si ce nouvel échec doit volontairement remplacer l’issue précédente. Un return dans finally peut remplacer un retour ou une exception antérieurs : évite donc de l’utiliser pour un nettoyage ordinaire.
Ici, renvoyer une promesse est différent de l’attendre
async function readSetting() {
try {
return await Promise.reject(new Error("Missing setting"));
} catch {
return "light";
}
}
readSetting().then(value => console.log(value));
Ce code affiche « light ». La fonction attend dans try : le rejet atteint donc le catch local. Retirer await renvoie immédiatement la promesse rejetée ; la fonction adopte son rejet, mais le catch local ne traite pas cette issue ultérieure. Le code appelant devrait l’intercepter.
Donner à chaque échec un périmètre adapté
Un seul try peut couvrir un groupe d’attentes lorsque toute l’action doit s’arrêter si une étape échoue. Donne à une étape facultative un périmètre plus restreint si elle peut récupérer indépendamment. Par exemple, const settings = await loadSettings().catch(() => ({ theme: "light" })); utilise un objet par défaut et continue.
Une variable déclarée dans try a une portée de bloc. Si le code après le bloc en a besoin, déclare let result; avant try ou termine les opérations qui dépendent du résultat dans try. Ne déplace pas une valeur hors de sa portée simplement pour garder un affichage final à l’endroit que tu préfères.
Lorsque la fonction en cours ne peut pas récupérer après l’erreur, laisse le rejet atteindre le code appelant. L’intercepter uniquement pour le masquer empêche cet appelant de prendre une décision utile.
Points clés
- Les rejets attendus lèvent une erreur à la ligne de await.
- Catch peut récupérer après une erreur, la signaler ou la relancer.
- Finally s’exécute quelle que soit l’issue, y compris en cas de return.
- Utilise return await dans try lorsque le catch local doit gérer le rejet de l’opération renvoyée.
- Les résultats à portée de bloc restent dans leur bloc try.
5.3 Rends les échecs visibles et prévois une récupération adaptée
Une action a besoin d’un périmètre d’interception des erreurs : un endroit qui reçoit son échec final et choisit une réponse. Sans ce périmètre, une promesse peut être rejetée alors que personne ne gère son issue.
Gérer le dernier chemin
Un rejet non géré est un rejet qui n’a toujours pas de gestionnaire lorsque l’hôte effectue sa vérification. La gestion peut utiliser catch, l’argument de rejet de then ou try/catch autour de await. Un navigateur signale le problème et peut déclencher unhandledrejection. Node.js s’arrête par défaut en cas de rejet non géré selon sa politique habituelle : en laisser un sans gestionnaire peut donc mettre fin à un programme.
Une action comme main() ou un gestionnaire de clic asynchrone doit gérer sa promesse finale. Les fonctions de niveau inférieur peuvent transmettre les échecs vers le haut, mais le parcours doit aboutir à un endroit où une décision est prise. Un écouteur global de rejets peut aider à consigner les échecs manqués ; il ne peut pas choisir la meilleure récupération pour chaque action.
Un catch vide passe l’échec sous silence. Consigne les détails techniques avec console.error(err) et affiche un message clair si l’action de l’utilisateur a échoué. Un message comme « Le libellé n’a pas pu être enregistré. Réessaie dans quelques instants. » indique à l’utilisateur une action possible sans exposer de trace de pile.
Ajouter du contexte sans perdre la cause
async function prepareLabel() {
try {
return await Promise.reject(new Error("Device offline"));
} catch (err) {
throw new Error("Could not prepare label", { cause: err });
}
}
prepareLabel().catch(err => {
console.log(err.message);
console.log(err.cause.message);
});
Ce code affiche « Could not prepare label » et « Device offline ». Le message externe indique l’action qui a échoué. La propriété cause conserve l’erreur de niveau inférieur pour le diagnostic. L’option de cause d’Error fait partie d’ECMAScript 2022.
Relancer une nouvelle opération, avec une limite
function wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function withRetry(task, attempts) {
for (let attempt = 1; attempt <= attempts; attempt++) {
try {
return await task();
} catch (err) {
if (attempt === attempts) throw err;
await wait(200);
}
}
}
Appelle cette fonction avec une fonction qui lance une nouvelle tentative et une limite exprimée par un entier positif. Si task échoue deux fois et réussit à son troisième appel, une limite de quatre arrête les essais au troisième appel et renvoie sa valeur. Si les quatre tentatives échouent, la quatrième erreur est relancée. Le délai n’a lieu qu’entre les tentatives.
return await garde le rejet dans ce try, où catch peut lancer une nouvelle tentative. Passer une promesse déjà rejetée au lieu d’une fonction de tâche ne relancerait pas l’opération.
Fais une nouvelle tentative lorsqu’un échec peut être temporaire. Utilise une solution de repli lorsque la fonctionnalité peut fonctionner avec des données par défaut. Arrête l’opération et informe l’utilisateur lorsqu’elle ne peut pas réussir. Si une fonction de niveau inférieur ne peut pas faire ce choix, conserve l’erreur et laisse le code appelant décider.
Points clés
- Toute action asynchrone a besoin d’un gestionnaire pour son échec final.
- Un catch vide masque des informations utiles sur l’échec.
- La cause d’un objet Error conserve l’échec initial tout en ajoutant du contexte.
- Fais une nouvelle tentative en rappelant une tâche, avec un nombre limité de tentatives.
- Choisis une nouvelle tentative, une solution de repli, l’arrêt ou la propagation selon l’échec.
6Exécute des tâches asynchrones en parallèle
Certaines opérations ont besoin de résultats précédents ; d’autres peuvent commencer ensemble. Identifie cette dépendance avant de choisir où placer await. Tu vas combiner des opérations indépendantes, conserver les résultats dans le bon ordre et choisir si ton programme a besoin de toutes les réussites, de toutes les issues, du premier acquittement ou de la première réussite. Chaque choix change ce que signifie un échec et la durée d’attente du code appelant.
6.1 Lance des tâches ensemble et attends leurs résultats avec Promise.all
Des opérations asynchrones indépendantes peuvent attendre en même temps. Le choix de les lancer ensemble dépend des données dont elles ont besoin, et non de l’ajout du mot async.
Lancer les opérations indépendantes avant de toutes les attendre
function wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function makePart(name, ms) {
return wait(ms).then(() => name);
}
async function assemble() {
const parts = await Promise.all([
makePart("cover", 1000),
makePart("pages", 2000),
makePart("label", 1500)
]);
console.log(parts.join(", "));
}
assemble().catch(console.error);
Les trois appels commencent avant que Promise.all n’attende leurs résultats. Environ 2 000 ms plus tard, le tableau contient ["cover", "pages", "label"]. Attendre chaque appel avant de lancer le suivant prendrait environ 4 500 ms. Les délais des minuteries sont approximatifs : les durées réelles peuvent donc être plus longues.
Ici, en parallèle signifie que les attentes se chevauchent. Les gestionnaires de promesses s’exécutent toujours un par un sur le thread principal. Appeler trois fonctions synchrones lourdes dans le tableau exécuterait ces calculs les uns après les autres ; Promise.all ne les déplacerait pas vers des threads distincts.
Les résultats conservent leur place dans les entrées
Le libellé se termine avant les pages, mais reste troisième dans le tableau. Promise.all conserve l’ordre des entrées, et non l’ordre d’achèvement. La déstructuration permet de nommer ces positions :
const [cover, pages] = await Promise.all([
makePart("cover", 1000),
makePart("pages", 2000)
]);
Utilise ce code dans une fonction asynchrone. Pour une collection, ids.map(id => loadItem(id)) lance une opération qui renvoie une promesse par identifiant et donne à Promise.all le tableau de promesses. N’attends pas chaque élément dans une boucle s’ils sont tous indépendants et doivent se chevaucher.
Un échec met fin à l’attente combinée
Promise.all est rejetée dès qu’une entrée est rejetée. Si une partie échoue après environ 300 ms, un catch peut alors recevoir cette raison ; il n’a pas besoin d’attendre la fin de l’opération réussie qui prend le plus de temps.
Les autres opérations continuent de s’exécuter. Leurs effets de bord peuvent encore se produire, mais la promesse combinée ne sera pas tenue plus tard avec leurs valeurs. Cette règle d’échec immédiat (fail-fast) est utile lorsque l’étape suivante exige tous les résultats.
Respecter les dépendances et ajouter rapidement des gestionnaires
Si une opération a besoin d’un utilisateur produit par une autre, obtiens d’abord cet utilisateur. Les appels suivants ne peuvent être combinés que s’ils sont indépendants les uns des autres une fois cet utilisateur disponible.
Lancer deux promesses et les attendre séparément fait bien se chevaucher leurs attentes, mais la seconde peut être rejetée avant que le code n’atteigne son await. Si elle n’a toujours pas de gestionnaire lorsque l’hôte vérifie les rejets, un rejet non géré peut être signalé. Combiner rapidement les promesses avec Promise.all associe un gestionnaire aux deux entrées et donne au code appelant un seul chemin pour les échecs.
Les valeurs ordinaires sont autorisées parmi les entrées et conservent leur place. Promise.all([]) est déjà tenue avec un tableau vide. Un gestionnaire qui lui est associé s’exécute toujours de façon asynchrone.
Points clés
- Lance les opérations indépendantes ensemble pour faire se chevaucher leurs attentes.
- Promise.all renvoie les résultats dans l’ordre des entrées.
- Un seul rejet rejette la promesse combinée sans annuler les autres entrées.
- Les étapes dépendantes ont toujours besoin des résultats précédents.
- Combine rapidement les promesses lancées pour garder leurs chemins d’erreur reliés.
6.2 Choisis entre Promise.allSettled, Promise.race et Promise.any
Le bon combinateur de promesses dépend de ce qui constitue un résultat utile. Détermine si tu as besoin de toutes les réussites, de toutes les issues, du premier acquittement ou de la première réussite.
Conserver les résultats partiels avec allSettled
Promise.allSettled attend chaque entrée et renvoie des objets décrivant les issues dans l’ordre des entrées. Une réussite devient { status: "fulfilled", value } ; un échec devient { status: "rejected", reason }. Une entrée rejetée ne rejette pas la promesse combinée.
Promise.allSettled([
Promise.resolve("Map ready"),
Promise.reject(new Error("Chart unavailable"))
]).then(results => {
for (const result of results) {
if (result.status === "fulfilled") console.log(result.value);
else console.log(result.reason.message);
}
});
Ce code affiche « Map ready » et « Chart unavailable ». Il convient à des panneaux indépendants, lorsqu’un panneau qui fonctionne reste utile même si un autre échoue. Lis value uniquement dans un objet correspondant à une promesse tenue et reason dans un objet correspondant à une promesse rejetée.
Laisser le premier acquittement décider avec race
Promise.race suit la première entrée acquittée, qu’elle soit tenue ou rejetée. Un échec après 100 ms l’emporte sur une réussite après 200 ms. C’est utile pour un délai d’expiration :
function wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function timeout(ms) {
return new Promise((_, reject) => {
setTimeout(() => reject(new Error("Time limit reached")), ms);
});
}
Promise.race([
wait(600).then(() => "Prepared"),
timeout(250)
]).then(console.log).catch(err => console.log(err.message));
Le délai d’expiration provoque un rejet en premier : la console affiche donc « Time limit reached ». L’opération de 600 ms continue de s’exécuter. Une course acquitte la promesse combinée du code appelant ; elle n’annule pas les opérations perdantes.
Attendre la première réussite avec any
Promise.any ignore les entrées qui échouent tant qu’une réussite reste possible. Si une opération échoue après 100 ms et qu’une autre est tenue après 200 ms, elle donne la valeur de cette dernière. Un rejet plus rapide ne l’emporte pas.
Si toutes les entrées sont rejetées, any est rejetée avec une AggregateError. Son tableau errors conserve les raisons des rejets dans l’ordre des entrées. Le code appelant peut ainsi examiner pourquoi chaque tentative a échoué, même si les échecs sont arrivés dans un ordre différent.
Comparer les règles de décision
all: toutes les valeurs sont nécessaires ; rejet au premier rejet.allSettled: toutes les issues sont nécessaires ; attente de tous les objets de résultat.race: le premier acquittement décide, y compris en cas d’échec.any: la première promesse tenue décide ; rejet uniquement si toutes sont rejetées.
Sans entrée, all et allSettled sont tenues avec []. any est rejetée avec une AggregateError, car aucune réussite n’est possible. race reste en attente, car rien ne peut l’acquitter.
Les quatre sont pris en charge dans les navigateurs actuels et dans Node.js. all et race sont entrées dans ECMAScript en 2015, allSettled en 2020 et any en 2021.
Comparer une paire d’issues
Supposons que les deux opérations commencent maintenant : la première entrée échoue après environ 100 ms, tandis que la seconde est tenue avec « usable » après environ 300 ms. Appliquer chaque combinateur à cette même paire donne des résultats différents :
- All est rejetée vers 100 ms avec l’erreur de la première entrée, car chaque entrée doit réussir.
- AllSettled est tenue vers 300 ms avec un objet de résultat rejeté suivi d’un objet de résultat tenu, en conservant l’ordre des entrées.
- Race est rejetée vers 100 ms, car c’est le premier acquittement.
- Any est tenue vers 300 ms avec « usable », car l’échec précédent ne peut pas fournir de réussite.
All et race sont toutes deux rejetées dans ce cas, mais elles utilisent des règles différentes. Si la première entrée rapide réussissait et que la seconde échouait plus tard, race conserverait sa réussite initiale tandis que all serait rejetée lors de l’échec ultérieur. Choisis en fonction de l’issue nécessaire, et non du délai observé dans un exemple.
Points clés
- allSettled rapporte chaque réussite et chaque échec dans l’ordre des entrées.
- race suit le premier acquittement, y compris un rejet.
- any accepte la première réussite et n’est rejetée que si toutes les entrées sont rejetées.
- Un délai d’expiration avec race laisse les opérations perdantes s’exécuter.
- Une entrée vide pour race reste en attente ; une entrée vide pour any provoque un rejet.
- Choisis le combinateur selon l’issue dont le code appelant a réellement besoin.
JavaScript asynchrone : exemples
1Prévois ce qui s’exécute maintenant et plus tard
14 étapes
- 1IntroductionDistinguer le début de la fin
Commence par distinguer les appels immédiats des fonctions de rappel enregistrées. Une minuterie peut attendre sans occuper le thread principal, mais sa fonction de rappel a toujours besoin de ce thread. Suis ce qui s’exécute pendant le script sur la pile d’appels avant de passer aux opérations en attente, une tâche ou une microtâche, car leur ordre détermine ce que tu vois en premier.
- 2ExempleEnregistrer une fonction de rappel
Lis ce script avant de prévoir ce qu’il affiche :
console.log("request"); setTimeout(() => console.log("ready"), 200); console.log("continue");Le premier affichage s’exécute maintenant. L’appel à la minuterie enregistre une fonction et se termine : le script atteint donc immédiatement le dernier affichage. L’affichage de la fonction de rappel appartient à une tâche ultérieure ; créer cette fonction ne l’a pas appelée.
- 3Point cléL’attente n’occupe pas le thread
La console affiche d’abord « request », « continue », puis « ready » après environ 200 ms. L’hôte gère l’attente, mais la fonction de rappel a toujours besoin du thread principal. Une longue boucle peut la retarder même après que la minuterie est prête.
- 4DéfiLa minuterie peut-elle interrompre le calcul ?
Un script enregistre une minuterie avec un délai nul, puis effectue un long calcul synchrone. La minuterie arrive à son terme avant la fin du calcul. Sa fonction de rappel peut-elle s’exécuter pendant ce calcul ?
---reveal---
Non. Le calcul conserve le thread principal jusqu’à la fin du travail en cours. Lorsque la minuterie arrive à son terme, sa fonction de rappel est placée dans une file pour plus tard ; cela ne lui donne ni un thread d’exécution distinct ni l’autorisation d’interrompre le calcul.
- 5ExplicationDistinguer deux types d’opérations différées
Une fonction de rappel de minuterie s’exécute comme une tâche. Un gestionnaire de promesse s’exécute comme une microtâche. La pile d’appels enregistre les appels en cours d’exécution.
Après le travail en cours, un point de contrôle des microtâches permet de vider la file de microtâches avant qu’une autre tâche puisse commencer. Toutes les microtâches ajoutées pendant ce traitement rejoignent cette même file. Pour suivre un script, conserve des listes distinctes pour les résultats immédiats, les microtâches et les tâches de minuterie.
- 6ExempleRepérer les files
Ajoute maintenant un gestionnaire de promesse :
console.log("request"); setTimeout(() => console.log("timer"), 0); Promise.resolve().then(() => console.log("job")); console.log("continue");La console affiche immédiatement « request », « continue ». Le gestionnaire de promesse attend comme microtâche ; la fonction de rappel de la minuterie est une tâche. Termine le script en cours avant d’exécuter l’une ou l’autre des fonctions enregistrées.
- 7ExempleVider la file avant la tâche suivante
Une fois le script terminé, la microtâche affiche « job ». Ce n’est qu’une fois la file de microtâches vide que la tâche de minuterie prête peut afficher « timer ».
La console affiche au total « request », « continue », « job », « timer ». Une promesse tenue n’appelle pas son gestionnaire de façon synchrone et une minuterie dont le délai est nul ne passe pas avant les microtâches. Le navigateur peut effectuer le rendu lorsqu’une occasion se présente après ce traitement.
- 8DéfiPlacer une opération nouvellement ajoutée à la file
Dans quel ordre les résultats s’affichent-ils ?
Promise.resolve().then(() => console.log("a")) .then(() => console.log("c")); queueMicrotask(() => console.log("b")); console.log("now");---reveal---
« now », « a », « b », « c ». Le script place a dans la file avant b. Le gestionnaire suivant de la chaîne n’est prêt que lorsque le gestionnaire de a se termine : c rejoint donc la file derrière l’opération b qui y était déjà.
- 9ExplicationChoisir une minuterie et conserver son identifiant
Utilise
setTimeoutpour un appel planifié etsetIntervalpour des appels répétés. Les deux renvoient un identifiant que tu peux conserver.const hint = setTimeout(() => console.log("Hint"), 800); clearTimeout(hint);Cela annule la fonction de rappel avant son exécution. Pour une minuterie périodique, passe son identifiant à
clearIntervallorsque la répétition doit prendre fin. Passe une fonction à planifier ; l’appeler dans la liste des arguments l’exécute immédiatement. - 10ConseilPrévoir les fonctions de rappel retardées
Les délais sont exprimés en millisecondes et indiquent une attente minimale. Du code qui occupe le thread, les onglets en arrière-plan et les limites du navigateur peuvent retarder davantage les fonctions de rappel. Planifie une temporisation imbriquée après l’exécution en cours ; attends aussi la fin de son opération asynchrone lorsque les exécutions ne doivent pas se chevaucher.
- 11ExempleUtiliser la bonne liaison de boucle
Avec
let, chaque itération donne à sa fonction de rappel un indice distinct :for (let n = 0; n < 3; n++) { setTimeout(() => console.log(n), 50); }Les fonctions de rappel affichent « 0 », « 1 », « 2 ». Remplacer let par var leur fait partager une seule variable, dont la valeur est 3 après la boucle. Les trois affichent alors « 3 ». La fonction de rappel lit la liaison lorsqu’elle s’exécute plus tard.
- 12DéfiRedémarrer une minuterie d’attente sans activité
Un gestionnaire utilise un délai d’anti-rebond de 300 ms. Des événements de saisie se produisent à 0 ms, 80 ms et 170 ms, puis il n’y a plus d’événement. Vers quel moment son unique action s’exécute-t-elle ?
---reveal---
Environ 470 ms après le premier événement : 300 ms après le dernier événement, à 170 ms. Chaque événement annule la temporisation précédente en attente et en planifie une nouvelle. Les actions planifiées auparavant ne s’exécutent pas non plus, et un thread occupé pourrait retarder davantage l’action finale.
- 13DéfiRelier les règles temporelles
Une minuterie est prête, mais chaque microtâche continue d’ajouter une autre microtâche à la file. Pourquoi ni la fonction de rappel de la minuterie ni la prochaine mise à jour de l’écran ne peuvent-elles avancer ?
---reveal---
Le traitement de la file de microtâches ne se termine jamais : la boucle d’événements ne peut donc pas passer à la tâche suivante ni à une occasion de rendu. Le délai écoulé de la minuterie ne modifie pas cet ordre. Planifier des microtâches sans fin peut bloquer la progression tout comme une longue boucle.
- 14RécapitulatifUn suivi d’exécution réutilisable
Suis d’abord le code immédiat, traite ensuite toutes les microtâches, puis passe aux tâches prêtes. Conserve les identifiants des minuteries pour pouvoir les annuler. Les délais sont approximatifs ; let conserve les valeurs des itérations, tandis que l’anti-rebond redémarre un délai d’attente sans activité.
2Transmets un résultat par une fonction de rappel
9 étapes
Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.
- 1IntroductionFournir une action suivante au code appelant
- 2ExempleProduire la valeur différée
- 3LienL’API contrôle le moment de l’exécution
- 4DéfiQuelle fonction se termine ?
- 5ExempleSignaler l’une des deux issues
- 6ConseilS’arrêter avant de lire un résultat absent après un échec
- 7DéfiRepérer le périmètre actif d’interception des erreurs
- 8DéfiRelier les résultats et les échecs entre les étapes
- 9RécapitulatifSuivre chaque issue jusqu’au code qui l’utilise
3Construis et relie les issues des promesses
12 étapes
Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.
- 1IntroductionUn objet pour une issue ultérieure
- 2ExempleConstruire une pause réutilisable
- 3Point cléDistinguer l’état de la résolution
- 4DéfiGérer correctement un échec tardif
- 5ExplicationAjouter le bon gestionnaire
- 6ExempleDeux abonnements conservent une même valeur
- 7DéfiLire le résultat d’un nettoyage
- 8ExempleConstruire le premier maillon
- 9ExempleFaire attendre le maillon suivant
- 10DéfiRéparer une opération détachée
- 11DéfiRelier le nettoyage à une chaîne d’étapes dépendantes
- 12RécapitulatifVérifier le chemin renvoyé
4Réécris une séquence avec async et await
12 étapes
Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.
- 1IntroductionGarder les promesses, changer l’ordre de lecture
- 2ExempleUn appel asynchrone commence immédiatement
- 3DéfiLire la valeur renvoyée
- 4ExempleRelier une fonction de rappel à une promesse
- 5ExempleGarder les valeurs intermédiaires dans la portée
- 6DéfiAsync suffit-il à adapter le résultat ?
- 7Point cléTester la valeur, pas sa promesse
- 8ExempleforEach n’attend pas les fonctions de rappel
- 9ConseilChoisir une méthode de parcours
- 10DéfiCorriger un achèvement prématuré
- 11DéfiRelier l’adaptation et la boucle
- 12RécapitulatifExaminer les deux côtés d’un appel asynchrone
5Garde l’échec, la récupération et le nettoyage reliés
13 étapes
Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.
- 1IntroductionDécider de la réponse à un échec
- 2ExempleObserver une récupération avec catch
- 3Point cléConsigner l’erreur détermine aussi un retour
- 4DéfiConserver l’issue en échec
- 5ExempleAttendre là où catch peut voir l’échec
- 6ConseilRenvoyer après le traitement local
- 7DéfiChoisir la solution de repli locale
- 8LienTerminer le chemin d’erreur de l’action
- 9ExempleConserver le contexte et la cause initiale
- 10ExempleRelancer un appel, avec une limite
- 11DéfiS’arrêter après une réussite
- 12DéfiRelier la récupération au nettoyage
- 13RécapitulatifDonner une destination à chaque issue
6Choisis ce qu’un groupe de tâches doit fournir
11 étapes
Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.
- 1IntroductionCommencer par la question des dépendances
- 2ExempleLancer les deux opérations ensemble
- 3Point cléUn échec n’annule pas le groupe
- 4DéfiRespecter une dépendance et faire se chevaucher le reste
- 5ExplicationConserver chaque issue lorsque des résultats partiels restent utiles
- 6ExempleDistinguer premier acquittement et première réussite
- 7DéfiChoisir pour une page aux résultats partiels
- 8ConseilUn délai d’expiration arrête l’attente, pas l’opération
- 9DéfiQu’est-ce qui peut acquitter un groupe vide ?
- 10DéfiRelier le moment du lancement et la gestion des échecs
- 11RécapitulatifChoisir la règle relative aux issues
JavaScript asynchrone : QCM
Série 1 : Suis le code des appels aux fonctions de rappel10 Questions Pratiques
Question 1 sur 10
Afficher la réponseMasquer la réponse
Question 2 sur 10
Dans quel ordre les résultats s’affichent-ils ?
console.log("open");
setTimeout(() => console.log("tick"), 0);
Promise.resolve().then(() => console.log("job"));
console.log("close");
Afficher la réponseMasquer la réponse
Question 3 sur 10
const reminder = setTimeout(showHint, 900);. Quelle instruction l’annule avant son exécution ?Afficher la réponseMasquer la réponse
Question 4 sur 10
Qu’affiche cette boucle après l’exécution de ses fonctions de rappel ?
for (var index = 2; index < 5; index++) {
setTimeout(() => console.log(index), 0);
}
Afficher la réponseMasquer la réponse
Question 5 sur 10
Combien d’appels à la fonction de rappel sont terminés lorsque ce code atteint l’affichage ?
let calls = 0;
[3, 6, 9].map(n => {
calls++;
return n + 1;
});
console.log(calls);
Afficher la réponseMasquer la réponse
Question 6 sur 10
Que contient result lorsque l’affichage s’exécute ?
function make() {
setTimeout(() => { return "badge"; }, 0);
}
const result = make();
console.log(result);
Afficher la réponseMasquer la réponse
Question 7 sur 10
Une fonction de rappel avec erreur en premier reçoit un objet Error et aucun résultat en cas d’échec. Qu’est-ce qui empêche le code de réussite situé après cette vérification de lire un item absent ?
if (err) {
console.log(err.message);
// Add one statement here.
}
console.log(item.name);
Afficher la réponseMasquer la réponse
Question 8 sur 10
Dans quel ordre les résultats s’affichent-ils ?
Promise.resolve().then(() => {
console.log("oak");
queueMicrotask(() => console.log("elm"));
}).then(() => console.log("ash"));
queueMicrotask(() => console.log("fir"));
console.log("pine");
Afficher la réponseMasquer la réponse
Question 9 sur 10
Une opération qui produit un seul résultat utilise ce corps de minuterie. Que se passe-t-il lorsque id vaut 0 et quelle modification corrige le problème ?
if (id <= 0) callback(new Error("Invalid"));
callback(null, { id });
Afficher la réponseMasquer la réponse
Question 10 sur 10
Dans quel ordre les résultats s’affichent-ils ?
console.log("outside");
const p = new Promise(resolve => {
console.log("executor");
resolve("value");
});
p.then(console.log);
console.log("registered");
Afficher la réponseMasquer la réponse
Série 2 : Transmets des valeurs avec les promesses et await10 Questions Pratiques
Enregistre cette unité pour voir chaque réponse et t'entraîner sur chaque série.
Question 1 sur 10
Quelle valeur cette promesse fournit-elle à son gestionnaire de réussite ?
const p = new Promise((resolve, reject) => {
resolve(14);
reject(new Error("Changed"));
resolve(28);
});
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 2 sur 10
Qu’affiche la console ?
const p = Promise.resolve(7);
p.then(n => n + 4);
p.then(n => console.log(n));
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 3 sur 10
Qu’affiche la console ?
Promise.resolve(6)
.then(n => n - 2)
.then(n => n * 5)
.then(console.log);
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 4 sur 10
La fonction wait(ms) renvoie une promesse tenue après environ ms millisecondes. Que reçoit console.log et quand ?
wait(120)
.then(() => { wait(480).then(() => "ready"); })
.then(console.log);
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 5 sur 10
Quel type de valeur l’appel de cette fonction renvoie-t-il ?
async function count() { return 9; }
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 6 sur 10
Dans quel ordre les résultats s’affichent-ils ?
async function show() {
console.log("inside");
await 0;
console.log("after await");
}
show();
console.log("caller");
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 7 sur 10
done(err, value) une fois. Que doit faire la fonction enveloppante renvoyant une promesse lorsque err est un objet Error ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 8 sur 10
Qu’affiche la console ?
async function permitted() { return false; }
if (permitted()) console.log("enter");
else console.log("stop");
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 9 sur 10
record(id) renvoie une promesse et peut être rejetée. Comment cette fonction doit-elle traiter les identifiants dans l’ordre, s’arrêter en cas d’échec et permettre au code appelant d’intercepter cet échec ?
async function saveAll(ids) {
ids.forEach(async id => { await record(id); });
}
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 10 sur 10
Quelle valeur le dernier gestionnaire affiche-t-il ?
Promise.resolve(11)
.finally(() => 40)
.then(n => n + 2)
.finally(() => Promise.resolve(90))
.then(console.log);
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Série 3 : Gère les échecs et combine des tâches indépendantes10 Questions Pratiques
Enregistre cette unité pour voir chaque réponse et t'entraîner sur chaque série.
Question 1 sur 10
"default" après avoir reçu un rejet. Que reçoit le gestionnaire then suivant ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 2 sur 10
Qu’affiche la console ?
Promise.resolve("start")
.then(() => { throw new Error("later"); },
() => console.log("same call"))
.catch(err => console.log(err.message));
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 3 sur 10
Dans quel ordre les résultats s’affichent-ils ?
async function run() {
try {
await Promise.reject(new Error("busy"));
console.log("saved");
} catch (err) {
console.log(err.message);
} finally {
console.log("reset");
}
}
run();
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 4 sur 10
Qu’affiche le code appelant ?
async function read() {
try {
return Promise.reject(new Error("missing"));
} catch {
return "local";
}
}
read().then(console.log).catch(err => console.log(err.message));
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 5 sur 10
new Error("Report failed", { cause: original }). Quelle propriété garde l’objet Error initial disponible ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 6 sur 10
"ok" à l’appel 3. Que se passe-t-il ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 7 sur 10
await Promise.all(...) et réussissent. Combien de temps dure environ l’attente combinée ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 8 sur 10
Promise.all([A, B, C]) ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 9 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 10 sur 10
"backup" après environ 400 ms. Quelle différence y a-t-il entre race et any ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Série 4 : Débogue des déroulements asynchrones complets10 Questions Pratiques
Enregistre cette unité pour voir chaque réponse et t'entraîner sur chaque série.
Question 1 sur 10
Qu’est-ce qui s’exécute en premier lors de l’évaluation de cette instruction ?
setTimeout(showMessage(), 500);
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 2 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 3 sur 10
inner est encore en attente. Que se passe-t-il ici si inner est rejetée plus tard ?
const outer = new Promise((resolve, reject) => {
resolve(inner);
reject(new Error("replacement"));
});
outer.catch(err => console.log(err.message));
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 4 sur 10
Dans quel ordre les résultats s’affichent-ils ?
async function step() {
console.log("a");
await null;
console.log("b");
queueMicrotask(() => console.log("c"));
}
setTimeout(() => console.log("d"), 0);
step();
Promise.resolve().then(() => console.log("e"));
console.log("f");
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 5 sur 10
Qu’affiche le dernier gestionnaire ?
Promise.reject(new Error("first"))
.catch(() => 4)
.then(n => { throw new Error("second " + n); })
.finally(() => 9)
.catch(err => console.log(err.message));
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 6 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 7 sur 10
Pourquoi cette fonction de nouvelle tentative ne réessaie-t-elle pas une tâche rejetée de façon asynchrone ?
async function retry(task) {
for (let n = 0; n < 3; n++) {
try { return task(); }
catch (err) { if (n === 2) throw err; }
}
}
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 8 sur 10
Promise.race([]) lorsqu’aucune entrée ne sera jamais ajoutée ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 9 sur 10
Promise.any([first, second]) à catch ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 10 sur 10
Qu’affiche le code appelant ?
async function value() {
try {
return await Promise.reject(new Error("lost"));
} catch {
return "fallback";
} finally {
console.log("cleanup");
}
}
value().then(console.log);
console.log("caller");
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Tu bloques sur ton propre exercice ? Aide aux Devoirs
Mots-Clés
A
Abonnement à une promesse
Acquittée
Adoption d’une promesse
AggregateError
Anti-rebond
Await au niveau supérieur
B
Bloc finally
Bloc try
Blocage
Boucle d’événements
C
Cause de l’erreur
Chaîne de promesses
Chaîne sans imbrication
Code synchrone
Concurrence
Continuation
E
En attente
Enfer des fonctions de rappel
Environnement hôte
Erreur passée sous silence
Exécution jusqu’à achèvement
É
Échec immédiat
Événement d’erreur
F
Famine due aux microtâches
Fonction asynchrone
Fonction de rappel
Fonction de rappel asynchrone
Fonction de rappel avec erreur en premier
Fonction de rappel de finally
Fonction de rappel synchrone
map double chaque élément avant que le tableau de résultats complet soit renvoyé.Fonction enveloppante renvoyant une promesse
Fonction exécutrice
G
Gestionnaire de rejet
Gestionnaire de réussite
I
Identifiant de minuterie
L
Levée d’exception
M
Microtâche
Minuterie périodique
N
Nouvelle tentative
O
Objet Error
Opérateur await
Opération asynchrone
Opération dépendante
Opérations indépendantes
Ordre des entrées
Oubli de await
Oubli de return
P
Périmètre d’interception des erreurs
Pile d’appels
Promesse
Promesse de fonction de rappel ignorée
Promessification
Promise.all
Promise.allSettled
Promise.any
Promise.race
Propagation des rejets
R
Récupération après erreur
Rejet non géré
Rejetée
Relancer une exception
Résolue
Résultat intermédiaire
Retour anticipé
S
Solution de repli
T
Tâche
Temporisation
Temporisation imbriquée
Tenue
Thread principal
Traitement séquentiel
V
Valeur vraie
À propos de cette unité
Crée ton propre Matériel d'Étude
Transforme tes notes, diapos et documents en Guides d'Étude, Questions Pratiques, Flashcards et 6 autres outils.