From e26d24940b2234a1a5e63b19d19d298bf36354e2 Mon Sep 17 00:00:00 2001 From: julieng Date: Sun, 14 Nov 2021 14:30:32 +0100 Subject: move *.html to *.md --- .../javascript/asynchronous/async_await/index.html | 546 ------------------- .../javascript/asynchronous/async_await/index.md | 546 +++++++++++++++++++ .../choosing_the_right_approach/index.html | 553 ------------------- .../choosing_the_right_approach/index.md | 553 +++++++++++++++++++ .../javascript/asynchronous/concepts/index.html | 165 ------ .../javascript/asynchronous/concepts/index.md | 165 ++++++ files/fr/learn/javascript/asynchronous/index.html | 62 --- files/fr/learn/javascript/asynchronous/index.md | 62 +++ .../javascript/asynchronous/introducing/index.html | 286 ---------- .../javascript/asynchronous/introducing/index.md | 286 ++++++++++ .../javascript/asynchronous/promises/index.html | 576 -------------------- .../javascript/asynchronous/promises/index.md | 576 ++++++++++++++++++++ .../asynchronous/timeouts_and_intervals/index.html | 599 --------------------- .../asynchronous/timeouts_and_intervals/index.md | 599 +++++++++++++++++++++ 14 files changed, 2787 insertions(+), 2787 deletions(-) delete mode 100644 files/fr/learn/javascript/asynchronous/async_await/index.html create mode 100644 files/fr/learn/javascript/asynchronous/async_await/index.md delete mode 100644 files/fr/learn/javascript/asynchronous/choosing_the_right_approach/index.html create mode 100644 files/fr/learn/javascript/asynchronous/choosing_the_right_approach/index.md delete mode 100644 files/fr/learn/javascript/asynchronous/concepts/index.html create mode 100644 files/fr/learn/javascript/asynchronous/concepts/index.md delete mode 100644 files/fr/learn/javascript/asynchronous/index.html create mode 100644 files/fr/learn/javascript/asynchronous/index.md delete mode 100644 files/fr/learn/javascript/asynchronous/introducing/index.html create mode 100644 files/fr/learn/javascript/asynchronous/introducing/index.md delete mode 100644 files/fr/learn/javascript/asynchronous/promises/index.html create mode 100644 files/fr/learn/javascript/asynchronous/promises/index.md delete mode 100644 files/fr/learn/javascript/asynchronous/timeouts_and_intervals/index.html create mode 100644 files/fr/learn/javascript/asynchronous/timeouts_and_intervals/index.md (limited to 'files/fr/learn/javascript/asynchronous') diff --git a/files/fr/learn/javascript/asynchronous/async_await/index.html b/files/fr/learn/javascript/asynchronous/async_await/index.html deleted file mode 100644 index 5955c0f27f..0000000000 --- a/files/fr/learn/javascript/asynchronous/async_await/index.html +++ /dev/null @@ -1,546 +0,0 @@ ---- -title: Faciliter la programmation asynchrone avec async et await -slug: Learn/JavaScript/Asynchronous/Async_await -tags: - - Beginner - - CodingScripting - - Guide - - JavaScript - - Learn - - Promises - - async - - asynchronous - - await -translation_of: Learn/JavaScript/Asynchronous/Async_await ---- -
{{LearnSidebar}}
- -
{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Promises", "Learn/JavaScript/Asynchronous/Choosing_the_right_approach", "Learn/JavaScript/Asynchronous")}}
- -

Plus récemment, les fonctions async et le mot-clé await ont été ajoutés au langage JavaScript avec l'édition intitulée ECMAScript 2017. Ces fonctionnalités agissent essentiellement comme du sucre syntaxique sur les promesses, rendant le code asynchrone plus facile à écrire et à lire par la suite. Elles font en sorte que le code asynchrone ressemble davantage au code synchrone de la vieille école, et elles valent donc la peine d'être apprises. Cet article fournit les informations à connaître.

- - - - - - - - - - - - -
Prérequis :Connaissances informatiques de base, compréhension raisonnable des principes fondamentaux de JavaScript, compréhension du code asynchrone en général et des promesses.
Objectif :Comprendre async/await et comment les utiliser.
- -

Les bases de async/await

- -

L'utilisation async/await dans votre code comporte deux parties.

- -

Le mot-clé async

- -

Tout d'abord, nous avons le mot-clé async, que vous mettez devant une déclaration de fonction pour la transformer en une fonction asynchrone. Une fonction asynchrone est une fonction qui saura réagir à une éventuelle utilisation du mot-clé await pour invoquer du code asynchrone.

- -

Essayez de taper les lignes suivantes dans la console JS de votre navigateur :

- -
function hello() { return "Bonjour" };
-hello();
- -

La fonction renvoie « Bonjour » — rien de spécial, n'est-ce pas ?

- -

Mais que se passe-t-il si nous transformons cette fonction en une fonction asynchrone ? Essayez ce qui suit :

- -
async function hello() { return "Bonjour" };
-hello();
- -

Ah. L'invocation de la fonction renvoie maintenant une promesse. C'est l'une des caractéristiques des fonctions asynchrones - leurs valeurs de retour sont nécessairement converties en promesses.

- -

Vous pouvez également créer une expression de fonction asynchrone, comme suit :

- -
let hello = async function() { return "Bonjour" };
-hello();
- -

Et vous pouvez utiliser les fonctions fléchées :

- -
let hello = async () => { return "Bonjour" };
- -

Elles font toutes à peu près la même chose.

- -

Pour consommer réellement la valeur renvoyée lorsque la promesse se réalise, puisqu'elle renvoie une promesse, nous pourrions utiliser un bloc .then() :

- -
hello().then((value) => console.log(value));
- -

ou même simplement un raccourci tel que

- -
hello().then(console.log);
- -

Comme nous l'avons vu dans l'article précédent.

- -

Ainsi, le mot-clé async est ajouté aux fonctions pour leur indiquer de retourner une promesse plutôt que de retourner directement la valeur.

- -

Le mot-clé await

- -

L'avantage d'une fonction asynchrone ne devient apparent que lorsque vous la combinez avec le mot-clé await. await ne fonctionne qu'à l'intérieur de fonctions asynchrones dans du code JavaScript ordinaire, mais il peut être utilisé seul avec des modules JavaScript.

- -

await peut être placé devant toute fonction asynchrone basée sur une promesse pour mettre en pause votre code sur cette ligne jusqu'à ce que la promesse se réalise, puis retourner la valeur résultante.

- -

Vous pouvez utiliser await lors de l'appel de toute fonction qui renvoie une promesse, y compris les fonctions de l'API web.

- -

Voici un exemple trivial :

- -
async function hello() {
-  return salutation = await Promise.resolve("Bonjour");
-};
-
-hello().then(console.log);
- -

Bien sûr, l'exemple ci-dessus n'est pas très utile, même s'il sert à illustrer la syntaxe. Passons maintenant à un exemple réel.

- -

Réécriture du code des promesses avec async/await

- -

Reprenons un exemple simple de récupération que nous avons vu dans l'article précédent :

- -
fetch('coffee.jpg')
-.then(response => {
-  if (!response.ok) {
-    throw new Error(`Erreur HTTP ! statut : ${response.status}`);
-  }
-  return response.blob();
-})
-.then(myBlob => {
-  let objectURL = URL.createObjectURL(myBlob);
-  let image = document.createElement('img');
-  image.src = objectURL;
-  document.body.appendChild(image);
-})
-.catch(e => {
-  console.log('Il y a eu un problème avec votre opération de récupération : ' + e.message);
-});
- -

À ce stade, vous devriez avoir une compréhension raisonnable des promesses et de leur fonctionnement, mais convertissons le tout en utilisant async/await pour voir à quel point cela simplifie les choses :

- -
async function myFetch() {
-  let response = await fetch('coffee.jpg');
-
-  if (!response.ok) {
-    throw new Error(`Erreur HTTP ! statut : ${response.status}`);
-  }
-
-  let myBlob = await response.blob();
-
-  let objectURL = URL.createObjectURL(myBlob);
-  let image = document.createElement('img');
-  image.src = objectURL;
-  document.body.appendChild(image);
-}
-
-myFetch()
-.catch(e => {
-  console.log('Il y a eu un problème avec votre opération de récupération : ' + e.message);
-});
- -

Cela rend le code beaucoup plus simple et plus facile à comprendre — plus de blocs .then() partout !

- -

Étant donné qu'un mot-clé async transforme une fonction en promesse, vous pourriez remanier votre code pour utiliser une approche hybride de promesses et de await, en faisant sortir la seconde moitié de la fonction dans un nouveau bloc pour la rendre plus flexible :

- -
async function myFetch() {
-  let response = await fetch('coffee.jpg');
-  if (!response.ok) {
-    throw new Error(`Erreur HTTP ! statut : ${response.status}`);
-  }
-  return await response.blob();
-
-}
-
-myFetch().then((blob) => {
-  let objectURL = URL.createObjectURL(blob);
-  let image = document.createElement('img');
-  image.src = objectURL;
-  document.body.appendChild(image);
-}).catch(e => console.log(e));
- -

Vous pouvez essayer de taper vous-même l'exemple, ou d'exécuter notre exemple en direct (voir aussi le code source).

- -

Mais comment est-ce que cela fonctionne ?

- -

Vous remarquerez que nous avons enveloppé le code à l'intérieur d'une fonction, et que nous avons inclus le mot-clé async avant le mot-clé function. C'est nécessaire - vous devez créer une fonction async pour définir un bloc de code dans lequel vous exécuterez votre code async ; comme nous l'avons dit précédemment, await ne fonctionne qu'à l'intérieur de fonctions async.

- -

À l'intérieur de la définition de la fonction myFetch(), vous pouvez voir que le code ressemble beaucoup à la version précédente de la promesse, mais il y a quelques différences. Au lieu de devoir enchaîner un bloc .then() à la fin de chaque méthode basée sur une promesse, il suffit d'ajouter un mot-clé await avant l'appel de la méthode, puis d'affecter le résultat à une variable. Le mot-clé await fait en sorte que le moteur d'exécution JavaScript mette votre code en pause sur cette ligne, ne permettant pas à d'autres codes de s'exécuter entre-temps jusqu'à ce que l'appel de fonction asynchrone ait retourné son résultat - très utile si le code suivant dépend de ce résultat !

- -

Une fois que c'est terminé, votre code continue à s'exécuter à partir de la ligne suivante. Par exemple :

- -
let response = await fetch('coffee.jpg');
- -

La réponse retournée par la promesse fetch() remplie est affectée à la variable response lorsque cette réponse devient disponible, et le parseur fait une pause sur cette ligne jusqu'à ce que cela se produise. Une fois que la réponse est disponible, le parseur passe à la ligne suivante, qui crée un Blob à partir de celle-ci. Cette ligne invoque également une méthode async basée sur les promesses, nous utilisons donc await ici aussi. Lorsque le résultat de l'opération revient, nous le retournons hors de la fonction myFetch().

- -

Cela signifie que lorsque nous appelons la fonction myFetch(), elle retourne une promesse, de sorte que nous pouvons enchaîner un .then() à la fin de celle-ci à l'intérieur duquel nous gérons l'affichage du blob à l'écran.

- -

Vous vous dites probablement déjà « c'est vraiment cool ! », et vous avez raison — moins de blocs .then() pour envelopper le code, et cela ressemble surtout à du code synchrone, donc c'est vraiment intuitif.

- -

Ajout de la gestion des erreurs

- -

Si vous voulez ajouter la gestion des erreurs, vous avez plusieurs options.

- -

Vous pouvez utiliser une structure synchrone try...catch avec async/await. Cet exemple développe la première version du code que nous avons montré ci-dessus :

- -
async function myFetch() {
-  try {
-    let response = await fetch('coffee.jpg');
-
-    if (!response.ok) {
-      throw new Error(`Erreur HTTP ! statut : ${response.status}`);
-    }
-    let myBlob = await response.blob();
-    let objectURL = URL.createObjectURL(myBlob);
-    let image = document.createElement('img');
-    image.src = objectURL;
-    document.body.appendChild(image);
-
-  } catch(e) {
-    console.log(e);
-  }
-}
-
-myFetch();
- -

Le bloc catch() {} reçoit un objet d'erreur, que nous avons appelé e ; nous pouvons maintenant l'enregistrer dans la console, et il nous donnera un message d'erreur détaillé montrant où dans le code l'erreur a été lancée.

- -

Si vous vouliez utiliser la deuxième version (remaniée) du code que nous avons montré ci-dessus, il serait préférable de continuer l'approche hybride et d'enchaîner un bloc .catch() à la fin de l'appel .then(), comme ceci :

- -
async function myFetch() {
-  let response = await fetch('coffee.jpg');
-  if (!response.ok) {
-    throw new Error(`Erreur HTTP ! statut : ${response.status}`);
-  }
-  return await response.blob();
-
-}
-
-myFetch().then((blob) => {
-  let objectURL = URL.createObjectURL(blob);
-  let image = document.createElement('img');
-  image.src = objectURL;
-  document.body.appendChild(image);
-})
-.catch((e) =>
-  console.log(e)
-);
- -

En effet, le bloc .catch() attrapera les erreurs survenant à la fois dans l'appel de fonction asynchrone et dans la chaîne de promesses. Si vous utilisiez le bloc try/catch ici, vous pourriez toujours obtenir des erreurs non gérées dans la fonction myFetch() lorsqu'elle est appelée.

- -

Vous pouvez trouver ces deux exemples sur GitHub :

- - - -

En attente d'un Promise.all()

- -

async/await est construit au-dessus de Promises, il est donc compatible avec toutes les fonctionnalités offertes par les promesses. Cela inclut Promise.all() — vous pouvez tout à fait attendre un appel Promise.all() pour obtenir tous les résultats retournés dans une variable d'une manière qui ressemble à du simple code synchrone. Encore une fois, revenons à un exemple que nous avons vu dans notre article précédent. Gardez-le ouvert dans un onglet séparé afin de pouvoir le comparer avec la nouvelle version présentée ci-dessous.

- -

En convertissant cela en async/await (voir la démo live et le code source), cela ressemble maintenant à ceci :

- -
async function fetchAndDecode(url, type) {
-  let response = await fetch(url);
-
-  let content;
-
-  if (!response.ok) {
-    throw new Error(`Erreur HTTP ! statut : ${response.status}`);
-  } else {
-    if(type === 'blob') {
-      content = await response.blob();
-    } else if(type === 'text') {
-      content = await response.text();
-    }
-  }
-
-  return content;
-}
-
-async function displayContent() {
-  let coffee = fetchAndDecode('coffee.jpg', 'blob');
-  let tea = fetchAndDecode('tea.jpg', 'blob');
-  let description = fetchAndDecode('description.txt', 'text');
-
-  let values = await Promise.all([coffee, tea, description]);
-
-  let objectURL1 = URL.createObjectURL(values[0]);
-  let objectURL2 = URL.createObjectURL(values[1]);
-  let descText = values[2];
-
-  let image1 = document.createElement('img');
-  let image2 = document.createElement('img');
-  image1.src = objectURL1;
-  image2.src = objectURL2;
-  document.body.appendChild(image1);
-  document.body.appendChild(image2);
-
-  let para = document.createElement('p');
-  para.textContent = descText;
-  document.body.appendChild(para);
-}
-
-displayContent()
-.catch((e) =>
-  console.log(e)
-);
- -

Vous verrez que la fonction fetchAndDecode() a été convertie facilement en fonction asynchrone avec seulement quelques modifications. Voir la ligne Promise.all() :

- -
let values = await Promise.all([coffee, tea, description]);
- -

En utilisant await ici, nous sommes en mesure d'obtenir tous les résultats des trois promesses retournées dans le tableau values, quand ils sont tous disponibles, d'une manière qui ressemble beaucoup à du code synchrone. Nous avons dû envelopper tout le code dans une nouvelle fonction asynchrone, displayContent(), et nous n'avons pas réduit le code de beaucoup de lignes, mais être capable de déplacer la majeure partie du code hors du bloc .then() fournit une simplification agréable et utile, nous laissant avec un programme beaucoup plus lisible.

- -

Pour la gestion des erreurs, nous avons inclus un bloc .catch() sur notre appel displayContent() ; cela permettra de gérer les erreurs survenant dans les deux fonctions.

- -
-

Note : Il est également possible d'utiliser un bloc finally au sein d'une fonction asynchrone, à la place d'un bloc asynchrone .finally(), pour montrer un état final sur le déroulement de l'opération — vous pouvez voir cela en action dans notre exemple en direct (voir aussi le code source).

-
- -

Gérer les ralentissements potentiellement causés par async/await

- -

Il est vraiment utile de connaître async/await, mais il y a quelques inconvénients à prendre en compte.

- -

async/await donne à votre code une apparence synchrone, et d'une certaine manière, il le fait se comporter de manière plus synchrone. Le mot-clé await bloque l'exécution de tout le code qui le suit jusqu'à ce que la promesse se réalise, exactement comme dans le cas d'une opération synchrone. Il permet certes aux autres tâches de continuer à s'exécuter entre-temps, mais le code attendu est bloqué.

- -
async function makeResult(items) {
-  let newArr = [];
-  for(let i=0; i < items.length; i++) {
-    newArr.push('word_'+i);
-  }
-  return newArr;
-}
-
-async function getResult() {
-  let result = await makeResult(items); // Bloqué sur cette ligne
-  useThatResult(result); // Pas exécuté tant que makeResult() n'a pas fini
-}
- -

Cela signifie que votre code pourrait être ralenti par un nombre important de promesses attendues se produisant directement les unes après les autres. Chaque await attendra que la précédente se termine, alors qu'en réalité ce que vous voulez, c'est que les promesses commencent à être traitées simultanément, comme elles le feraient si nous n'utilisions pas async/await.

- -

Il existe un modèle qui peut atténuer ce problème - déclencher tous les processus de promesse en stockant les objets Promise dans des variables, et en les attendant tous ensuite. Jetons un coup d'œil à quelques exemples qui prouvent le concept.

- -

Nous disposons de deux exemples - slow-async-await.html (voir le code source) et fast-async-await.html (voir le code source). Les deux commencent par une fonction promise personnalisée qui simule un processus asynchrone avec un appel setTimeout() :

- -
function timeoutPromise(interval) {
-  return new Promise((resolve, reject) => {
-    setTimeout(function(){
-      resolve("fait");
-    }, interval);
-  });
-};
- -

Ensuite, chacun comprend une fonction asynchrone timeTest() qui attend trois appels timeoutPromise() :

- -
async function timeTest() {
-  ...
-}
- -

Chacune d'entre elles se termine en enregistrant une heure de début, en voyant combien de temps la promesse timeTest() met à se réaliser, puis en enregistrant une heure de fin et en indiquant combien de temps l'opération a pris au total :

- -
let startTime = Date.now();
-timeTest().then(() => {
-  let finishTime = Date.now();
-  let timeTaken = finishTime - startTime;
-  console.log("Temps pris en millisecondes : " + timeTaken);
-})
- -

C'est la fonction timeTest() qui diffère dans chaque cas.

- -

Dans l'exemple slow-async-await.html, timeTest() ressemble à ceci :

- -
async function timeTest() {
-  await timeoutPromise(3000);
-  await timeoutPromise(3000);
-  await timeoutPromise(3000);
-}
- -

Ici, nous attendons les trois appels timeoutPromise() directement, en faisant en sorte que chacun d'eux alerte pendant 3 secondes. Chaque appel suivant est forcé d'attendre jusqu'à ce que le dernier soit terminé - si vous exécutez le premier exemple, vous verrez la boîte d'alerte signaler une durée d'exécution totale d'environ 9 secondes.

- -

Dans l'exemple fast-async-await.html, timeTest() ressemble à ceci :

- -
async function timeTest() {
-  const timeoutPromise1 = timeoutPromise(3000);
-  const timeoutPromise2 = timeoutPromise(3000);
-  const timeoutPromise3 = timeoutPromise(3000);
-
-  await timeoutPromise1;
-  await timeoutPromise2;
-  await timeoutPromise3;
-}
- -

Ici, nous stockons les trois objets Promise dans des variables, ce qui a pour effet de déclencher leurs processus associés, tous exécutés simultanément.

- -

Ensuite, nous attendons leurs résultats - parce que les promesses ont toutes commencé à être traitées essentiellement au même moment, les promesses se réaliseront toutes en même temps ; lorsque vous exécuterez le deuxième exemple, vous verrez la boîte d'alerte indiquant un temps d'exécution total d'un peu plus de 3 secondes !

- -

Gestion des erreurs

- -

La stratégie précédente a un défaut : on pourrait avoir des erreurs qui ne seraient pas gérées.

- -

Mettons à jour les exemples précédents en ajoutant une promesse rejetée et une instruction catch à la fin :

- -
function timeoutPromiseResolve(interval) {
-  return new Promise((resolve, reject) => {
-    setTimeout(function(){
-      resolve("Succès");
-    }, interval);
-  });
-};
-
-function timeoutPromiseReject(interval) {
-  return new Promise((resolve, reject) => {
-    setTimeout(function(){
-      reject("Erreur");
-    }, interval);
-  });
-};
-
-async function timeTest() {
-  await timeoutPromiseResolve(5000);
-  await timeoutPromiseReject(2000);
-  await timeoutPromiseResolve(3000);
-}
-
-let startTime = Date.now();
-timeTest().then(() => {})
-.catch(e => {
-  console.log(e);
-  let finishTime = Date.now();
-  let timeTaken = finishTime - startTime;
-  console.log("Temps écoulé en millisecondes : " + timeTaken);
-})
- -

Dans l'exemple qui précède, l'erreur est gérée correctement et le message dans la console apparaît après environ 7 secondes.

- -

Voyons maintenant la deuxième approche :

- -
function timeoutPromiseResolve(interval) {
-  return new Promise((resolve, reject) => {
-    setTimeout(function(){
-      resolve("Succès");
-    }, interval);
-  });
-};
-
-function timeoutPromiseReject(interval) {
-  return new Promise((resolve, reject) => {
-    setTimeout(function(){
-      reject("Erreur");
-    }, interval);
-  });
-};
-
-async function timeTest() {
-  const timeoutPromiseResolve1 = timeoutPromiseResolve(5000);
-  const timeoutPromiseReject2 = timeoutPromiseReject(2000);
-  const timeoutPromiseResolve3 = timeoutPromiseResolve(3000);
-
-  await timeoutPromiseResolve1;
-  await timeoutPromiseReject2;
-  await timeoutPromiseResolve3;
-}
-
-let startTime = Date.now();
-timeTest().then(() => {
-}).catch(e => {
-  console.log(e);
-  let finishTime = Date.now();
-  let timeTaken = finishTime - startTime;
-  console.log("Temps écoulé en millisecondes : " + timeTaken);
-})
- -

Dans cet exemple, on a une erreur qui n'est pas gérée dans la console (après 2 secondes) et le message apparaît après environ 5 secondes.

- -

Pour démarrer les promesses en parallèles et intercepter les erreurs correctement, on pourrait utiliser Promise.all() comme vu auparavant :

- -
function timeoutPromiseResolve(interval) {
-  return new Promise((resolve, reject) => {
-    setTimeout(function(){
-      resolve("Succès");
-    }, interval);
-  });
-};
-
-function timeoutPromiseReject(interval) {
-  return new Promise((resolve, reject) => {
-    setTimeout(function(){
-      reject("Erreur");
-    }, interval);
-  });
-};
-
-async function timeTest() {
-  const timeoutPromiseResolve1 = timeoutPromiseResolve(5000);
-  const timeoutPromiseReject2 = timeoutPromiseReject(2000);
-  const timeoutPromiseResolve3 = timeoutPromiseResolve(3000);
-
-  const results = await Promise.all([timeoutPromiseResolve1, timeoutPromiseReject2, timeoutPromiseResolve3]);
-  return results;
-}
-
-let startTime = Date.now();
-timeTest().then(() => {
-}).catch(e => {
-  console.log(e);
-  let finishTime = Date.now();
-  let timeTaken = finishTime - startTime;
-  console.log("Temps écoulé en millisecondes : " + timeTaken);
-})
- -

Dans cet exemple, l'erreur est gérée correctement après 2 secondes et on a le message dans la console après environ 2 secondes.

- -

La méthode Promise.all() rejète lorsqu'au moins un de ses promesses d'entrée rejète. Si on veut que toutes les promesses soient résolues (correctement ou avec un rejet), on pourra utiliser la méthode Promise.allSettled() à la place.

- -

Méthodes de classe async/await

- -

Une dernière remarque avant de poursuivre, vous pouvez même ajouter async devant les méthodes de classe/objet pour qu'elles renvoient des promesses, et await des promesses à l'intérieur de celles-ci. Jetez un œil au code de la classe ES que nous avons vu dans notre article sur le JavaScript orienté objet, puis regardez notre version modifiée avec une méthode async :

- -
class Personne {
-  constructor(prenom, nomFamille, age, genre, interets) {
-    this.nom = {
-      prenom,
-      nomFamille
-    };
-    this.age = age;
-    this.genre = genre;
-    this.interets = interets;
-  }
-
-  async salutation() {
-    return await Promise.resolve(`Bonjour ! Je suis ${this.nom.prenom}`);
-  };
-
-  aurevoir() {
-    console.log(`${this.nom.prenom} a quitté le bâtiment. À une prochaine fois !`);
-  };
-}
-
-let han = new Personne('Han', 'Solo', 25, 'homme', ['Contrebande']);
- -

La méthode de la première classe peut maintenant être utilisée de la manière suivante :

- -
han.salutation().then(console.log);
- -

Prise en charge des navigateurs

- -

L'une des considérations à prendre en compte pour décider d'utiliser async/awaitest la prise en charge des anciens navigateurs. Ces fonctionnalités sont disponibles dans les versions modernes de la plupart des navigateurs, tout comme les promesses ; les principaux problèmes de prise en charge concernent Internet Explorer et Opera Mini.

- -

Si vous souhaitez utiliser async/await mais que vous êtes préoccupé par la prise en charge de navigateurs plus anciens, vous pouvez envisager d'utiliser la bibliothèque BabelJS. Cela vous permet d'écrire vos applications en utilisant les dernières versions de JavaScript et de laisser Babel déterminer les modifications éventuellement nécessaires pour les navigateurs de vos utilisateurs. Lorsque vous rencontrez un navigateur qui ne supporte pas async/await, le polyfill « prothèse d'émulation » de Babel peut automatiquement fournir des fallbacks « solutions de secours » qui fonctionnent dans les anciens navigateurs.

- -

Conclusion

- -

Et voilà, async/await offre un moyen agréable et simplifié d'écrire du code asynchrone, plus facile à lire et à maintenir. Même si la prise en charge par les navigateurs est plus limitée que d'autres mécanismes de code asynchrone à l'heure où nous écrivons ces lignes, cela vaut la peine de l'apprendre et d'envisager de l'utiliser, maintenant et à l'avenir.

- -

{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Promises", "Learn/JavaScript/Asynchronous/Choosing_the_right_approach", "Learn/JavaScript/Asynchronous")}}

- -

Dans ce module

- - diff --git a/files/fr/learn/javascript/asynchronous/async_await/index.md b/files/fr/learn/javascript/asynchronous/async_await/index.md new file mode 100644 index 0000000000..5955c0f27f --- /dev/null +++ b/files/fr/learn/javascript/asynchronous/async_await/index.md @@ -0,0 +1,546 @@ +--- +title: Faciliter la programmation asynchrone avec async et await +slug: Learn/JavaScript/Asynchronous/Async_await +tags: + - Beginner + - CodingScripting + - Guide + - JavaScript + - Learn + - Promises + - async + - asynchronous + - await +translation_of: Learn/JavaScript/Asynchronous/Async_await +--- +
{{LearnSidebar}}
+ +
{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Promises", "Learn/JavaScript/Asynchronous/Choosing_the_right_approach", "Learn/JavaScript/Asynchronous")}}
+ +

Plus récemment, les fonctions async et le mot-clé await ont été ajoutés au langage JavaScript avec l'édition intitulée ECMAScript 2017. Ces fonctionnalités agissent essentiellement comme du sucre syntaxique sur les promesses, rendant le code asynchrone plus facile à écrire et à lire par la suite. Elles font en sorte que le code asynchrone ressemble davantage au code synchrone de la vieille école, et elles valent donc la peine d'être apprises. Cet article fournit les informations à connaître.

+ + + + + + + + + + + + +
Prérequis :Connaissances informatiques de base, compréhension raisonnable des principes fondamentaux de JavaScript, compréhension du code asynchrone en général et des promesses.
Objectif :Comprendre async/await et comment les utiliser.
+ +

Les bases de async/await

+ +

L'utilisation async/await dans votre code comporte deux parties.

+ +

Le mot-clé async

+ +

Tout d'abord, nous avons le mot-clé async, que vous mettez devant une déclaration de fonction pour la transformer en une fonction asynchrone. Une fonction asynchrone est une fonction qui saura réagir à une éventuelle utilisation du mot-clé await pour invoquer du code asynchrone.

+ +

Essayez de taper les lignes suivantes dans la console JS de votre navigateur :

+ +
function hello() { return "Bonjour" };
+hello();
+ +

La fonction renvoie « Bonjour » — rien de spécial, n'est-ce pas ?

+ +

Mais que se passe-t-il si nous transformons cette fonction en une fonction asynchrone ? Essayez ce qui suit :

+ +
async function hello() { return "Bonjour" };
+hello();
+ +

Ah. L'invocation de la fonction renvoie maintenant une promesse. C'est l'une des caractéristiques des fonctions asynchrones - leurs valeurs de retour sont nécessairement converties en promesses.

+ +

Vous pouvez également créer une expression de fonction asynchrone, comme suit :

+ +
let hello = async function() { return "Bonjour" };
+hello();
+ +

Et vous pouvez utiliser les fonctions fléchées :

+ +
let hello = async () => { return "Bonjour" };
+ +

Elles font toutes à peu près la même chose.

+ +

Pour consommer réellement la valeur renvoyée lorsque la promesse se réalise, puisqu'elle renvoie une promesse, nous pourrions utiliser un bloc .then() :

+ +
hello().then((value) => console.log(value));
+ +

ou même simplement un raccourci tel que

+ +
hello().then(console.log);
+ +

Comme nous l'avons vu dans l'article précédent.

+ +

Ainsi, le mot-clé async est ajouté aux fonctions pour leur indiquer de retourner une promesse plutôt que de retourner directement la valeur.

+ +

Le mot-clé await

+ +

L'avantage d'une fonction asynchrone ne devient apparent que lorsque vous la combinez avec le mot-clé await. await ne fonctionne qu'à l'intérieur de fonctions asynchrones dans du code JavaScript ordinaire, mais il peut être utilisé seul avec des modules JavaScript.

+ +

await peut être placé devant toute fonction asynchrone basée sur une promesse pour mettre en pause votre code sur cette ligne jusqu'à ce que la promesse se réalise, puis retourner la valeur résultante.

+ +

Vous pouvez utiliser await lors de l'appel de toute fonction qui renvoie une promesse, y compris les fonctions de l'API web.

+ +

Voici un exemple trivial :

+ +
async function hello() {
+  return salutation = await Promise.resolve("Bonjour");
+};
+
+hello().then(console.log);
+ +

Bien sûr, l'exemple ci-dessus n'est pas très utile, même s'il sert à illustrer la syntaxe. Passons maintenant à un exemple réel.

+ +

Réécriture du code des promesses avec async/await

+ +

Reprenons un exemple simple de récupération que nous avons vu dans l'article précédent :

+ +
fetch('coffee.jpg')
+.then(response => {
+  if (!response.ok) {
+    throw new Error(`Erreur HTTP ! statut : ${response.status}`);
+  }
+  return response.blob();
+})
+.then(myBlob => {
+  let objectURL = URL.createObjectURL(myBlob);
+  let image = document.createElement('img');
+  image.src = objectURL;
+  document.body.appendChild(image);
+})
+.catch(e => {
+  console.log('Il y a eu un problème avec votre opération de récupération : ' + e.message);
+});
+ +

À ce stade, vous devriez avoir une compréhension raisonnable des promesses et de leur fonctionnement, mais convertissons le tout en utilisant async/await pour voir à quel point cela simplifie les choses :

+ +
async function myFetch() {
+  let response = await fetch('coffee.jpg');
+
+  if (!response.ok) {
+    throw new Error(`Erreur HTTP ! statut : ${response.status}`);
+  }
+
+  let myBlob = await response.blob();
+
+  let objectURL = URL.createObjectURL(myBlob);
+  let image = document.createElement('img');
+  image.src = objectURL;
+  document.body.appendChild(image);
+}
+
+myFetch()
+.catch(e => {
+  console.log('Il y a eu un problème avec votre opération de récupération : ' + e.message);
+});
+ +

Cela rend le code beaucoup plus simple et plus facile à comprendre — plus de blocs .then() partout !

+ +

Étant donné qu'un mot-clé async transforme une fonction en promesse, vous pourriez remanier votre code pour utiliser une approche hybride de promesses et de await, en faisant sortir la seconde moitié de la fonction dans un nouveau bloc pour la rendre plus flexible :

+ +
async function myFetch() {
+  let response = await fetch('coffee.jpg');
+  if (!response.ok) {
+    throw new Error(`Erreur HTTP ! statut : ${response.status}`);
+  }
+  return await response.blob();
+
+}
+
+myFetch().then((blob) => {
+  let objectURL = URL.createObjectURL(blob);
+  let image = document.createElement('img');
+  image.src = objectURL;
+  document.body.appendChild(image);
+}).catch(e => console.log(e));
+ +

Vous pouvez essayer de taper vous-même l'exemple, ou d'exécuter notre exemple en direct (voir aussi le code source).

+ +

Mais comment est-ce que cela fonctionne ?

+ +

Vous remarquerez que nous avons enveloppé le code à l'intérieur d'une fonction, et que nous avons inclus le mot-clé async avant le mot-clé function. C'est nécessaire - vous devez créer une fonction async pour définir un bloc de code dans lequel vous exécuterez votre code async ; comme nous l'avons dit précédemment, await ne fonctionne qu'à l'intérieur de fonctions async.

+ +

À l'intérieur de la définition de la fonction myFetch(), vous pouvez voir que le code ressemble beaucoup à la version précédente de la promesse, mais il y a quelques différences. Au lieu de devoir enchaîner un bloc .then() à la fin de chaque méthode basée sur une promesse, il suffit d'ajouter un mot-clé await avant l'appel de la méthode, puis d'affecter le résultat à une variable. Le mot-clé await fait en sorte que le moteur d'exécution JavaScript mette votre code en pause sur cette ligne, ne permettant pas à d'autres codes de s'exécuter entre-temps jusqu'à ce que l'appel de fonction asynchrone ait retourné son résultat - très utile si le code suivant dépend de ce résultat !

+ +

Une fois que c'est terminé, votre code continue à s'exécuter à partir de la ligne suivante. Par exemple :

+ +
let response = await fetch('coffee.jpg');
+ +

La réponse retournée par la promesse fetch() remplie est affectée à la variable response lorsque cette réponse devient disponible, et le parseur fait une pause sur cette ligne jusqu'à ce que cela se produise. Une fois que la réponse est disponible, le parseur passe à la ligne suivante, qui crée un Blob à partir de celle-ci. Cette ligne invoque également une méthode async basée sur les promesses, nous utilisons donc await ici aussi. Lorsque le résultat de l'opération revient, nous le retournons hors de la fonction myFetch().

+ +

Cela signifie que lorsque nous appelons la fonction myFetch(), elle retourne une promesse, de sorte que nous pouvons enchaîner un .then() à la fin de celle-ci à l'intérieur duquel nous gérons l'affichage du blob à l'écran.

+ +

Vous vous dites probablement déjà « c'est vraiment cool ! », et vous avez raison — moins de blocs .then() pour envelopper le code, et cela ressemble surtout à du code synchrone, donc c'est vraiment intuitif.

+ +

Ajout de la gestion des erreurs

+ +

Si vous voulez ajouter la gestion des erreurs, vous avez plusieurs options.

+ +

Vous pouvez utiliser une structure synchrone try...catch avec async/await. Cet exemple développe la première version du code que nous avons montré ci-dessus :

+ +
async function myFetch() {
+  try {
+    let response = await fetch('coffee.jpg');
+
+    if (!response.ok) {
+      throw new Error(`Erreur HTTP ! statut : ${response.status}`);
+    }
+    let myBlob = await response.blob();
+    let objectURL = URL.createObjectURL(myBlob);
+    let image = document.createElement('img');
+    image.src = objectURL;
+    document.body.appendChild(image);
+
+  } catch(e) {
+    console.log(e);
+  }
+}
+
+myFetch();
+ +

Le bloc catch() {} reçoit un objet d'erreur, que nous avons appelé e ; nous pouvons maintenant l'enregistrer dans la console, et il nous donnera un message d'erreur détaillé montrant où dans le code l'erreur a été lancée.

+ +

Si vous vouliez utiliser la deuxième version (remaniée) du code que nous avons montré ci-dessus, il serait préférable de continuer l'approche hybride et d'enchaîner un bloc .catch() à la fin de l'appel .then(), comme ceci :

+ +
async function myFetch() {
+  let response = await fetch('coffee.jpg');
+  if (!response.ok) {
+    throw new Error(`Erreur HTTP ! statut : ${response.status}`);
+  }
+  return await response.blob();
+
+}
+
+myFetch().then((blob) => {
+  let objectURL = URL.createObjectURL(blob);
+  let image = document.createElement('img');
+  image.src = objectURL;
+  document.body.appendChild(image);
+})
+.catch((e) =>
+  console.log(e)
+);
+ +

En effet, le bloc .catch() attrapera les erreurs survenant à la fois dans l'appel de fonction asynchrone et dans la chaîne de promesses. Si vous utilisiez le bloc try/catch ici, vous pourriez toujours obtenir des erreurs non gérées dans la fonction myFetch() lorsqu'elle est appelée.

+ +

Vous pouvez trouver ces deux exemples sur GitHub :

+ + + +

En attente d'un Promise.all()

+ +

async/await est construit au-dessus de Promises, il est donc compatible avec toutes les fonctionnalités offertes par les promesses. Cela inclut Promise.all() — vous pouvez tout à fait attendre un appel Promise.all() pour obtenir tous les résultats retournés dans une variable d'une manière qui ressemble à du simple code synchrone. Encore une fois, revenons à un exemple que nous avons vu dans notre article précédent. Gardez-le ouvert dans un onglet séparé afin de pouvoir le comparer avec la nouvelle version présentée ci-dessous.

+ +

En convertissant cela en async/await (voir la démo live et le code source), cela ressemble maintenant à ceci :

+ +
async function fetchAndDecode(url, type) {
+  let response = await fetch(url);
+
+  let content;
+
+  if (!response.ok) {
+    throw new Error(`Erreur HTTP ! statut : ${response.status}`);
+  } else {
+    if(type === 'blob') {
+      content = await response.blob();
+    } else if(type === 'text') {
+      content = await response.text();
+    }
+  }
+
+  return content;
+}
+
+async function displayContent() {
+  let coffee = fetchAndDecode('coffee.jpg', 'blob');
+  let tea = fetchAndDecode('tea.jpg', 'blob');
+  let description = fetchAndDecode('description.txt', 'text');
+
+  let values = await Promise.all([coffee, tea, description]);
+
+  let objectURL1 = URL.createObjectURL(values[0]);
+  let objectURL2 = URL.createObjectURL(values[1]);
+  let descText = values[2];
+
+  let image1 = document.createElement('img');
+  let image2 = document.createElement('img');
+  image1.src = objectURL1;
+  image2.src = objectURL2;
+  document.body.appendChild(image1);
+  document.body.appendChild(image2);
+
+  let para = document.createElement('p');
+  para.textContent = descText;
+  document.body.appendChild(para);
+}
+
+displayContent()
+.catch((e) =>
+  console.log(e)
+);
+ +

Vous verrez que la fonction fetchAndDecode() a été convertie facilement en fonction asynchrone avec seulement quelques modifications. Voir la ligne Promise.all() :

+ +
let values = await Promise.all([coffee, tea, description]);
+ +

En utilisant await ici, nous sommes en mesure d'obtenir tous les résultats des trois promesses retournées dans le tableau values, quand ils sont tous disponibles, d'une manière qui ressemble beaucoup à du code synchrone. Nous avons dû envelopper tout le code dans une nouvelle fonction asynchrone, displayContent(), et nous n'avons pas réduit le code de beaucoup de lignes, mais être capable de déplacer la majeure partie du code hors du bloc .then() fournit une simplification agréable et utile, nous laissant avec un programme beaucoup plus lisible.

+ +

Pour la gestion des erreurs, nous avons inclus un bloc .catch() sur notre appel displayContent() ; cela permettra de gérer les erreurs survenant dans les deux fonctions.

+ +
+

Note : Il est également possible d'utiliser un bloc finally au sein d'une fonction asynchrone, à la place d'un bloc asynchrone .finally(), pour montrer un état final sur le déroulement de l'opération — vous pouvez voir cela en action dans notre exemple en direct (voir aussi le code source).

+
+ +

Gérer les ralentissements potentiellement causés par async/await

+ +

Il est vraiment utile de connaître async/await, mais il y a quelques inconvénients à prendre en compte.

+ +

async/await donne à votre code une apparence synchrone, et d'une certaine manière, il le fait se comporter de manière plus synchrone. Le mot-clé await bloque l'exécution de tout le code qui le suit jusqu'à ce que la promesse se réalise, exactement comme dans le cas d'une opération synchrone. Il permet certes aux autres tâches de continuer à s'exécuter entre-temps, mais le code attendu est bloqué.

+ +
async function makeResult(items) {
+  let newArr = [];
+  for(let i=0; i < items.length; i++) {
+    newArr.push('word_'+i);
+  }
+  return newArr;
+}
+
+async function getResult() {
+  let result = await makeResult(items); // Bloqué sur cette ligne
+  useThatResult(result); // Pas exécuté tant que makeResult() n'a pas fini
+}
+ +

Cela signifie que votre code pourrait être ralenti par un nombre important de promesses attendues se produisant directement les unes après les autres. Chaque await attendra que la précédente se termine, alors qu'en réalité ce que vous voulez, c'est que les promesses commencent à être traitées simultanément, comme elles le feraient si nous n'utilisions pas async/await.

+ +

Il existe un modèle qui peut atténuer ce problème - déclencher tous les processus de promesse en stockant les objets Promise dans des variables, et en les attendant tous ensuite. Jetons un coup d'œil à quelques exemples qui prouvent le concept.

+ +

Nous disposons de deux exemples - slow-async-await.html (voir le code source) et fast-async-await.html (voir le code source). Les deux commencent par une fonction promise personnalisée qui simule un processus asynchrone avec un appel setTimeout() :

+ +
function timeoutPromise(interval) {
+  return new Promise((resolve, reject) => {
+    setTimeout(function(){
+      resolve("fait");
+    }, interval);
+  });
+};
+ +

Ensuite, chacun comprend une fonction asynchrone timeTest() qui attend trois appels timeoutPromise() :

+ +
async function timeTest() {
+  ...
+}
+ +

Chacune d'entre elles se termine en enregistrant une heure de début, en voyant combien de temps la promesse timeTest() met à se réaliser, puis en enregistrant une heure de fin et en indiquant combien de temps l'opération a pris au total :

+ +
let startTime = Date.now();
+timeTest().then(() => {
+  let finishTime = Date.now();
+  let timeTaken = finishTime - startTime;
+  console.log("Temps pris en millisecondes : " + timeTaken);
+})
+ +

C'est la fonction timeTest() qui diffère dans chaque cas.

+ +

Dans l'exemple slow-async-await.html, timeTest() ressemble à ceci :

+ +
async function timeTest() {
+  await timeoutPromise(3000);
+  await timeoutPromise(3000);
+  await timeoutPromise(3000);
+}
+ +

Ici, nous attendons les trois appels timeoutPromise() directement, en faisant en sorte que chacun d'eux alerte pendant 3 secondes. Chaque appel suivant est forcé d'attendre jusqu'à ce que le dernier soit terminé - si vous exécutez le premier exemple, vous verrez la boîte d'alerte signaler une durée d'exécution totale d'environ 9 secondes.

+ +

Dans l'exemple fast-async-await.html, timeTest() ressemble à ceci :

+ +
async function timeTest() {
+  const timeoutPromise1 = timeoutPromise(3000);
+  const timeoutPromise2 = timeoutPromise(3000);
+  const timeoutPromise3 = timeoutPromise(3000);
+
+  await timeoutPromise1;
+  await timeoutPromise2;
+  await timeoutPromise3;
+}
+ +

Ici, nous stockons les trois objets Promise dans des variables, ce qui a pour effet de déclencher leurs processus associés, tous exécutés simultanément.

+ +

Ensuite, nous attendons leurs résultats - parce que les promesses ont toutes commencé à être traitées essentiellement au même moment, les promesses se réaliseront toutes en même temps ; lorsque vous exécuterez le deuxième exemple, vous verrez la boîte d'alerte indiquant un temps d'exécution total d'un peu plus de 3 secondes !

+ +

Gestion des erreurs

+ +

La stratégie précédente a un défaut : on pourrait avoir des erreurs qui ne seraient pas gérées.

+ +

Mettons à jour les exemples précédents en ajoutant une promesse rejetée et une instruction catch à la fin :

+ +
function timeoutPromiseResolve(interval) {
+  return new Promise((resolve, reject) => {
+    setTimeout(function(){
+      resolve("Succès");
+    }, interval);
+  });
+};
+
+function timeoutPromiseReject(interval) {
+  return new Promise((resolve, reject) => {
+    setTimeout(function(){
+      reject("Erreur");
+    }, interval);
+  });
+};
+
+async function timeTest() {
+  await timeoutPromiseResolve(5000);
+  await timeoutPromiseReject(2000);
+  await timeoutPromiseResolve(3000);
+}
+
+let startTime = Date.now();
+timeTest().then(() => {})
+.catch(e => {
+  console.log(e);
+  let finishTime = Date.now();
+  let timeTaken = finishTime - startTime;
+  console.log("Temps écoulé en millisecondes : " + timeTaken);
+})
+ +

Dans l'exemple qui précède, l'erreur est gérée correctement et le message dans la console apparaît après environ 7 secondes.

+ +

Voyons maintenant la deuxième approche :

+ +
function timeoutPromiseResolve(interval) {
+  return new Promise((resolve, reject) => {
+    setTimeout(function(){
+      resolve("Succès");
+    }, interval);
+  });
+};
+
+function timeoutPromiseReject(interval) {
+  return new Promise((resolve, reject) => {
+    setTimeout(function(){
+      reject("Erreur");
+    }, interval);
+  });
+};
+
+async function timeTest() {
+  const timeoutPromiseResolve1 = timeoutPromiseResolve(5000);
+  const timeoutPromiseReject2 = timeoutPromiseReject(2000);
+  const timeoutPromiseResolve3 = timeoutPromiseResolve(3000);
+
+  await timeoutPromiseResolve1;
+  await timeoutPromiseReject2;
+  await timeoutPromiseResolve3;
+}
+
+let startTime = Date.now();
+timeTest().then(() => {
+}).catch(e => {
+  console.log(e);
+  let finishTime = Date.now();
+  let timeTaken = finishTime - startTime;
+  console.log("Temps écoulé en millisecondes : " + timeTaken);
+})
+ +

Dans cet exemple, on a une erreur qui n'est pas gérée dans la console (après 2 secondes) et le message apparaît après environ 5 secondes.

+ +

Pour démarrer les promesses en parallèles et intercepter les erreurs correctement, on pourrait utiliser Promise.all() comme vu auparavant :

+ +
function timeoutPromiseResolve(interval) {
+  return new Promise((resolve, reject) => {
+    setTimeout(function(){
+      resolve("Succès");
+    }, interval);
+  });
+};
+
+function timeoutPromiseReject(interval) {
+  return new Promise((resolve, reject) => {
+    setTimeout(function(){
+      reject("Erreur");
+    }, interval);
+  });
+};
+
+async function timeTest() {
+  const timeoutPromiseResolve1 = timeoutPromiseResolve(5000);
+  const timeoutPromiseReject2 = timeoutPromiseReject(2000);
+  const timeoutPromiseResolve3 = timeoutPromiseResolve(3000);
+
+  const results = await Promise.all([timeoutPromiseResolve1, timeoutPromiseReject2, timeoutPromiseResolve3]);
+  return results;
+}
+
+let startTime = Date.now();
+timeTest().then(() => {
+}).catch(e => {
+  console.log(e);
+  let finishTime = Date.now();
+  let timeTaken = finishTime - startTime;
+  console.log("Temps écoulé en millisecondes : " + timeTaken);
+})
+ +

Dans cet exemple, l'erreur est gérée correctement après 2 secondes et on a le message dans la console après environ 2 secondes.

+ +

La méthode Promise.all() rejète lorsqu'au moins un de ses promesses d'entrée rejète. Si on veut que toutes les promesses soient résolues (correctement ou avec un rejet), on pourra utiliser la méthode Promise.allSettled() à la place.

+ +

Méthodes de classe async/await

+ +

Une dernière remarque avant de poursuivre, vous pouvez même ajouter async devant les méthodes de classe/objet pour qu'elles renvoient des promesses, et await des promesses à l'intérieur de celles-ci. Jetez un œil au code de la classe ES que nous avons vu dans notre article sur le JavaScript orienté objet, puis regardez notre version modifiée avec une méthode async :

+ +
class Personne {
+  constructor(prenom, nomFamille, age, genre, interets) {
+    this.nom = {
+      prenom,
+      nomFamille
+    };
+    this.age = age;
+    this.genre = genre;
+    this.interets = interets;
+  }
+
+  async salutation() {
+    return await Promise.resolve(`Bonjour ! Je suis ${this.nom.prenom}`);
+  };
+
+  aurevoir() {
+    console.log(`${this.nom.prenom} a quitté le bâtiment. À une prochaine fois !`);
+  };
+}
+
+let han = new Personne('Han', 'Solo', 25, 'homme', ['Contrebande']);
+ +

La méthode de la première classe peut maintenant être utilisée de la manière suivante :

+ +
han.salutation().then(console.log);
+ +

Prise en charge des navigateurs

+ +

L'une des considérations à prendre en compte pour décider d'utiliser async/awaitest la prise en charge des anciens navigateurs. Ces fonctionnalités sont disponibles dans les versions modernes de la plupart des navigateurs, tout comme les promesses ; les principaux problèmes de prise en charge concernent Internet Explorer et Opera Mini.

+ +

Si vous souhaitez utiliser async/await mais que vous êtes préoccupé par la prise en charge de navigateurs plus anciens, vous pouvez envisager d'utiliser la bibliothèque BabelJS. Cela vous permet d'écrire vos applications en utilisant les dernières versions de JavaScript et de laisser Babel déterminer les modifications éventuellement nécessaires pour les navigateurs de vos utilisateurs. Lorsque vous rencontrez un navigateur qui ne supporte pas async/await, le polyfill « prothèse d'émulation » de Babel peut automatiquement fournir des fallbacks « solutions de secours » qui fonctionnent dans les anciens navigateurs.

+ +

Conclusion

+ +

Et voilà, async/await offre un moyen agréable et simplifié d'écrire du code asynchrone, plus facile à lire et à maintenir. Même si la prise en charge par les navigateurs est plus limitée que d'autres mécanismes de code asynchrone à l'heure où nous écrivons ces lignes, cela vaut la peine de l'apprendre et d'envisager de l'utiliser, maintenant et à l'avenir.

+ +

{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Promises", "Learn/JavaScript/Asynchronous/Choosing_the_right_approach", "Learn/JavaScript/Asynchronous")}}

+ +

Dans ce module

+ + diff --git a/files/fr/learn/javascript/asynchronous/choosing_the_right_approach/index.html b/files/fr/learn/javascript/asynchronous/choosing_the_right_approach/index.html deleted file mode 100644 index 0b71c56c49..0000000000 --- a/files/fr/learn/javascript/asynchronous/choosing_the_right_approach/index.html +++ /dev/null @@ -1,553 +0,0 @@ ---- -title: Choisir la bonne approche -slug: Learn/JavaScript/Asynchronous/Choosing_the_right_approach -tags: - - Beginner - - Intervals - - JavaScript - - Learn - - Optimize - - Promises - - async - - asynchronous - - await - - requestAnimationFrame - - setInterval - - setTimeout - - timeouts -translation_of: Learn/JavaScript/Asynchronous/Choosing_the_right_approach ---- -
{{LearnSidebar}}
- -
{{PreviousMenu("Learn/JavaScript/Asynchronous/Async_await", "Learn/JavaScript/Asynchronous")}}
- -

Pour terminer ce module, nous vous proposons une brève discussion sur les différentes techniques et fonctionnalités asynchrones abordées tout au long de ce module, en examinant laquelle de ces techniques est la plus pertinente en fonction de la situation ainsi que des recommandations et des rappels des pièges courants le cas échéant.

- - - - - - - - - - - - -
Prérequis :Connaissances informatiques de base, une compréhension raisonnable des principes fondamentaux de JavaScript.
Objectif :Être capable de faire un choix judicieux quant à l'utilisation de différentes techniques de programmation asynchrone.
- -

Fonctions de rappels (callbacks) asynchrones

- -

Généralement trouvé dans les API à l'ancienne, une fonction de rappel (ou callback en anglais) implique qu'une fonction soit passée en paramètre à une autre fonction, qui est ensuite invoquée lorsqu'une opération asynchrone est terminée afin réaliser une opération avec le résultat. C'est la méthode qui précédait l'arrivée des promesses : elle n'est pas aussi efficace ou flexible. Ne l'utilisez que si nécessaire.

- - - - - - - - - - - - - - - - - - - -
Utile pour…
Opération unique retardéeOpération répétéeOpérations séquentielles multiplesOpérations simultanées multiples
NonOui (rappels récursifs)Oui (rappels imbriqués)Non
- -

Exemple de code

- -

Un exemple qui charge une ressource via l'API XMLHttpRequest (l'exécuter en direct, et voir la source) :

- -
function loadAsset(url, type, callback) {
-  let xhr = new XMLHttpRequest();
-  xhr.open('GET', url);
-  xhr.responseType = type;
-
-  xhr.onload = function() {
-    callback(xhr.response);
-  };
-
-  xhr.send();
-}
-
-function displayImage(blob) {
-  let objectURL = URL.createObjectURL(blob);
-
-  let image = document.createElement('img');
-  image.src = objectURL;
-  document.body.appendChild(image);
-}
-
-loadAsset('coffee.jpg', 'blob', displayImage);
- -

Pièges

- - - -

Compatibilité des navigateurs

- -

Très bonne prise en charge générale, bien que la prise en charge exacte dans les différentes API dépende de l'API en question. Reportez-vous à la documentation de référence de l'API que vous utilisez pour obtenir des informations plus spécifiques.

- -

Plus d'informations

- - - -

setTimeout()

- -

setTimeout() est une méthode qui permet d'exécuter une fonction après l'écoulement d'un délai arbitraire.

- - - - - - - - - - - - - - - - - - - -
Utile pour…
Opération unique retardéeOpération répétéeOpérations séquentielles multiplesOpérations simultanées multiples
OuiOui (délais récursifs)Oui (délais d'attente imbriqués)Non
- -

Exemple de code

- -

Ici, le navigateur attendra deux secondes avant d'exécuter la fonction anonyme, puis affichera le message dans la console (voir son exécution en direct, et voir le code source) :

- -
let myGreeting = setTimeout(function() {
-  console.log('Bonjour, M. Univers !');
-}, 2000)
- -

Pièges

- -

Vous pouvez utiliser des appels récursifs à setTimeout() pour exécuter une fonction de manière répétée de façon similaire à setInterval(), en utilisant un code comme celui-ci :

- -
let i = 1;
-setTimeout(function run() {
-  console.log(i);
-  i++;
-
-  setTimeout(run, 100);
-}, 100);
- -

Il existe une différence entre setTimeout() appelé récursivement et setInterval() :

- - - -

Lorsque votre code a le potentiel de prendre plus de temps à s'exécuter que l'intervalle de temps que vous avez assigné, il est préférable d'utiliser setTimeout() récursivement - cela maintiendra l'intervalle de temps constant entre les exécutions, quelle que soit la durée d'exécution du code, et vous n'obtiendrez pas d'erreurs.

- -

Compatibilité des navigateurs

- -

{{Compat("api.WindowOrWorkerGlobalScope.setTimeout")}}

- -

Plus d'informations

- - - -

setInterval()

- -

setInterval() est une méthode qui permet d'exécuter une fonction de façon répétée avec des intervalles de temps donnés entre chaque exécution. Cette méthode n'est pas aussi efficace que requestAnimationFrame(), mais elle permet de choisir le rythme d'exécution.

- - - - - - - - - - - - - - - - - - - -
Utile pour…
Opération unique retardéeOpération répétéeOpérations séquentielles multiplesOpérations simultanées multiples
NonOuiNon (à moins qu'elles ne soient les mêmes)Non
- -

Exemple de code

- -

La fonction suivante crée un nouvel objet Date(), en extrait une chaîne de temps à l'aide de toLocaleTimeString(), puis l'affiche dans l'interface utilisateur. Nous l'exécutons ensuite une fois par seconde à l'aide de setInterval(), créant l'effet d'une horloge numérique qui se met à jour une fois par seconde (voir cela en direct, et aussi voir la source) :

- -
function displayTime() {
-  let date = new Date();
-  let time = date.toLocaleTimeString();
-  document.getElementById('demo').textContent = time;
-}
-
-const createClock = setInterval(displayTime, 1000);
- -

Pièges

- - - -

Compatibilité des navigateurs

- -

{{Compat("api.WindowOrWorkerGlobalScope.setInterval")}}

- -

Plus d'informations

- - - -

requestAnimationFrame()

- -

requestAnimationFrame() est une méthode qui vous permet d'exécuter une fonction de manière répétée, et efficace, à la meilleure fréquence de rafraîchissement disponible compte tenu du navigateur/système actuel. Vous devriez, dans la mesure du possible, utiliser cette méthode au lieu de setInterval()/setTimeout() récursif, sauf si vous avez besoin d'une fréquence de rafraîchissement spécifique.

- - - - - - - - - - - - - - - - - - - -
Utile pour…
Opération unique retardéeOpération répétéeOpérations séquentielles multiplesOpérations simultanées multiples
NonOuiNon (à moins que ce soit les mêmes)Non
- -

Exemple de code

- -

Une toupie animée simple ; vous pouvez trouver cet exemple en direct sur GitHub (voir le code source) :

- -
const spinner = document.querySelector('div');
-let rotateCount = 0;
-let startTime = null;
-let rAF;
-
-function draw(timestamp) {
-  if(!startTime) {
-    startTime = timestamp;
-  }
-
-  rotateCount = (timestamp - startTime) / 3;
-
-  if(rotateCount > 359) {
-    rotateCount %= 360;
-  }
-
-  spinner.style.transform = 'rotate(' + rotateCount + 'deg)';
-
-  rAF = requestAnimationFrame(draw);
-}
-
-draw();
- -

Pièges

- - - -

Compatibilité des navigateurs

- -

{{Compat("api.Window.requestAnimationFrame")}}

- -

Plus d'informations

- - - -

Promises (Promesses)

- -

Les promesses sont une fonctionnalité JavaScript qui permet d'exécuter des opérations asynchrones et d'attendre qu'elles soient définitivement terminées avant d'exécuter une autre opération en fonction de son résultat. Les promesses sont la colonne vertébrale du JavaScript asynchrone moderne.

- - - - - - - - - - - - - - - - - - - -
Utile pour…
Opération unique retardéeOpération répétéeOpérations séquentielles multiplesOpérations simultanées multiples
NonNonOuiVoir Promise.all(), en dessous
- -

Exemple de code

- -

Le code suivant va chercher une image sur le serveur et l'affiche à l'intérieur d'un élément <img> ; voyez-le aussi en direct, et voyez aussi le code source :

- -
fetch('coffee.jpg')
-.then(response => response.blob())
-.then(myBlob => {
-  let objectURL = URL.createObjectURL(myBlob);
-  let image = document.createElement('img');
-  image.src = objectURL;
-  document.body.appendChild(image);
-})
-.catch(e => {
-  console.log('Il y a eu un problème avec votre opération de récupération : ' + e.message);
-});
- -

Pièges

- -

Les chaînes de promesses peuvent être complexes et difficiles à analyser. Si vous imbriquez un certain nombre de promesses, vous pouvez vous retrouver avec des problèmes similaires à l'enfer des rappels. Par exemple :

- -
remotedb.allDocs({
-  include_docs: true,
-  attachments: true
-}).then(function (result) {
-  let docs = result.rows;
-  docs.forEach(function(element) {
-    localdb.put(element.doc).then(function(response) {
-      alert("Un document extrait avec un id " + element.doc._id + " et ajouté à la base de données locale.");
-    }).catch(function (err) {
-      if (err.name == 'conflict') {
-        localdb.get(element.doc._id).then(function (resp) {
-          localdb.remove(resp._id, resp._rev).then(function (resp) {
-// et cetera...
- -

Il est préférable d'utiliser la puissance de chaînage des promesses pour aller avec une structure plus plate et plus facile à analyser :

- -
remotedb.allDocs(...).then(function (resultOfAllDocs) {
-  return localdb.put(...);
-}).then(function (resultOfPut) {
-  return localdb.get(...);
-}).then(function (resultOfGet) {
-  return localdb.put(...);
-}).catch(function (err) {
-  console.log(err);
-});
- -

ou encore :

- -
remotedb.allDocs(...)
-.then(resultOfAllDocs => {
-  return localdb.put(...);
-})
-.then(resultOfPut => {
-  return localdb.get(...);
-})
-.then(resultOfGet => {
-  return localdb.put(...);
-})
-.catch(err => console.log(err));
- -

Cela couvre une grande partie des éléments de base. Pour un traitement beaucoup plus complet, voir l'excellent article Nous avons un problème avec les promesses (en), par Nolan Lawson.

- -

Compatibilité des navigateurs

- -

{{Compat("javascript.builtins.Promise")}}

- -

Plus d'informations

- - - -

Promise.all()

- -

Une fonction JavaScript qui vous permet d'attendre que plusieurs promesses se terminent avant d'exécuter une autre opération basée sur les résultats de toutes les autres promesses.

- - - - - - - - - - - - - - - - - - - -
Utile pour…
Opération unique retardéeOpération répétéeOpérations séquentielles multiplesOpérations simultanées multiples
NonNonNonOui
- -

Exemple de code

- -

L'exemple suivant va chercher plusieurs ressources sur le serveur, et utilise Promise.all() pour attendre qu'elles soient toutes disponibles avant de les afficher toutes — le voir fonctionner, et voir son code source :

- -
function fetchAndDecode(url, type) {
-  // Retourner la promesse de niveau supérieur, de sorte que le résultat
-  // de l'ensemble de la chaîne est retourné hors de la fonction
-  return fetch(url).then(response => {
-    // Selon le type de fichier recherché, utilisez la fonction appropriée pour décoder son contenu
-    if(type === 'blob') {
-      return response.blob();
-    } else if(type === 'text') {
-      return response.text();
-    }
-  })
-  .catch(e => {
-    console.log(`Il y a eu un problème avec votre opération de récupération de la ressource "${url}" : ` + e.message);
-  });
-}
-
-// Appeler la méthode fetchAndDecode() pour récupérer les images et le texte
-// et stocker leurs promesses dans des variables
-let coffee = fetchAndDecode('coffee.jpg', 'blob');
-let tea = fetchAndDecode('tea.jpg', 'blob');
-let description = fetchAndDecode('description.txt', 'text');
-
-// Utiliser Promise.all() pour exécuter le code uniquement lorsque
-// les trois appels de fonction ont été résolus
-Promise.all([coffee, tea, description]).then(values => {
-  console.log(values);
-  // Stocker chaque valeur retournée par les promesses dans des variables séparées ;
-  // créer des URL d'objets à partir des blobs.
-  let objectURL1 = URL.createObjectURL(values[0]);
-  let objectURL2 = URL.createObjectURL(values[1]);
-  let descText = values[2];
-
-  // Afficher les images dans les éléments <img>
-  let image1 = document.createElement('img');
-  let image2 = document.createElement('img');
-  image1.src = objectURL1;
-  image2.src = objectURL2;
-  document.body.appendChild(image1);
-  document.body.appendChild(image2);
-
-  // Afficher le texte d'un paragraphe
-  let para = document.createElement('p');
-  para.textContent = descText;
-  document.body.appendChild(para);
-});
- -

Pièges

- - - -

Compatibilité des navigateurs

- -

{{Compat("javascript.builtins.Promise.all")}}

- -

Plus d'informations

- - - -

async/await

- -

Un outil syntaxique construit sur les promesses qui vous permet d'exécuter des opérations asynchrones en utilisant une syntaxe qui ressemble plus à l'écriture de code de rappel synchrone.

- - - - - - - - - - - - - - - - - - - -
Utile pour…
Opération unique retardéeOpération répétéeOpérations séquentielles multiplesOpérations simultanées multiples
NonNonOuiOui (en combinaison avec Promise.all())
- -

Exemple de code

- -

L'exemple suivant est un remaniement de l'exemple simple de promesse que nous avons vu précédemment, qui récupère et affiche une image, écrit à l'aide d'async/await (voir en direct, et voir le code source) :

- -
async function myFetch() {
-  let response = await fetch('coffee.jpg');
-  let myBlob = await response.blob();
-
-  let objectURL = URL.createObjectURL(myBlob);
-  let image = document.createElement('img');
-  image.src = objectURL;
-  document.body.appendChild(image);
-}
-
-myFetch();
- -

Pièges

- - - -

Compatibilité des navigateurs

- -

{{Compat("javascript.statements.async_function")}}

- -

Plus d'informations

- - - -

{{PreviousMenu("Learn/JavaScript/Asynchronous/Async_await", "Learn/JavaScript/Asynchronous")}}

- -

Dans ce module

- - diff --git a/files/fr/learn/javascript/asynchronous/choosing_the_right_approach/index.md b/files/fr/learn/javascript/asynchronous/choosing_the_right_approach/index.md new file mode 100644 index 0000000000..0b71c56c49 --- /dev/null +++ b/files/fr/learn/javascript/asynchronous/choosing_the_right_approach/index.md @@ -0,0 +1,553 @@ +--- +title: Choisir la bonne approche +slug: Learn/JavaScript/Asynchronous/Choosing_the_right_approach +tags: + - Beginner + - Intervals + - JavaScript + - Learn + - Optimize + - Promises + - async + - asynchronous + - await + - requestAnimationFrame + - setInterval + - setTimeout + - timeouts +translation_of: Learn/JavaScript/Asynchronous/Choosing_the_right_approach +--- +
{{LearnSidebar}}
+ +
{{PreviousMenu("Learn/JavaScript/Asynchronous/Async_await", "Learn/JavaScript/Asynchronous")}}
+ +

Pour terminer ce module, nous vous proposons une brève discussion sur les différentes techniques et fonctionnalités asynchrones abordées tout au long de ce module, en examinant laquelle de ces techniques est la plus pertinente en fonction de la situation ainsi que des recommandations et des rappels des pièges courants le cas échéant.

+ + + + + + + + + + + + +
Prérequis :Connaissances informatiques de base, une compréhension raisonnable des principes fondamentaux de JavaScript.
Objectif :Être capable de faire un choix judicieux quant à l'utilisation de différentes techniques de programmation asynchrone.
+ +

Fonctions de rappels (callbacks) asynchrones

+ +

Généralement trouvé dans les API à l'ancienne, une fonction de rappel (ou callback en anglais) implique qu'une fonction soit passée en paramètre à une autre fonction, qui est ensuite invoquée lorsqu'une opération asynchrone est terminée afin réaliser une opération avec le résultat. C'est la méthode qui précédait l'arrivée des promesses : elle n'est pas aussi efficace ou flexible. Ne l'utilisez que si nécessaire.

+ + + + + + + + + + + + + + + + + + + +
Utile pour…
Opération unique retardéeOpération répétéeOpérations séquentielles multiplesOpérations simultanées multiples
NonOui (rappels récursifs)Oui (rappels imbriqués)Non
+ +

Exemple de code

+ +

Un exemple qui charge une ressource via l'API XMLHttpRequest (l'exécuter en direct, et voir la source) :

+ +
function loadAsset(url, type, callback) {
+  let xhr = new XMLHttpRequest();
+  xhr.open('GET', url);
+  xhr.responseType = type;
+
+  xhr.onload = function() {
+    callback(xhr.response);
+  };
+
+  xhr.send();
+}
+
+function displayImage(blob) {
+  let objectURL = URL.createObjectURL(blob);
+
+  let image = document.createElement('img');
+  image.src = objectURL;
+  document.body.appendChild(image);
+}
+
+loadAsset('coffee.jpg', 'blob', displayImage);
+ +

Pièges

+ + + +

Compatibilité des navigateurs

+ +

Très bonne prise en charge générale, bien que la prise en charge exacte dans les différentes API dépende de l'API en question. Reportez-vous à la documentation de référence de l'API que vous utilisez pour obtenir des informations plus spécifiques.

+ +

Plus d'informations

+ + + +

setTimeout()

+ +

setTimeout() est une méthode qui permet d'exécuter une fonction après l'écoulement d'un délai arbitraire.

+ + + + + + + + + + + + + + + + + + + +
Utile pour…
Opération unique retardéeOpération répétéeOpérations séquentielles multiplesOpérations simultanées multiples
OuiOui (délais récursifs)Oui (délais d'attente imbriqués)Non
+ +

Exemple de code

+ +

Ici, le navigateur attendra deux secondes avant d'exécuter la fonction anonyme, puis affichera le message dans la console (voir son exécution en direct, et voir le code source) :

+ +
let myGreeting = setTimeout(function() {
+  console.log('Bonjour, M. Univers !');
+}, 2000)
+ +

Pièges

+ +

Vous pouvez utiliser des appels récursifs à setTimeout() pour exécuter une fonction de manière répétée de façon similaire à setInterval(), en utilisant un code comme celui-ci :

+ +
let i = 1;
+setTimeout(function run() {
+  console.log(i);
+  i++;
+
+  setTimeout(run, 100);
+}, 100);
+ +

Il existe une différence entre setTimeout() appelé récursivement et setInterval() :

+ + + +

Lorsque votre code a le potentiel de prendre plus de temps à s'exécuter que l'intervalle de temps que vous avez assigné, il est préférable d'utiliser setTimeout() récursivement - cela maintiendra l'intervalle de temps constant entre les exécutions, quelle que soit la durée d'exécution du code, et vous n'obtiendrez pas d'erreurs.

+ +

Compatibilité des navigateurs

+ +

{{Compat("api.WindowOrWorkerGlobalScope.setTimeout")}}

+ +

Plus d'informations

+ + + +

setInterval()

+ +

setInterval() est une méthode qui permet d'exécuter une fonction de façon répétée avec des intervalles de temps donnés entre chaque exécution. Cette méthode n'est pas aussi efficace que requestAnimationFrame(), mais elle permet de choisir le rythme d'exécution.

+ + + + + + + + + + + + + + + + + + + +
Utile pour…
Opération unique retardéeOpération répétéeOpérations séquentielles multiplesOpérations simultanées multiples
NonOuiNon (à moins qu'elles ne soient les mêmes)Non
+ +

Exemple de code

+ +

La fonction suivante crée un nouvel objet Date(), en extrait une chaîne de temps à l'aide de toLocaleTimeString(), puis l'affiche dans l'interface utilisateur. Nous l'exécutons ensuite une fois par seconde à l'aide de setInterval(), créant l'effet d'une horloge numérique qui se met à jour une fois par seconde (voir cela en direct, et aussi voir la source) :

+ +
function displayTime() {
+  let date = new Date();
+  let time = date.toLocaleTimeString();
+  document.getElementById('demo').textContent = time;
+}
+
+const createClock = setInterval(displayTime, 1000);
+ +

Pièges

+ + + +

Compatibilité des navigateurs

+ +

{{Compat("api.WindowOrWorkerGlobalScope.setInterval")}}

+ +

Plus d'informations

+ + + +

requestAnimationFrame()

+ +

requestAnimationFrame() est une méthode qui vous permet d'exécuter une fonction de manière répétée, et efficace, à la meilleure fréquence de rafraîchissement disponible compte tenu du navigateur/système actuel. Vous devriez, dans la mesure du possible, utiliser cette méthode au lieu de setInterval()/setTimeout() récursif, sauf si vous avez besoin d'une fréquence de rafraîchissement spécifique.

+ + + + + + + + + + + + + + + + + + + +
Utile pour…
Opération unique retardéeOpération répétéeOpérations séquentielles multiplesOpérations simultanées multiples
NonOuiNon (à moins que ce soit les mêmes)Non
+ +

Exemple de code

+ +

Une toupie animée simple ; vous pouvez trouver cet exemple en direct sur GitHub (voir le code source) :

+ +
const spinner = document.querySelector('div');
+let rotateCount = 0;
+let startTime = null;
+let rAF;
+
+function draw(timestamp) {
+  if(!startTime) {
+    startTime = timestamp;
+  }
+
+  rotateCount = (timestamp - startTime) / 3;
+
+  if(rotateCount > 359) {
+    rotateCount %= 360;
+  }
+
+  spinner.style.transform = 'rotate(' + rotateCount + 'deg)';
+
+  rAF = requestAnimationFrame(draw);
+}
+
+draw();
+ +

Pièges

+ + + +

Compatibilité des navigateurs

+ +

{{Compat("api.Window.requestAnimationFrame")}}

+ +

Plus d'informations

+ + + +

Promises (Promesses)

+ +

Les promesses sont une fonctionnalité JavaScript qui permet d'exécuter des opérations asynchrones et d'attendre qu'elles soient définitivement terminées avant d'exécuter une autre opération en fonction de son résultat. Les promesses sont la colonne vertébrale du JavaScript asynchrone moderne.

+ + + + + + + + + + + + + + + + + + + +
Utile pour…
Opération unique retardéeOpération répétéeOpérations séquentielles multiplesOpérations simultanées multiples
NonNonOuiVoir Promise.all(), en dessous
+ +

Exemple de code

+ +

Le code suivant va chercher une image sur le serveur et l'affiche à l'intérieur d'un élément <img> ; voyez-le aussi en direct, et voyez aussi le code source :

+ +
fetch('coffee.jpg')
+.then(response => response.blob())
+.then(myBlob => {
+  let objectURL = URL.createObjectURL(myBlob);
+  let image = document.createElement('img');
+  image.src = objectURL;
+  document.body.appendChild(image);
+})
+.catch(e => {
+  console.log('Il y a eu un problème avec votre opération de récupération : ' + e.message);
+});
+ +

Pièges

+ +

Les chaînes de promesses peuvent être complexes et difficiles à analyser. Si vous imbriquez un certain nombre de promesses, vous pouvez vous retrouver avec des problèmes similaires à l'enfer des rappels. Par exemple :

+ +
remotedb.allDocs({
+  include_docs: true,
+  attachments: true
+}).then(function (result) {
+  let docs = result.rows;
+  docs.forEach(function(element) {
+    localdb.put(element.doc).then(function(response) {
+      alert("Un document extrait avec un id " + element.doc._id + " et ajouté à la base de données locale.");
+    }).catch(function (err) {
+      if (err.name == 'conflict') {
+        localdb.get(element.doc._id).then(function (resp) {
+          localdb.remove(resp._id, resp._rev).then(function (resp) {
+// et cetera...
+ +

Il est préférable d'utiliser la puissance de chaînage des promesses pour aller avec une structure plus plate et plus facile à analyser :

+ +
remotedb.allDocs(...).then(function (resultOfAllDocs) {
+  return localdb.put(...);
+}).then(function (resultOfPut) {
+  return localdb.get(...);
+}).then(function (resultOfGet) {
+  return localdb.put(...);
+}).catch(function (err) {
+  console.log(err);
+});
+ +

ou encore :

+ +
remotedb.allDocs(...)
+.then(resultOfAllDocs => {
+  return localdb.put(...);
+})
+.then(resultOfPut => {
+  return localdb.get(...);
+})
+.then(resultOfGet => {
+  return localdb.put(...);
+})
+.catch(err => console.log(err));
+ +

Cela couvre une grande partie des éléments de base. Pour un traitement beaucoup plus complet, voir l'excellent article Nous avons un problème avec les promesses (en), par Nolan Lawson.

+ +

Compatibilité des navigateurs

+ +

{{Compat("javascript.builtins.Promise")}}

+ +

Plus d'informations

+ + + +

Promise.all()

+ +

Une fonction JavaScript qui vous permet d'attendre que plusieurs promesses se terminent avant d'exécuter une autre opération basée sur les résultats de toutes les autres promesses.

+ + + + + + + + + + + + + + + + + + + +
Utile pour…
Opération unique retardéeOpération répétéeOpérations séquentielles multiplesOpérations simultanées multiples
NonNonNonOui
+ +

Exemple de code

+ +

L'exemple suivant va chercher plusieurs ressources sur le serveur, et utilise Promise.all() pour attendre qu'elles soient toutes disponibles avant de les afficher toutes — le voir fonctionner, et voir son code source :

+ +
function fetchAndDecode(url, type) {
+  // Retourner la promesse de niveau supérieur, de sorte que le résultat
+  // de l'ensemble de la chaîne est retourné hors de la fonction
+  return fetch(url).then(response => {
+    // Selon le type de fichier recherché, utilisez la fonction appropriée pour décoder son contenu
+    if(type === 'blob') {
+      return response.blob();
+    } else if(type === 'text') {
+      return response.text();
+    }
+  })
+  .catch(e => {
+    console.log(`Il y a eu un problème avec votre opération de récupération de la ressource "${url}" : ` + e.message);
+  });
+}
+
+// Appeler la méthode fetchAndDecode() pour récupérer les images et le texte
+// et stocker leurs promesses dans des variables
+let coffee = fetchAndDecode('coffee.jpg', 'blob');
+let tea = fetchAndDecode('tea.jpg', 'blob');
+let description = fetchAndDecode('description.txt', 'text');
+
+// Utiliser Promise.all() pour exécuter le code uniquement lorsque
+// les trois appels de fonction ont été résolus
+Promise.all([coffee, tea, description]).then(values => {
+  console.log(values);
+  // Stocker chaque valeur retournée par les promesses dans des variables séparées ;
+  // créer des URL d'objets à partir des blobs.
+  let objectURL1 = URL.createObjectURL(values[0]);
+  let objectURL2 = URL.createObjectURL(values[1]);
+  let descText = values[2];
+
+  // Afficher les images dans les éléments <img>
+  let image1 = document.createElement('img');
+  let image2 = document.createElement('img');
+  image1.src = objectURL1;
+  image2.src = objectURL2;
+  document.body.appendChild(image1);
+  document.body.appendChild(image2);
+
+  // Afficher le texte d'un paragraphe
+  let para = document.createElement('p');
+  para.textContent = descText;
+  document.body.appendChild(para);
+});
+ +

Pièges

+ + + +

Compatibilité des navigateurs

+ +

{{Compat("javascript.builtins.Promise.all")}}

+ +

Plus d'informations

+ + + +

async/await

+ +

Un outil syntaxique construit sur les promesses qui vous permet d'exécuter des opérations asynchrones en utilisant une syntaxe qui ressemble plus à l'écriture de code de rappel synchrone.

+ + + + + + + + + + + + + + + + + + + +
Utile pour…
Opération unique retardéeOpération répétéeOpérations séquentielles multiplesOpérations simultanées multiples
NonNonOuiOui (en combinaison avec Promise.all())
+ +

Exemple de code

+ +

L'exemple suivant est un remaniement de l'exemple simple de promesse que nous avons vu précédemment, qui récupère et affiche une image, écrit à l'aide d'async/await (voir en direct, et voir le code source) :

+ +
async function myFetch() {
+  let response = await fetch('coffee.jpg');
+  let myBlob = await response.blob();
+
+  let objectURL = URL.createObjectURL(myBlob);
+  let image = document.createElement('img');
+  image.src = objectURL;
+  document.body.appendChild(image);
+}
+
+myFetch();
+ +

Pièges

+ + + +

Compatibilité des navigateurs

+ +

{{Compat("javascript.statements.async_function")}}

+ +

Plus d'informations

+ + + +

{{PreviousMenu("Learn/JavaScript/Asynchronous/Async_await", "Learn/JavaScript/Asynchronous")}}

+ +

Dans ce module

+ + diff --git a/files/fr/learn/javascript/asynchronous/concepts/index.html b/files/fr/learn/javascript/asynchronous/concepts/index.html deleted file mode 100644 index ee10969b47..0000000000 --- a/files/fr/learn/javascript/asynchronous/concepts/index.html +++ /dev/null @@ -1,165 +0,0 @@ ---- -title: Concepts généraux de programmation asynchrone -slug: Learn/JavaScript/Asynchronous/Concepts -tags: - - JavaScript - - Learn - - Promises - - Threads - - asynchronous - - blocking -translation_of: Learn/JavaScript/Asynchronous/Concepts ---- -
{{LearnSidebar}}{{NextMenu("Learn/JavaScript/Asynchronous/Introducing", "Learn/JavaScript/Asynchronous")}}
- -

Dans cet article, nous allons passer en revue un certain nombre de concepts importants relatifs à la programmation asynchrone et à la façon dont elle se présente dans les navigateurs web et JavaScript. Vous devriez comprendre ces concepts avant de travailler sur les autres articles du module.

- - - - - - - - - - - - -
Prérequis :Connaissances informatiques de base, compréhension raisonnable des principes fondamentaux de JavaScript.
Objectif :Comprendre les concepts de base de la programmation asynchrone et la façon dont elles se manifestent dans les navigateurs Web et dans JavaScript.
- -

Asynchrone ?

- -

Normalement, le code d'un programme donné se déroule sans interruption, une seule chose se produisant à la fois. Si une fonction dépend du résultat d'une autre fonction, elle doit attendre que l'autre fonction se termine et retourne sa réponse, et jusqu'à ce que cela se produise, le programme entier est essentiellement bloqué du point de vue de l'utilisateur.

- -

Les utilisatrices et utilisateurs de macOS, par exemple, le voient parfois avec le curseur rotatif de couleur arc-en-ciel (ou « ballon de plage », comme on l'appelle souvent). Ce curseur est la façon dont le système d'exploitation dit "le programme que vous utilisez actuellement a dû s'arrêter et attendre que quelque chose se termine, et cela prend tellement de temps que je craignais que vous vous demandiez ce qui se passe".

- -

Spinner multicolore pour macOS avec ballon de plage.

- - -

C'est une expérience frustrante qui n'est pas une bonne utilisation de la puissance de traitement de l'ordinateur, surtout à une époque où les ordinateurs disposent de plusieurs cœurs de processeur. Il est inutile de rester assis à attendre quelque chose alors que vous pouvez laisser une tâche se dérouler sur un autre cœur de processeur et être averti quand elle a terminé. Cela vous permet d'effectuer d'autres travaux en même temps, ce qui est la base de la programmation asynchrone. C'est à l'environnement de programmation que vous utilisez (les navigateurs web, dans le cas du développement web) de vous fournir des API qui vous permettent d'exécuter de telles tâches de manière asynchrone.

- -

Code bloquant

- -

Les techniques asynchrones sont très utiles, notamment dans la programmation web. Lorsqu'une application web s'exécute dans un navigateur et qu'elle exécute un morceau de code considérable sans rendre le contrôle au navigateur, ce dernier peut sembler figé. C'est ce qu'on appelle du code bloquant ; le navigateur est incapable de continuer à traiter les entrées de l'utilisateur et d'effectuer d'autres tâches jusqu'à ce que l'application web rende le contrôle du processeur.

- -

Examinons quelques exemples qui montrent ce que nous entendons par blocage.

- -

Dans notre exemple simple-sync.html (voir le fonctionnement en direct), nous ajoutons un écouteur d'événement de clic à un bouton de sorte que, lorsqu'il est cliqué, il exécute une opération qui prend du temps (calcule 10 millions de dates puis enregistre la dernière dans la console) et ajoute ensuite un paragraphe au DOM :

- -
const btn = document.querySelector('button');
-btn.addEventListener('click', () => {
-  let myDate;
-  for(let i = 0; i < 10000000; i++) {
-    let date = new Date();
-    myDate = date;
-  }
-
-  console.log(myDate);
-
-  let pElem = document.createElement('p');
-  pElem.textContent = `Il s'agit d'un paragraphe nouvellement ajouté.`;
-  document.body.appendChild(pElem);
-});
- -

Lorsque vous exécutez l'exemple, ouvrez votre console JavaScript, puis cliquez sur le bouton. Vous remarquerez que le paragraphe n'apparaît qu'une fois que le calcul des dates est terminé et que le message de la console a été enregistré. Le code s'exécute dans l'ordre où il apparaît dans la source, et la dernière opération ne s'exécute pas tant que la première n'est pas terminée.

- -
-

Note : L'exemple précédent est très peu réaliste. Vous ne calculeriez jamais 10 millions de dates sur une véritable application web ! Il sert cependant à vous donner l'idée de base.

-
- -

Dans notre deuxième exemple, simple-sync-ui-blocking.html (voir en direct), nous simulons quelque chose de légèrement plus réaliste que vous pourriez rencontrer sur une page réelle. Nous bloquons l'interactivité de l'utilisateur avec le rendu de l'interface utilisateur. Dans cet exemple, nous avons deux boutons :

- - - -
function expensiveOperation() {
-  for(let i = 0; i < 1000000; i++) {
-    ctx.fillStyle = 'rgba(0,0,255, 0.2)';
-    ctx.beginPath();
-    ctx.arc(random(0, canvas.width), random(0, canvas.height), 10, degToRad(0), degToRad(360), false);
-    ctx.fill();
-  }
-}
-
-fillBtn.addEventListener('click', expensiveOperation);
-
-alertBtn.addEventListener('click', () =>
-  alert('Vous avez cliqué sur moi !');
-);
- -

Si vous cliquez sur le premier bouton, puis rapidement sur le second, vous verrez que l'alerte n'apparaît pas avant que les cercles aient fini d'être rendus. La première opération bloque la seconde jusqu'à ce qu'elle ait fini de s'exécuter.

- -
-

Note : D'accord, dans notre cas, c'est laid et nous simulons l'effet de blocage, mais il s'agit d'un problème courant contre lequel les développeuses et développeurs d'applications réelles se battent sans cesse pour atténuer les impacts indésirables.

-
- -

Pourquoi ? La réponse est que JavaScript, de manière générale, ne s'exécute que sur un seul thread. À ce stade, nous devons introduire le concept de threads.

- -

Les threads

- -

Un thread est fondamentalement un processus unique qu'un programme peut utiliser pour accomplir des tâches. Chaque thread ne peut effectuer qu'une seule tâche à la fois :

- -
Tâche A --> Tâche B --> Tâche C
- -

Chaque tâche sera exécutée de manière séquentielle ; une tâche doit être terminée avant que la suivante puisse être lancée.

- -

Comme nous l'avons dit précédemment, de nombreux ordinateurs sont désormais dotés de plusieurs cœurs et peuvent donc faire plusieurs choses à la fois. Les langages de programmation qui prennent en charge plusieurs processus peuvent utiliser plusieurs cœurs pour accomplir de multiples tâches simultanément :

- -
Processus 1: Tâche A --> Tâche B
-Processus 2: Tâche C --> Tâche D
- -

JavaScript n'a qu'un thread

- -

JavaScript est traditionnellement « single-threaded ». Même avec plusieurs cœurs, vous ne pouviez le faire exécuter des tâches que sur un seul processus, appelé le main thread. Notre exemple ci-dessus est exécuté comme ceci :

- -
processus principal : Rendre des cercles dans <canvas> --> Afficher alert()
- -

Après un certain temps, JavaScript a gagné quelques outils pour aider à résoudre de tels problèmes. Les Web workers vous permettent d'envoyer une partie du traitement JavaScript hors d'un processus distinct, appelé worker, afin que vous puissiez exécuter plusieurs morceaux JavaScript simultanément. Vous utiliserez généralement un worker pour exécuter des processus coûteux hors du processus principal afin de ne pas bloquer l'interaction avec l'utilisateur.

- -
Processus principal : Tâche A --> Tâche C
-Processus du Worker : Tâche coûteuse B
- -

Dans cette optique, jetez un coup d'œil à simple-sync-worker.html (voyez-le fonctionner en direct), toujours avec la console JavaScript de votre navigateur ouverte. Il s'agit d'une réécriture de notre exemple précédent qui calcule les 10 millions de dates dans un fil de travail (worker) séparé. Vous pouvez voir le code du worker ici : worker.js. Désormais, lorsque vous cliquez sur le bouton, le navigateur est capable d'afficher le paragraphe avant que les dates n'aient fini d'être calculées. La première opération ne bloque plus la seconde et une fois que le worker a fini ses calculs, la date est affichée dans la console.

- -

Code asynchrone

- -

Les web workers sont très utiles, mais ils ont leurs limites. L'une des principales est qu'ils ne peuvent pas accéder au DOM. Vous ne pouvez pas demander à un worker de faire directement quelque chose pour mettre à jour l'interface utilisateur. Nous n'avons pas pu rendre nos 1 million de cercles bleus à l'intérieur de notre worker ; il ne peut faire que le calcul des chiffres.

- -

Le deuxième problème est que, bien que le code exécuté dans un worker ne soit pas bloquant, il reste fondamentalement synchrone. Cela devient un problème lorsqu'une fonction s'appuie sur les résultats de plusieurs processus précédents pour fonctionner. Considérons les diagrammes de processus suivants :

- -
Processus principal : Tâche A --> Tâche B
- -

Dans ce cas, disons que la tâche A fait quelque chose comme récupérer une image du serveur et que la tâche B fait ensuite quelque chose à l'image comme lui appliquer un filtre. Si vous lancez la tâche A et essayez immédiatement d'exécuter la tâche B, vous obtiendrez une erreur, car l'image ne sera pas encore disponible.

- -
Processus principal : Tâche A --> Tâche B --> |Tâche D|
-Processus du Worker : Tâche C --------------> |       |
- -

Dans ce cas, disons que la tâche D utilise les résultats de la tâche B et de la tâche C. Si nous pouvons garantir que ces résultats seront tous deux disponibles au même moment, alors nous pourrions être OK, mais c'est peu probable. Si la tâche D tente de s'exécuter alors que l'une de ses entrées n'est pas encore disponible, elle déclenchera une erreur.

- -

Pour résoudre ces problèmes, les navigateurs nous permettent d'exécuter certaines opérations de manière asynchrone. Des fonctionnalités telles que Promise permettent de lancer une opération (par exemple, la récupération d'une image sur le serveur), puis d'attendre le retour du résultat avant de lancer une autre opération :

- -
Processus principal : Tâche A                   Tâche B
-Promesse :               |__opération asynchrone__|
- -

Comme l'opération se déroule ailleurs, le processus principal n'est pas bloqué pendant le traitement de l'opération asynchrone.

- -

Nous allons commencer à examiner comment écrire du code asynchrone dans le prochain article. C'est passionnant, non ? Bonne lecture !

- -

Conclusion

- -

La conception de logiciels modernes s'articule de plus en plus autour de l'utilisation de la programmation asynchrone, afin de permettre aux programmes de faire plusieurs choses à la fois. À mesure que vous utilisez des API plus récentes et plus puissantes, vous trouverez de plus en plus de cas où la seule façon de faire les choses est asynchrone. Il était autrefois difficile d'écrire du code asynchrone. Il faut encore s'y habituer, mais c'est devenu beaucoup plus facile. Dans la suite de ce module, nous étudierons plus en détail pourquoi le code asynchrone est important et comment concevoir un code qui évite certains des problèmes décrits ci-dessus.

- -
{{NextMenu("Learn/JavaScript/Asynchronous/Introducing", "Learn/JavaScript/Asynchronous")}}
- -

Dans ce module

- - diff --git a/files/fr/learn/javascript/asynchronous/concepts/index.md b/files/fr/learn/javascript/asynchronous/concepts/index.md new file mode 100644 index 0000000000..ee10969b47 --- /dev/null +++ b/files/fr/learn/javascript/asynchronous/concepts/index.md @@ -0,0 +1,165 @@ +--- +title: Concepts généraux de programmation asynchrone +slug: Learn/JavaScript/Asynchronous/Concepts +tags: + - JavaScript + - Learn + - Promises + - Threads + - asynchronous + - blocking +translation_of: Learn/JavaScript/Asynchronous/Concepts +--- +
{{LearnSidebar}}{{NextMenu("Learn/JavaScript/Asynchronous/Introducing", "Learn/JavaScript/Asynchronous")}}
+ +

Dans cet article, nous allons passer en revue un certain nombre de concepts importants relatifs à la programmation asynchrone et à la façon dont elle se présente dans les navigateurs web et JavaScript. Vous devriez comprendre ces concepts avant de travailler sur les autres articles du module.

+ + + + + + + + + + + + +
Prérequis :Connaissances informatiques de base, compréhension raisonnable des principes fondamentaux de JavaScript.
Objectif :Comprendre les concepts de base de la programmation asynchrone et la façon dont elles se manifestent dans les navigateurs Web et dans JavaScript.
+ +

Asynchrone ?

+ +

Normalement, le code d'un programme donné se déroule sans interruption, une seule chose se produisant à la fois. Si une fonction dépend du résultat d'une autre fonction, elle doit attendre que l'autre fonction se termine et retourne sa réponse, et jusqu'à ce que cela se produise, le programme entier est essentiellement bloqué du point de vue de l'utilisateur.

+ +

Les utilisatrices et utilisateurs de macOS, par exemple, le voient parfois avec le curseur rotatif de couleur arc-en-ciel (ou « ballon de plage », comme on l'appelle souvent). Ce curseur est la façon dont le système d'exploitation dit "le programme que vous utilisez actuellement a dû s'arrêter et attendre que quelque chose se termine, et cela prend tellement de temps que je craignais que vous vous demandiez ce qui se passe".

+ +

Spinner multicolore pour macOS avec ballon de plage.

+ + +

C'est une expérience frustrante qui n'est pas une bonne utilisation de la puissance de traitement de l'ordinateur, surtout à une époque où les ordinateurs disposent de plusieurs cœurs de processeur. Il est inutile de rester assis à attendre quelque chose alors que vous pouvez laisser une tâche se dérouler sur un autre cœur de processeur et être averti quand elle a terminé. Cela vous permet d'effectuer d'autres travaux en même temps, ce qui est la base de la programmation asynchrone. C'est à l'environnement de programmation que vous utilisez (les navigateurs web, dans le cas du développement web) de vous fournir des API qui vous permettent d'exécuter de telles tâches de manière asynchrone.

+ +

Code bloquant

+ +

Les techniques asynchrones sont très utiles, notamment dans la programmation web. Lorsqu'une application web s'exécute dans un navigateur et qu'elle exécute un morceau de code considérable sans rendre le contrôle au navigateur, ce dernier peut sembler figé. C'est ce qu'on appelle du code bloquant ; le navigateur est incapable de continuer à traiter les entrées de l'utilisateur et d'effectuer d'autres tâches jusqu'à ce que l'application web rende le contrôle du processeur.

+ +

Examinons quelques exemples qui montrent ce que nous entendons par blocage.

+ +

Dans notre exemple simple-sync.html (voir le fonctionnement en direct), nous ajoutons un écouteur d'événement de clic à un bouton de sorte que, lorsqu'il est cliqué, il exécute une opération qui prend du temps (calcule 10 millions de dates puis enregistre la dernière dans la console) et ajoute ensuite un paragraphe au DOM :

+ +
const btn = document.querySelector('button');
+btn.addEventListener('click', () => {
+  let myDate;
+  for(let i = 0; i < 10000000; i++) {
+    let date = new Date();
+    myDate = date;
+  }
+
+  console.log(myDate);
+
+  let pElem = document.createElement('p');
+  pElem.textContent = `Il s'agit d'un paragraphe nouvellement ajouté.`;
+  document.body.appendChild(pElem);
+});
+ +

Lorsque vous exécutez l'exemple, ouvrez votre console JavaScript, puis cliquez sur le bouton. Vous remarquerez que le paragraphe n'apparaît qu'une fois que le calcul des dates est terminé et que le message de la console a été enregistré. Le code s'exécute dans l'ordre où il apparaît dans la source, et la dernière opération ne s'exécute pas tant que la première n'est pas terminée.

+ +
+

Note : L'exemple précédent est très peu réaliste. Vous ne calculeriez jamais 10 millions de dates sur une véritable application web ! Il sert cependant à vous donner l'idée de base.

+
+ +

Dans notre deuxième exemple, simple-sync-ui-blocking.html (voir en direct), nous simulons quelque chose de légèrement plus réaliste que vous pourriez rencontrer sur une page réelle. Nous bloquons l'interactivité de l'utilisateur avec le rendu de l'interface utilisateur. Dans cet exemple, nous avons deux boutons :

+ + + +
function expensiveOperation() {
+  for(let i = 0; i < 1000000; i++) {
+    ctx.fillStyle = 'rgba(0,0,255, 0.2)';
+    ctx.beginPath();
+    ctx.arc(random(0, canvas.width), random(0, canvas.height), 10, degToRad(0), degToRad(360), false);
+    ctx.fill();
+  }
+}
+
+fillBtn.addEventListener('click', expensiveOperation);
+
+alertBtn.addEventListener('click', () =>
+  alert('Vous avez cliqué sur moi !');
+);
+ +

Si vous cliquez sur le premier bouton, puis rapidement sur le second, vous verrez que l'alerte n'apparaît pas avant que les cercles aient fini d'être rendus. La première opération bloque la seconde jusqu'à ce qu'elle ait fini de s'exécuter.

+ +
+

Note : D'accord, dans notre cas, c'est laid et nous simulons l'effet de blocage, mais il s'agit d'un problème courant contre lequel les développeuses et développeurs d'applications réelles se battent sans cesse pour atténuer les impacts indésirables.

+
+ +

Pourquoi ? La réponse est que JavaScript, de manière générale, ne s'exécute que sur un seul thread. À ce stade, nous devons introduire le concept de threads.

+ +

Les threads

+ +

Un thread est fondamentalement un processus unique qu'un programme peut utiliser pour accomplir des tâches. Chaque thread ne peut effectuer qu'une seule tâche à la fois :

+ +
Tâche A --> Tâche B --> Tâche C
+ +

Chaque tâche sera exécutée de manière séquentielle ; une tâche doit être terminée avant que la suivante puisse être lancée.

+ +

Comme nous l'avons dit précédemment, de nombreux ordinateurs sont désormais dotés de plusieurs cœurs et peuvent donc faire plusieurs choses à la fois. Les langages de programmation qui prennent en charge plusieurs processus peuvent utiliser plusieurs cœurs pour accomplir de multiples tâches simultanément :

+ +
Processus 1: Tâche A --> Tâche B
+Processus 2: Tâche C --> Tâche D
+ +

JavaScript n'a qu'un thread

+ +

JavaScript est traditionnellement « single-threaded ». Même avec plusieurs cœurs, vous ne pouviez le faire exécuter des tâches que sur un seul processus, appelé le main thread. Notre exemple ci-dessus est exécuté comme ceci :

+ +
processus principal : Rendre des cercles dans <canvas> --> Afficher alert()
+ +

Après un certain temps, JavaScript a gagné quelques outils pour aider à résoudre de tels problèmes. Les Web workers vous permettent d'envoyer une partie du traitement JavaScript hors d'un processus distinct, appelé worker, afin que vous puissiez exécuter plusieurs morceaux JavaScript simultanément. Vous utiliserez généralement un worker pour exécuter des processus coûteux hors du processus principal afin de ne pas bloquer l'interaction avec l'utilisateur.

+ +
Processus principal : Tâche A --> Tâche C
+Processus du Worker : Tâche coûteuse B
+ +

Dans cette optique, jetez un coup d'œil à simple-sync-worker.html (voyez-le fonctionner en direct), toujours avec la console JavaScript de votre navigateur ouverte. Il s'agit d'une réécriture de notre exemple précédent qui calcule les 10 millions de dates dans un fil de travail (worker) séparé. Vous pouvez voir le code du worker ici : worker.js. Désormais, lorsque vous cliquez sur le bouton, le navigateur est capable d'afficher le paragraphe avant que les dates n'aient fini d'être calculées. La première opération ne bloque plus la seconde et une fois que le worker a fini ses calculs, la date est affichée dans la console.

+ +

Code asynchrone

+ +

Les web workers sont très utiles, mais ils ont leurs limites. L'une des principales est qu'ils ne peuvent pas accéder au DOM. Vous ne pouvez pas demander à un worker de faire directement quelque chose pour mettre à jour l'interface utilisateur. Nous n'avons pas pu rendre nos 1 million de cercles bleus à l'intérieur de notre worker ; il ne peut faire que le calcul des chiffres.

+ +

Le deuxième problème est que, bien que le code exécuté dans un worker ne soit pas bloquant, il reste fondamentalement synchrone. Cela devient un problème lorsqu'une fonction s'appuie sur les résultats de plusieurs processus précédents pour fonctionner. Considérons les diagrammes de processus suivants :

+ +
Processus principal : Tâche A --> Tâche B
+ +

Dans ce cas, disons que la tâche A fait quelque chose comme récupérer une image du serveur et que la tâche B fait ensuite quelque chose à l'image comme lui appliquer un filtre. Si vous lancez la tâche A et essayez immédiatement d'exécuter la tâche B, vous obtiendrez une erreur, car l'image ne sera pas encore disponible.

+ +
Processus principal : Tâche A --> Tâche B --> |Tâche D|
+Processus du Worker : Tâche C --------------> |       |
+ +

Dans ce cas, disons que la tâche D utilise les résultats de la tâche B et de la tâche C. Si nous pouvons garantir que ces résultats seront tous deux disponibles au même moment, alors nous pourrions être OK, mais c'est peu probable. Si la tâche D tente de s'exécuter alors que l'une de ses entrées n'est pas encore disponible, elle déclenchera une erreur.

+ +

Pour résoudre ces problèmes, les navigateurs nous permettent d'exécuter certaines opérations de manière asynchrone. Des fonctionnalités telles que Promise permettent de lancer une opération (par exemple, la récupération d'une image sur le serveur), puis d'attendre le retour du résultat avant de lancer une autre opération :

+ +
Processus principal : Tâche A                   Tâche B
+Promesse :               |__opération asynchrone__|
+ +

Comme l'opération se déroule ailleurs, le processus principal n'est pas bloqué pendant le traitement de l'opération asynchrone.

+ +

Nous allons commencer à examiner comment écrire du code asynchrone dans le prochain article. C'est passionnant, non ? Bonne lecture !

+ +

Conclusion

+ +

La conception de logiciels modernes s'articule de plus en plus autour de l'utilisation de la programmation asynchrone, afin de permettre aux programmes de faire plusieurs choses à la fois. À mesure que vous utilisez des API plus récentes et plus puissantes, vous trouverez de plus en plus de cas où la seule façon de faire les choses est asynchrone. Il était autrefois difficile d'écrire du code asynchrone. Il faut encore s'y habituer, mais c'est devenu beaucoup plus facile. Dans la suite de ce module, nous étudierons plus en détail pourquoi le code asynchrone est important et comment concevoir un code qui évite certains des problèmes décrits ci-dessus.

+ +
{{NextMenu("Learn/JavaScript/Asynchronous/Introducing", "Learn/JavaScript/Asynchronous")}}
+ +

Dans ce module

+ + diff --git a/files/fr/learn/javascript/asynchronous/index.html b/files/fr/learn/javascript/asynchronous/index.html deleted file mode 100644 index 0688d5de42..0000000000 --- a/files/fr/learn/javascript/asynchronous/index.html +++ /dev/null @@ -1,62 +0,0 @@ ---- -title: JavaScript asynchrone -slug: Learn/JavaScript/Asynchronous -tags: - - Beginner - - CodingScripting - - Guide - - JavaScript - - Landing - - Promises - - async - - asynchronous - - await - - callbacks - - requestAnimationFrame - - setInterval - - setTimeout -translation_of: Learn/JavaScript/Asynchronous ---- -
{{LearnSidebar}}
- -

Dans ce module, nous examinons le JavaScript asynchrone, pourquoi il est important et comment il peut être utilisé afin de gérer efficacement les opérations potentiellement bloquantes telles que la récupération de ressources sur un serveur.

- -
-

Vous cherchez à devenir une développeuse ou un développeur web front-end ?

-

Nous avons élaboré un cours qui comprend toutes les informations essentielles dont vous avez besoin pour atteindre votre objectif.

-

Commencer -

-
- -

Prérequis

- -

Le JavaScript asynchrone est un sujet assez avancé, et il vous est conseillé de travailler sur les modules Premiers pas en JavaScript et Blocs de construction de JavaScript avant d'attaquer cette leçon.

- -

Si vous n'êtes pas familier avec le concept de programmation asynchrone, vous devriez absolument commencer par l'article Concepts généraux de programmation asynchrone de ce module. Si vous connaissez ce concept, vous pouvez probablement passer directement au module Introduction au JavaScript asynchrone.

- -
-

Note : Si vous travaillez sur un ordinateur/tablette/autre appareil où vous n'avez pas la possibilité de créer vos propres fichiers, vous pouvez essayer (la plupart) des exemples de code dans un programme de codage en ligne tel que JSBin ou Glitch.

-
- -

Guides

- -
-
Concepts généraux de programmation asynchrone
-
Dans cet article, nous allons passer en revue un certain nombre de concepts importants relatifs à la programmation asynchrone et à la façon dont elle se présente dans les navigateurs web et JavaScript. Vous devez comprendre ces concepts avant de travailler sur les autres articles du module.
-
Introduction au JavaScript asynchrone
-
Dans cet article, nous récapitulons brièvement les problèmes associés au JavaScript synchrone, et nous jetons un premier coup d'œil à certaines des différentes techniques JavaScript asynchrones que vous rencontrerez, en montrant comment elles peuvent nous aider à résoudre ces problèmes.
-
JavaScript asynchrone coopératif : Délais et intervalles
-
Nous examinons ici les méthodes traditionnelles dont dispose JavaScript pour exécuter du code de manière asynchrone après l'écoulement d'une période déterminée ou à un intervalle régulier (par exemple, un nombre déterminé de fois par seconde), nous expliquons à quoi elles servent et nous examinons leurs problèmes inhérents.
-
Gérer les opérations asynchrones avec élégance grâce aux Promesses
-
Les promesses sont une fonctionnalité relativement nouvelle du langage JavaScript qui vous permet de reporter d'autres actions jusqu'à ce que l'action précédente soit terminée, ou de réagir à son échec. C'est très utile pour mettre en place une séquence d'opérations qui fonctionne correctement. Cet article vous montre comment les promesses fonctionnent, où vous les verrez utilisées dans les API Web et comment écrire les vôtres.
-
Faciliter la programmation asynchrone avec async et await
-
Les promesses peuvent être quelque peu complexes à mettre en place et à comprendre, c'est pourquoi les navigateurs modernes ont implémenté les fonctions async et l'opérateur await. Le premier permet aux fonctions standard de se comporter implicitement de manière asynchrone avec les promesses, tandis que le second peut être utilisé à l'intérieur des fonctions async pour attendre les promesses avant que la fonction ne continue. Cela rend l'enchaînement des promesses plus simple et plus facile à lire.
-
Choisir la bonne approche
-
Pour terminer ce module, nous examinerons les différentes techniques et fonctionnalités de codage que nous avons abordées tout au long de ce module, et nous verrons lesquelles vous devez utiliser et à quel moment, avec des recommandations et des rappels des pièges courants le cas échéant.
-
- -

Voir aussi

- - diff --git a/files/fr/learn/javascript/asynchronous/index.md b/files/fr/learn/javascript/asynchronous/index.md new file mode 100644 index 0000000000..0688d5de42 --- /dev/null +++ b/files/fr/learn/javascript/asynchronous/index.md @@ -0,0 +1,62 @@ +--- +title: JavaScript asynchrone +slug: Learn/JavaScript/Asynchronous +tags: + - Beginner + - CodingScripting + - Guide + - JavaScript + - Landing + - Promises + - async + - asynchronous + - await + - callbacks + - requestAnimationFrame + - setInterval + - setTimeout +translation_of: Learn/JavaScript/Asynchronous +--- +
{{LearnSidebar}}
+ +

Dans ce module, nous examinons le JavaScript asynchrone, pourquoi il est important et comment il peut être utilisé afin de gérer efficacement les opérations potentiellement bloquantes telles que la récupération de ressources sur un serveur.

+ +
+

Vous cherchez à devenir une développeuse ou un développeur web front-end ?

+

Nous avons élaboré un cours qui comprend toutes les informations essentielles dont vous avez besoin pour atteindre votre objectif.

+

Commencer +

+
+ +

Prérequis

+ +

Le JavaScript asynchrone est un sujet assez avancé, et il vous est conseillé de travailler sur les modules Premiers pas en JavaScript et Blocs de construction de JavaScript avant d'attaquer cette leçon.

+ +

Si vous n'êtes pas familier avec le concept de programmation asynchrone, vous devriez absolument commencer par l'article Concepts généraux de programmation asynchrone de ce module. Si vous connaissez ce concept, vous pouvez probablement passer directement au module Introduction au JavaScript asynchrone.

+ +
+

Note : Si vous travaillez sur un ordinateur/tablette/autre appareil où vous n'avez pas la possibilité de créer vos propres fichiers, vous pouvez essayer (la plupart) des exemples de code dans un programme de codage en ligne tel que JSBin ou Glitch.

+
+ +

Guides

+ +
+
Concepts généraux de programmation asynchrone
+
Dans cet article, nous allons passer en revue un certain nombre de concepts importants relatifs à la programmation asynchrone et à la façon dont elle se présente dans les navigateurs web et JavaScript. Vous devez comprendre ces concepts avant de travailler sur les autres articles du module.
+
Introduction au JavaScript asynchrone
+
Dans cet article, nous récapitulons brièvement les problèmes associés au JavaScript synchrone, et nous jetons un premier coup d'œil à certaines des différentes techniques JavaScript asynchrones que vous rencontrerez, en montrant comment elles peuvent nous aider à résoudre ces problèmes.
+
JavaScript asynchrone coopératif : Délais et intervalles
+
Nous examinons ici les méthodes traditionnelles dont dispose JavaScript pour exécuter du code de manière asynchrone après l'écoulement d'une période déterminée ou à un intervalle régulier (par exemple, un nombre déterminé de fois par seconde), nous expliquons à quoi elles servent et nous examinons leurs problèmes inhérents.
+
Gérer les opérations asynchrones avec élégance grâce aux Promesses
+
Les promesses sont une fonctionnalité relativement nouvelle du langage JavaScript qui vous permet de reporter d'autres actions jusqu'à ce que l'action précédente soit terminée, ou de réagir à son échec. C'est très utile pour mettre en place une séquence d'opérations qui fonctionne correctement. Cet article vous montre comment les promesses fonctionnent, où vous les verrez utilisées dans les API Web et comment écrire les vôtres.
+
Faciliter la programmation asynchrone avec async et await
+
Les promesses peuvent être quelque peu complexes à mettre en place et à comprendre, c'est pourquoi les navigateurs modernes ont implémenté les fonctions async et l'opérateur await. Le premier permet aux fonctions standard de se comporter implicitement de manière asynchrone avec les promesses, tandis que le second peut être utilisé à l'intérieur des fonctions async pour attendre les promesses avant que la fonction ne continue. Cela rend l'enchaînement des promesses plus simple et plus facile à lire.
+
Choisir la bonne approche
+
Pour terminer ce module, nous examinerons les différentes techniques et fonctionnalités de codage que nous avons abordées tout au long de ce module, et nous verrons lesquelles vous devez utiliser et à quel moment, avec des recommandations et des rappels des pièges courants le cas échéant.
+
+ +

Voir aussi

+ + diff --git a/files/fr/learn/javascript/asynchronous/introducing/index.html b/files/fr/learn/javascript/asynchronous/introducing/index.html deleted file mode 100644 index 1a352017dd..0000000000 --- a/files/fr/learn/javascript/asynchronous/introducing/index.html +++ /dev/null @@ -1,286 +0,0 @@ ---- -title: Introduction au JavaScript asynchrone -slug: Learn/JavaScript/Asynchronous/Introducing -tags: - - Beginner - - CodingScripting - - Guide - - Introducing - - JavaScript - - Learn - - Promises - - async - - asynchronous - - await - - callbacks -translation_of: Learn/JavaScript/Asynchronous/Introducing ---- -
{{LearnSidebar}}
- -
{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Concepts", "Learn/JavaScript/Asynchronous/Timeouts_and_intervals", "Learn/JavaScript/Asynchronous")}}
- -

Dans cet article, nous récapitulons brièvement les problèmes associés au JavaScript synchrone, et nous jetons un premier coup d'œil à certaines des différentes techniques asynchrones que vous rencontrerez, en montrant comment elles peuvent nous aider à résoudre ces problèmes.

- - - - - - - - - - - - -
Prérequis :Connaissances informatiques de base, compréhension raisonnable des principes fondamentaux de JavaScript.
Objectif :Se familiariser avec ce qu'est le JavaScript asynchrone, comment il diffère du JavaScript synchrone et quels sont ses cas d'utilisation.
- -

JavaScript synchrone

- -

Pour nous permettre de comprendre ce qu'est le JavaScript asynchrone, nous devons commencer par nous assurer que nous comprenons ce qu'est le JavaScript synchrone. Cette section récapitule certaines des informations que nous avons vues dans l'article précédent.

- -

Une grande partie des fonctionnalités que nous avons examinées dans les modules précédents du domaine d'apprentissage sont synchrones - vous exécutez un certain code, et le résultat est renvoyé dès que le navigateur peut le faire. Examinons un exemple simple (voir en direct ici, et voir la source) :

- -
const btn = document.querySelector('button');
-btn.addEventListener('click', () => {
-  alert(`Vous avez cliqué sur moi !`);
-
-  let pElem = document.createElement('p');
-  pElem.textContent = `Il s'agit d'un paragraphe nouvellement ajouté.`;
-  document.body.appendChild(pElem);
-});
-
- -

Dans ce bloc, les lignes sont exécutées les unes après les autres :

- -
    -
  1. Nous saisissons une référence à un élément <button> qui est déjà disponible dans le DOM.
  2. -
  3. Nous lui ajoutons un écouteur d'événements click afin que lorsque le bouton est cliqué : -
      -
    1. Un message alert() apparaît.
    2. -
    3. Une fois l'alerte rejetée, nous créons un élément <p>.
    4. -
    5. Nous lui donnons ensuite du contenu textuel.
    6. -
    7. Enfin, nous ajoutons le paragraphe au corps du document.
    8. -
    -
  4. -
- -

Pendant que chaque opération est en cours de traitement, rien d'autre ne peut se produire - le rendu est mis en pause. Cela est dû au fait que, comme nous l'avons dit dans l'article précédent, JavaScript est ne dispose que d'un seul thread. Une seule chose peut se produire à la fois, sur le thread principal, et tout le reste est bloqué jusqu'à la fin d'une opération.

- -

Ainsi, dans l'exemple ci-dessus, après avoir cliqué sur le bouton, le paragraphe n'apparaîtra qu'après avoir appuyé sur le bouton OK dans la boîte d'alerte. Vous pouvez l'essayer vous-même :

- - - -

{{EmbedLiveSample('synchronous_javascript', '100%', '110px')}}

- -
-

Note : Il est important de se rappeler que alert(), tout en étant très utile pour démontrer une opération de blocage synchrone, est horrible à utiliser dans des applications du monde réel.

-
- -

JavaScript asynchrone

- -

Pour des raisons illustrées précédemment (par exemple, en rapport avec le blocage), de nombreuses fonctionnalités des API Web utilisent désormais du code asynchrone pour s'exécuter, en particulier celles qui accèdent à un type de ressource ou le récupèrent à partir d'un périphérique externe, par exemple en récupérant un fichier sur le réseau, en accédant à une base de données et en renvoyant des données, en accédant à un flux vidéo à partir d'une webcam ou en diffusant l'affichage vers un casque VR.

- -

Pourquoi est-il difficile de faire fonctionner un code synchrone ? Prenons un exemple rapide. Lorsque vous récupérez une image sur un serveur, vous ne pouvez pas renvoyer le résultat immédiatement. Cela signifie que l'exemple suivant (pseudocode) ne fonctionnerait pas :

- -
let response = fetch('myImage.png'); // la récupération est asynchrone
-let blob = response.blob();
-// afficher votre blob d'image à l'écran d'une manière ou d'une autre
- -

C'est parce que vous ne savez pas combien de temps l'image prendra pour être téléchargée, donc lorsque vous viendrez à exécuter la deuxième ligne, elle lancera une erreur (éventuellement par intermittence, éventuellement à chaque fois) parce que la réponse response n'est pas encore disponible. Au lieu de cela, vous avez besoin que votre code attende que la réponse response soit retournée avant d'essayer de lui faire quoi que ce soit d'autre.

- -

Il existe deux principaux types de code asynchrone que vous rencontrerez dans le code JavaScript : les anciens rappels et le code plus récent de type promesse. Dans les sections suivantes, nous allons examiner chacun d'eux à tour de rôle.

- -

Fonctions de rappel asynchrones

- -

Les callbacks asynchrones ou fonctions de rappels asynchrones sont des fonctions qui sont passées comme arguments lors de l'appel d'une fonction qui commencera à exécuter du code en arrière-plan. Lorsque le code d'arrière-plan a fini de s'exécuter, il appelle la fonction de rappel pour vous faire savoir que le travail est terminé, ou pour vous faire savoir que quelque chose d'intéressant s'est produit. L'utilisation des callbacks est un peu démodée aujourd'hui, mais vous les verrez encore dans un certain nombre d'API plus anciennes encore couramment utilisées.

- -

Un exemple de rappel asynchrone est le deuxième paramètre de la méthode addEventListener() (comme nous pouvons le voir en action ci-dessous) :

- -
btn.addEventListener('click', () => {
-  alert(`Vous avez cliqué sur moi !`);
-
-  let pElem = document.createElement('p');
-  pElem.textContent = `Il s'agit d'un paragraphe nouvellement ajouté.`;
-  document.body.appendChild(pElem);
-});
- -

Le premier paramètre est le type d'événement à écouter, et le deuxième paramètre est une fonction de rappel qui est invoquée lorsque l'événement est déclenché.

- -

Lorsque nous passons une fonction de rappel comme argument à une autre fonction, nous ne passons que la référence de la fonction comme argument, c'est-à-dire que la fonction de rappel n'est pas exécutée immédiatement. Elle est « rappelée » (d'où son nom) de manière asynchrone quelque part dans le corps de la fonction contenante. La fonction contenante est responsable de l'exécution de la fonction de rappel le moment venu.

- -

Vous pouvez écrire votre propre fonction contenant un callback assez facilement. Examinons un autre exemple qui charge une ressource via l'API XMLHttpRequest (exécutez-le code en direct, et voir sa source) :

- -
function loadAsset(url, type, callback) {
-  let xhr = new XMLHttpRequest();
-  xhr.open('GET', url);
-  xhr.responseType = type;
-
-  xhr.onload = function() {
-    callback(xhr.response);
-  };
-
-  xhr.send();
-}
-
-function displayImage(blob) {
-  let objectURL = URL.createObjectURL(blob);
-
-  let image = document.createElement('img');
-  image.src = objectURL;
-  document.body.appendChild(image);
-}
-
-loadAsset('coffee.jpg', 'blob', displayImage);
- -

Ici, nous créons une fonction displayImage() qui représente un blob qui lui est passé sous forme d'URL d'objet, puis crée une image dans laquelle afficher l'URL, en l'annexant au <body> du document. Cependant, nous créons ensuite une fonction loadAsset() qui prend une fonction de rappel en paramètre, ainsi qu'une URL à récupérer et un type de contenu. Elle utilise XMLHttpRequest (souvent abrégé en "XHR") pour récupérer la ressource à l'URL donnée, puis passe la réponse à la fonction de rappel pour qu'elle fasse quelque chose avec. Dans ce cas, le callback attend que l'appel XHR termine le téléchargement de la ressource (en utilisant l'écouteur d'événement onload) avant de la transmettre au callback.

- -

Les fonctions de rappel sont polyvalentes - non seulement elles permettent de contrôler l'ordre dans lequel les fonctions sont exécutées et quelles données sont transmises entre elles, mais elles permettent également de transmettre des données à différentes fonctions en fonction des circonstances. Ainsi, vous pourriez avoir différentes actions à exécuter sur la réponse téléchargée, comme processingJSON(), displayText(), etc.

- -

Notez que tous les callbacks ne sont pas asynchrones - certains s'exécutent de manière synchrone. Par exemple, lorsque nous utilisons Array.prototype.forEach() pour parcourir en boucle les éléments d'un tableau (voir en direct, et la source) :

- -
const gods = ['Apollon', 'Artémis', 'Arès', 'Zeus'];
-
-gods.forEach(function (eachName, index){
-  console.log(`${index}. ${eachName}`);
-});
- -

Dans cet exemple, nous parcourons en boucle un tableau de dieux grecs et nous imprimons les numéros d'index et les valeurs sur la console. Le paramètre attendu de forEach() est une fonction de rappel, qui prend elle-même deux paramètres, une référence au nom du tableau et aux valeurs d'index. Cependant, elle n'attend rien - elle s'exécute immédiatement.

- -

Promesses

- -

Les promesses sont le nouveau style de code asynchrone que vous verrez utilisé dans les API Web modernes. Un bon exemple est l'API fetch(), qui est en fait comme une version moderne et plus efficace de XMLHttpRequest. Voyons un exemple rapide, tiré de notre article Fetching data from the server :

- -
fetch('products.json').then(function(response) {
-  return response.json();
-}).then(function(json) {
-  let products = json;
-  initialize(products);
-}).catch(function(err) {
-  console.log('Problème de récupération : ' + err.message);
-});
- -
-

Note : Vous pouvez trouver la version finale sur GitHub (voir la source ici, et aussi voir le fonctionnement en direct).

-
- -

Nous voyons ici fetch() prendre un seul paramètre - l'URL d'une ressource que vous souhaitez récupérer sur le réseau - et renvoyer une promesse. La promesse est un objet représentant l'achèvement ou l'échec de l'opération asynchrone. Elle représente un état intermédiaire, en quelque sorte. En substance, c'est la façon dont le navigateur dit « Je promets de vous donner la réponse dès que possible », d'où le nom de « promesse ».

- -

Il faut parfois s'habituer à ce concept, qui ressemble un peu au Chat de Schrödinger en action. Aucun des résultats possibles ne s'est encore produit, donc l'opération de récupération est actuellement en attente du résultat du navigateur qui tente de terminer l'opération à un moment donné dans le futur. Nous avons ensuite trois autres blocs de code enchaînés à la fin de fetch() :

- - - -
-

Note : Vous en apprendrez beaucoup plus sur les promesses plus tard dans le module, alors ne vous inquiétez pas si vous ne les comprenez pas encore complètement.

-
- -

La file d'attente des événements

- -

Les opérations asynchrones comme les promesses sont placées dans une file d'attente d'événements, qui s'exécute après que le processus principal a terminé son traitement afin qu'elles ne bloquent pas l'exécution du code JavaScript suivant. Les opérations mises en file d'attente se terminent dès que possible puis renvoient leurs résultats à l'environnement JavaScript.

- -

Promesses contre callbacks

- -

Les promesses présentent certaines similitudes avec les anciennes fonctions de rappel. Il s'agit essentiellement d'un objet retourné auquel vous attachez des fonctions de rappel, plutôt que de devoir passer des callbacks dans une fonction.

- -

Cependant, les promesses sont spécifiquement conçues pour gérer les opérations asynchrones et présentent de nombreux avantages par rapport aux fonctions de rappel classiques :

- - - -

La nature du code asynchrone

- -

Explorons un exemple qui illustre davantage la nature du code asynchrone, en montrant ce qui peut se produire lorsque nous ne sommes pas pleinement conscients de l'ordre d'exécution du code et les problèmes liés à la tentative de traiter le code asynchrone comme du code synchrone. L'exemple suivant est assez similaire à ce que nous avons vu auparavant (voir en direct, et la source). Une différence est que nous avons inclus un certain nombre d'instructions console.log() pour illustrer un ordre dans lequel on pourrait penser que le code s'exécute.

- -
console.log(`Démarrage`);
-let image;
-
-fetch('coffee.jpg').then((response) => {
-  console.log(`Ça a fonctionné :)`)
-  return response.blob();
-}).then((myBlob) => {
-  let objectURL = URL.createObjectURL(myBlob);
-  image = document.createElement('img');
-  image.src = objectURL;
-  document.body.appendChild(image);
-}).catch((error) => {
-  console.log(`Il y a eu un problème avec votre opération de récupération : ${error.message}`);
-});
-
-console.log(`C'est fait !`);
- -

Le navigateur va commencer à exécuter le code, voir la première instruction console.log() (Démarrage) et l'exécuter, puis créer la variable image.

- -

Il passera ensuite à la ligne suivante et commencera à exécuter le bloc fetch() mais, comme fetch() s'exécute de manière asynchrone sans blocage, l'exécution du code se poursuit après le code lié à la promesse, atteignant ainsi l'instruction finale console.log() (C'est fait !) et la sortant sur la console.

- -

Ce n'est qu'une fois que le bloc fetch() a complètement fini de s'exécuter et de délivrer son résultat à travers les blocs .then() que nous verrons enfin apparaître le deuxième message console.log() (Ça a fonctionné :)). Les messages sont donc apparus dans un ordre différent de celui auquel on pourrait s'attendre :

- - - -

Si cela vous déconcerte, considérez le petit exemple suivant :

- -
console.log(`Enregistrement de l'événement de clics`);
-
-button.addEventListener('click', () => {
-  console.log(`Obtenir un clic`);
-});
-
-console.log(`Tout est bon !`);
- -

Le comportement est très similaire - les premier et troisième messages console.log() s'affichent immédiatement, mais le deuxième est bloqué jusqu'à ce que quelqu'un clique sur le bouton de la souris. L'exemple précédent fonctionne de la même manière, sauf que dans ce cas, le deuxième message est bloqué sur la chaîne de promesses allant chercher une ressource puis l'affichant à l'écran, plutôt que sur un clic.

- -

Dans un exemple de code moins trivial, ce type de configuration pourrait poser un problème - vous ne pouvez pas inclure un bloc de code asynchrone qui renvoie un résultat, sur lequel vous vous appuyez ensuite dans un bloc de code synchrone. Vous ne pouvez tout simplement pas garantir que la fonction asynchrone retournera avant que le navigateur ait traité le bloc de synchronisation.

- -

Pour voir cela en action, essayez de prendre une copie locale de notre exemple, et changez le quatrième appel console.log() par le suivant :

- -
console.log (`Tout est bon ! ${image.src} est affiché.`);
- -

Vous devriez maintenant obtenir une erreur dans votre console au lieu du troisième message :

- -
TypeError: image is undefined; can't access its "src" property
- -

Cela est dû au fait qu'au moment où le navigateur tente d'exécuter la troisième instruction console.log(), le bloc fetch() n'a pas fini de s'exécuter, de sorte que la variable image n'a pas reçu de valeur.

- -
-

Note : Pour des raisons de sécurité, vous ne pouvez pas fetch() les fichiers de votre système de fichiers local (ou exécuter d'autres opérations de ce type localement) ; pour exécuter l'exemple ci-dessus localement, vous devrez le faire passer par un serveur web local.

-
- -

Apprentissage actif : rendez tout asynchrone !

- -

Pour corriger l'exemple problématique de fetch() et faire en sorte que les trois déclarations console.log() apparaissent dans l'ordre souhaité, vous pourriez faire en sorte que la troisième déclaration console.log() s'exécute également de manière asynchrone. Cela peut être fait en la déplaçant à l'intérieur d'un autre bloc .then() enchaîné à la fin du deuxième, ou en la déplaçant à l'intérieur du deuxième bloc then(). Essayez de corriger cela maintenant.

- -
-

Note : Si vous êtes bloqué, vous pouvez trouver une réponse ici (voyez-la fonctionner en direct). Vous pouvez également trouver beaucoup plus d'informations sur les promesses dans notre guide Gérer les opérations asynchrones avec élégance grâce aux Promesses, plus loin dans ce module.

-
- -

Conclusion

- -

Dans sa forme la plus élémentaire, JavaScript est un langage synchrone, bloquant et à un seul processus, dans lequel une seule opération peut être en cours à la fois. Mais les navigateurs web définissent des fonctions et des API qui nous permettent d'enregistrer des fonctions qui ne doivent pas être exécutées de manière synchrone, mais qui doivent être invoquées de manière asynchrone lorsqu'un événement quelconque se produit (le passage du temps, l'interaction de l'utilisateur avec la souris ou l'arrivée de données sur le réseau, par exemple). Cela signifie que vous pouvez laisser votre code faire plusieurs choses en même temps sans arrêter ou bloquer votre processus principal.

- -

Le fait de vouloir exécuter du code de manière synchrone ou asynchrone dépend de ce que l'on essaie de faire.

- -

Il y a des moments où nous voulons que les choses se chargent et se produisent immédiatement. Par exemple, lorsque vous appliquez des styles définis par l'utilisateur à une page web, vous voulez que les styles soient appliqués dès que possible.

- -

Cependant, si nous exécutons une opération qui prend du temps, comme l'interrogation d'une base de données et l'utilisation des résultats pour remplir des modèles, il est préférable d'écarter cette opération du thread principal et de réaliser la tâche de manière asynchrone. Avec le temps, vous apprendrez quand il est plus judicieux de choisir une technique asynchrone plutôt qu'une technique synchrone.

- -

{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Concepts", "Learn/JavaScript/Asynchronous/Timeouts_and_intervals", "Learn/JavaScript/Asynchronous")}}

- -

Dans ce module

- - diff --git a/files/fr/learn/javascript/asynchronous/introducing/index.md b/files/fr/learn/javascript/asynchronous/introducing/index.md new file mode 100644 index 0000000000..1a352017dd --- /dev/null +++ b/files/fr/learn/javascript/asynchronous/introducing/index.md @@ -0,0 +1,286 @@ +--- +title: Introduction au JavaScript asynchrone +slug: Learn/JavaScript/Asynchronous/Introducing +tags: + - Beginner + - CodingScripting + - Guide + - Introducing + - JavaScript + - Learn + - Promises + - async + - asynchronous + - await + - callbacks +translation_of: Learn/JavaScript/Asynchronous/Introducing +--- +
{{LearnSidebar}}
+ +
{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Concepts", "Learn/JavaScript/Asynchronous/Timeouts_and_intervals", "Learn/JavaScript/Asynchronous")}}
+ +

Dans cet article, nous récapitulons brièvement les problèmes associés au JavaScript synchrone, et nous jetons un premier coup d'œil à certaines des différentes techniques asynchrones que vous rencontrerez, en montrant comment elles peuvent nous aider à résoudre ces problèmes.

+ + + + + + + + + + + + +
Prérequis :Connaissances informatiques de base, compréhension raisonnable des principes fondamentaux de JavaScript.
Objectif :Se familiariser avec ce qu'est le JavaScript asynchrone, comment il diffère du JavaScript synchrone et quels sont ses cas d'utilisation.
+ +

JavaScript synchrone

+ +

Pour nous permettre de comprendre ce qu'est le JavaScript asynchrone, nous devons commencer par nous assurer que nous comprenons ce qu'est le JavaScript synchrone. Cette section récapitule certaines des informations que nous avons vues dans l'article précédent.

+ +

Une grande partie des fonctionnalités que nous avons examinées dans les modules précédents du domaine d'apprentissage sont synchrones - vous exécutez un certain code, et le résultat est renvoyé dès que le navigateur peut le faire. Examinons un exemple simple (voir en direct ici, et voir la source) :

+ +
const btn = document.querySelector('button');
+btn.addEventListener('click', () => {
+  alert(`Vous avez cliqué sur moi !`);
+
+  let pElem = document.createElement('p');
+  pElem.textContent = `Il s'agit d'un paragraphe nouvellement ajouté.`;
+  document.body.appendChild(pElem);
+});
+
+ +

Dans ce bloc, les lignes sont exécutées les unes après les autres :

+ +
    +
  1. Nous saisissons une référence à un élément <button> qui est déjà disponible dans le DOM.
  2. +
  3. Nous lui ajoutons un écouteur d'événements click afin que lorsque le bouton est cliqué : +
      +
    1. Un message alert() apparaît.
    2. +
    3. Une fois l'alerte rejetée, nous créons un élément <p>.
    4. +
    5. Nous lui donnons ensuite du contenu textuel.
    6. +
    7. Enfin, nous ajoutons le paragraphe au corps du document.
    8. +
    +
  4. +
+ +

Pendant que chaque opération est en cours de traitement, rien d'autre ne peut se produire - le rendu est mis en pause. Cela est dû au fait que, comme nous l'avons dit dans l'article précédent, JavaScript est ne dispose que d'un seul thread. Une seule chose peut se produire à la fois, sur le thread principal, et tout le reste est bloqué jusqu'à la fin d'une opération.

+ +

Ainsi, dans l'exemple ci-dessus, après avoir cliqué sur le bouton, le paragraphe n'apparaîtra qu'après avoir appuyé sur le bouton OK dans la boîte d'alerte. Vous pouvez l'essayer vous-même :

+ + + +

{{EmbedLiveSample('synchronous_javascript', '100%', '110px')}}

+ +
+

Note : Il est important de se rappeler que alert(), tout en étant très utile pour démontrer une opération de blocage synchrone, est horrible à utiliser dans des applications du monde réel.

+
+ +

JavaScript asynchrone

+ +

Pour des raisons illustrées précédemment (par exemple, en rapport avec le blocage), de nombreuses fonctionnalités des API Web utilisent désormais du code asynchrone pour s'exécuter, en particulier celles qui accèdent à un type de ressource ou le récupèrent à partir d'un périphérique externe, par exemple en récupérant un fichier sur le réseau, en accédant à une base de données et en renvoyant des données, en accédant à un flux vidéo à partir d'une webcam ou en diffusant l'affichage vers un casque VR.

+ +

Pourquoi est-il difficile de faire fonctionner un code synchrone ? Prenons un exemple rapide. Lorsque vous récupérez une image sur un serveur, vous ne pouvez pas renvoyer le résultat immédiatement. Cela signifie que l'exemple suivant (pseudocode) ne fonctionnerait pas :

+ +
let response = fetch('myImage.png'); // la récupération est asynchrone
+let blob = response.blob();
+// afficher votre blob d'image à l'écran d'une manière ou d'une autre
+ +

C'est parce que vous ne savez pas combien de temps l'image prendra pour être téléchargée, donc lorsque vous viendrez à exécuter la deuxième ligne, elle lancera une erreur (éventuellement par intermittence, éventuellement à chaque fois) parce que la réponse response n'est pas encore disponible. Au lieu de cela, vous avez besoin que votre code attende que la réponse response soit retournée avant d'essayer de lui faire quoi que ce soit d'autre.

+ +

Il existe deux principaux types de code asynchrone que vous rencontrerez dans le code JavaScript : les anciens rappels et le code plus récent de type promesse. Dans les sections suivantes, nous allons examiner chacun d'eux à tour de rôle.

+ +

Fonctions de rappel asynchrones

+ +

Les callbacks asynchrones ou fonctions de rappels asynchrones sont des fonctions qui sont passées comme arguments lors de l'appel d'une fonction qui commencera à exécuter du code en arrière-plan. Lorsque le code d'arrière-plan a fini de s'exécuter, il appelle la fonction de rappel pour vous faire savoir que le travail est terminé, ou pour vous faire savoir que quelque chose d'intéressant s'est produit. L'utilisation des callbacks est un peu démodée aujourd'hui, mais vous les verrez encore dans un certain nombre d'API plus anciennes encore couramment utilisées.

+ +

Un exemple de rappel asynchrone est le deuxième paramètre de la méthode addEventListener() (comme nous pouvons le voir en action ci-dessous) :

+ +
btn.addEventListener('click', () => {
+  alert(`Vous avez cliqué sur moi !`);
+
+  let pElem = document.createElement('p');
+  pElem.textContent = `Il s'agit d'un paragraphe nouvellement ajouté.`;
+  document.body.appendChild(pElem);
+});
+ +

Le premier paramètre est le type d'événement à écouter, et le deuxième paramètre est une fonction de rappel qui est invoquée lorsque l'événement est déclenché.

+ +

Lorsque nous passons une fonction de rappel comme argument à une autre fonction, nous ne passons que la référence de la fonction comme argument, c'est-à-dire que la fonction de rappel n'est pas exécutée immédiatement. Elle est « rappelée » (d'où son nom) de manière asynchrone quelque part dans le corps de la fonction contenante. La fonction contenante est responsable de l'exécution de la fonction de rappel le moment venu.

+ +

Vous pouvez écrire votre propre fonction contenant un callback assez facilement. Examinons un autre exemple qui charge une ressource via l'API XMLHttpRequest (exécutez-le code en direct, et voir sa source) :

+ +
function loadAsset(url, type, callback) {
+  let xhr = new XMLHttpRequest();
+  xhr.open('GET', url);
+  xhr.responseType = type;
+
+  xhr.onload = function() {
+    callback(xhr.response);
+  };
+
+  xhr.send();
+}
+
+function displayImage(blob) {
+  let objectURL = URL.createObjectURL(blob);
+
+  let image = document.createElement('img');
+  image.src = objectURL;
+  document.body.appendChild(image);
+}
+
+loadAsset('coffee.jpg', 'blob', displayImage);
+ +

Ici, nous créons une fonction displayImage() qui représente un blob qui lui est passé sous forme d'URL d'objet, puis crée une image dans laquelle afficher l'URL, en l'annexant au <body> du document. Cependant, nous créons ensuite une fonction loadAsset() qui prend une fonction de rappel en paramètre, ainsi qu'une URL à récupérer et un type de contenu. Elle utilise XMLHttpRequest (souvent abrégé en "XHR") pour récupérer la ressource à l'URL donnée, puis passe la réponse à la fonction de rappel pour qu'elle fasse quelque chose avec. Dans ce cas, le callback attend que l'appel XHR termine le téléchargement de la ressource (en utilisant l'écouteur d'événement onload) avant de la transmettre au callback.

+ +

Les fonctions de rappel sont polyvalentes - non seulement elles permettent de contrôler l'ordre dans lequel les fonctions sont exécutées et quelles données sont transmises entre elles, mais elles permettent également de transmettre des données à différentes fonctions en fonction des circonstances. Ainsi, vous pourriez avoir différentes actions à exécuter sur la réponse téléchargée, comme processingJSON(), displayText(), etc.

+ +

Notez que tous les callbacks ne sont pas asynchrones - certains s'exécutent de manière synchrone. Par exemple, lorsque nous utilisons Array.prototype.forEach() pour parcourir en boucle les éléments d'un tableau (voir en direct, et la source) :

+ +
const gods = ['Apollon', 'Artémis', 'Arès', 'Zeus'];
+
+gods.forEach(function (eachName, index){
+  console.log(`${index}. ${eachName}`);
+});
+ +

Dans cet exemple, nous parcourons en boucle un tableau de dieux grecs et nous imprimons les numéros d'index et les valeurs sur la console. Le paramètre attendu de forEach() est une fonction de rappel, qui prend elle-même deux paramètres, une référence au nom du tableau et aux valeurs d'index. Cependant, elle n'attend rien - elle s'exécute immédiatement.

+ +

Promesses

+ +

Les promesses sont le nouveau style de code asynchrone que vous verrez utilisé dans les API Web modernes. Un bon exemple est l'API fetch(), qui est en fait comme une version moderne et plus efficace de XMLHttpRequest. Voyons un exemple rapide, tiré de notre article Fetching data from the server :

+ +
fetch('products.json').then(function(response) {
+  return response.json();
+}).then(function(json) {
+  let products = json;
+  initialize(products);
+}).catch(function(err) {
+  console.log('Problème de récupération : ' + err.message);
+});
+ +
+

Note : Vous pouvez trouver la version finale sur GitHub (voir la source ici, et aussi voir le fonctionnement en direct).

+
+ +

Nous voyons ici fetch() prendre un seul paramètre - l'URL d'une ressource que vous souhaitez récupérer sur le réseau - et renvoyer une promesse. La promesse est un objet représentant l'achèvement ou l'échec de l'opération asynchrone. Elle représente un état intermédiaire, en quelque sorte. En substance, c'est la façon dont le navigateur dit « Je promets de vous donner la réponse dès que possible », d'où le nom de « promesse ».

+ +

Il faut parfois s'habituer à ce concept, qui ressemble un peu au Chat de Schrödinger en action. Aucun des résultats possibles ne s'est encore produit, donc l'opération de récupération est actuellement en attente du résultat du navigateur qui tente de terminer l'opération à un moment donné dans le futur. Nous avons ensuite trois autres blocs de code enchaînés à la fin de fetch() :

+ + + +
+

Note : Vous en apprendrez beaucoup plus sur les promesses plus tard dans le module, alors ne vous inquiétez pas si vous ne les comprenez pas encore complètement.

+
+ +

La file d'attente des événements

+ +

Les opérations asynchrones comme les promesses sont placées dans une file d'attente d'événements, qui s'exécute après que le processus principal a terminé son traitement afin qu'elles ne bloquent pas l'exécution du code JavaScript suivant. Les opérations mises en file d'attente se terminent dès que possible puis renvoient leurs résultats à l'environnement JavaScript.

+ +

Promesses contre callbacks

+ +

Les promesses présentent certaines similitudes avec les anciennes fonctions de rappel. Il s'agit essentiellement d'un objet retourné auquel vous attachez des fonctions de rappel, plutôt que de devoir passer des callbacks dans une fonction.

+ +

Cependant, les promesses sont spécifiquement conçues pour gérer les opérations asynchrones et présentent de nombreux avantages par rapport aux fonctions de rappel classiques :

+ + + +

La nature du code asynchrone

+ +

Explorons un exemple qui illustre davantage la nature du code asynchrone, en montrant ce qui peut se produire lorsque nous ne sommes pas pleinement conscients de l'ordre d'exécution du code et les problèmes liés à la tentative de traiter le code asynchrone comme du code synchrone. L'exemple suivant est assez similaire à ce que nous avons vu auparavant (voir en direct, et la source). Une différence est que nous avons inclus un certain nombre d'instructions console.log() pour illustrer un ordre dans lequel on pourrait penser que le code s'exécute.

+ +
console.log(`Démarrage`);
+let image;
+
+fetch('coffee.jpg').then((response) => {
+  console.log(`Ça a fonctionné :)`)
+  return response.blob();
+}).then((myBlob) => {
+  let objectURL = URL.createObjectURL(myBlob);
+  image = document.createElement('img');
+  image.src = objectURL;
+  document.body.appendChild(image);
+}).catch((error) => {
+  console.log(`Il y a eu un problème avec votre opération de récupération : ${error.message}`);
+});
+
+console.log(`C'est fait !`);
+ +

Le navigateur va commencer à exécuter le code, voir la première instruction console.log() (Démarrage) et l'exécuter, puis créer la variable image.

+ +

Il passera ensuite à la ligne suivante et commencera à exécuter le bloc fetch() mais, comme fetch() s'exécute de manière asynchrone sans blocage, l'exécution du code se poursuit après le code lié à la promesse, atteignant ainsi l'instruction finale console.log() (C'est fait !) et la sortant sur la console.

+ +

Ce n'est qu'une fois que le bloc fetch() a complètement fini de s'exécuter et de délivrer son résultat à travers les blocs .then() que nous verrons enfin apparaître le deuxième message console.log() (Ça a fonctionné :)). Les messages sont donc apparus dans un ordre différent de celui auquel on pourrait s'attendre :

+ + + +

Si cela vous déconcerte, considérez le petit exemple suivant :

+ +
console.log(`Enregistrement de l'événement de clics`);
+
+button.addEventListener('click', () => {
+  console.log(`Obtenir un clic`);
+});
+
+console.log(`Tout est bon !`);
+ +

Le comportement est très similaire - les premier et troisième messages console.log() s'affichent immédiatement, mais le deuxième est bloqué jusqu'à ce que quelqu'un clique sur le bouton de la souris. L'exemple précédent fonctionne de la même manière, sauf que dans ce cas, le deuxième message est bloqué sur la chaîne de promesses allant chercher une ressource puis l'affichant à l'écran, plutôt que sur un clic.

+ +

Dans un exemple de code moins trivial, ce type de configuration pourrait poser un problème - vous ne pouvez pas inclure un bloc de code asynchrone qui renvoie un résultat, sur lequel vous vous appuyez ensuite dans un bloc de code synchrone. Vous ne pouvez tout simplement pas garantir que la fonction asynchrone retournera avant que le navigateur ait traité le bloc de synchronisation.

+ +

Pour voir cela en action, essayez de prendre une copie locale de notre exemple, et changez le quatrième appel console.log() par le suivant :

+ +
console.log (`Tout est bon ! ${image.src} est affiché.`);
+ +

Vous devriez maintenant obtenir une erreur dans votre console au lieu du troisième message :

+ +
TypeError: image is undefined; can't access its "src" property
+ +

Cela est dû au fait qu'au moment où le navigateur tente d'exécuter la troisième instruction console.log(), le bloc fetch() n'a pas fini de s'exécuter, de sorte que la variable image n'a pas reçu de valeur.

+ +
+

Note : Pour des raisons de sécurité, vous ne pouvez pas fetch() les fichiers de votre système de fichiers local (ou exécuter d'autres opérations de ce type localement) ; pour exécuter l'exemple ci-dessus localement, vous devrez le faire passer par un serveur web local.

+
+ +

Apprentissage actif : rendez tout asynchrone !

+ +

Pour corriger l'exemple problématique de fetch() et faire en sorte que les trois déclarations console.log() apparaissent dans l'ordre souhaité, vous pourriez faire en sorte que la troisième déclaration console.log() s'exécute également de manière asynchrone. Cela peut être fait en la déplaçant à l'intérieur d'un autre bloc .then() enchaîné à la fin du deuxième, ou en la déplaçant à l'intérieur du deuxième bloc then(). Essayez de corriger cela maintenant.

+ +
+

Note : Si vous êtes bloqué, vous pouvez trouver une réponse ici (voyez-la fonctionner en direct). Vous pouvez également trouver beaucoup plus d'informations sur les promesses dans notre guide Gérer les opérations asynchrones avec élégance grâce aux Promesses, plus loin dans ce module.

+
+ +

Conclusion

+ +

Dans sa forme la plus élémentaire, JavaScript est un langage synchrone, bloquant et à un seul processus, dans lequel une seule opération peut être en cours à la fois. Mais les navigateurs web définissent des fonctions et des API qui nous permettent d'enregistrer des fonctions qui ne doivent pas être exécutées de manière synchrone, mais qui doivent être invoquées de manière asynchrone lorsqu'un événement quelconque se produit (le passage du temps, l'interaction de l'utilisateur avec la souris ou l'arrivée de données sur le réseau, par exemple). Cela signifie que vous pouvez laisser votre code faire plusieurs choses en même temps sans arrêter ou bloquer votre processus principal.

+ +

Le fait de vouloir exécuter du code de manière synchrone ou asynchrone dépend de ce que l'on essaie de faire.

+ +

Il y a des moments où nous voulons que les choses se chargent et se produisent immédiatement. Par exemple, lorsque vous appliquez des styles définis par l'utilisateur à une page web, vous voulez que les styles soient appliqués dès que possible.

+ +

Cependant, si nous exécutons une opération qui prend du temps, comme l'interrogation d'une base de données et l'utilisation des résultats pour remplir des modèles, il est préférable d'écarter cette opération du thread principal et de réaliser la tâche de manière asynchrone. Avec le temps, vous apprendrez quand il est plus judicieux de choisir une technique asynchrone plutôt qu'une technique synchrone.

+ +

{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Concepts", "Learn/JavaScript/Asynchronous/Timeouts_and_intervals", "Learn/JavaScript/Asynchronous")}}

+ +

Dans ce module

+ + diff --git a/files/fr/learn/javascript/asynchronous/promises/index.html b/files/fr/learn/javascript/asynchronous/promises/index.html deleted file mode 100644 index 69f6de6a21..0000000000 --- a/files/fr/learn/javascript/asynchronous/promises/index.html +++ /dev/null @@ -1,576 +0,0 @@ ---- -title: Gérer les opérations asynchrones avec élégance grâce aux promesses -slug: Learn/JavaScript/Asynchronous/Promises -tags: - - Beginner - - CodingScripting - - Guide - - JavaScript - - Learn - - Promises - - async - - asynchronous - - catch - - finally - - then -translation_of: Learn/JavaScript/Asynchronous/Promises ---- -
{{LearnSidebar}}
- -
{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Timeouts_and_intervals", "Learn/JavaScript/Asynchronous/Async_await", "Learn/JavaScript/Asynchronous")}}
- -

Les promesses sont une fonctionnalité relativement nouvelle du langage JavaScript qui vous permet de reporter d'autres actions jusqu'à ce qu'une action précédente soit terminée, ou de répondre à son échec. Ceci est utile pour mettre en place une séquence d'opérations asynchrones afin qu'elles fonctionnent correctement. Cet article vous montre comment les promesses fonctionnent, comment vous les verrez utilisées avec les API Web, et comment écrire les vôtres.

- - - - - - - - - - - - -
Prérequis :Connaissances informatiques de base, compréhension raisonnable des principes fondamentaux de JavaScript.
Objectif :Comprendre les promesses et savoir comment les utiliser.
- -

Que sont les promesses ?

- -

Nous avons examiné les promesses (Promise) brièvement dans le premier article du cours, mais ici nous allons les examiner de manière beaucoup plus approfondie.

- -

Essentiellement, une promesse est un objet qui représente un état intermédiaire d'une opération - en fait, c'est une promesse qu'un résultat d'une certaine nature sera retourné à un moment donné dans le futur. Il n'y a aucune garantie du moment exact où l'opération se terminera et où le résultat sera renvoyé, mais il est une garantie que lorsque le résultat est disponible, ou que la promesse échoue, le code que vous fournissez sera exécuté afin de faire autre chose avec un résultat réussi, ou de gérer gracieusement un cas d'échec.

- -

En général, vous êtes moins intéressé par le temps qu'une opération asynchrone prendra pour renvoyer son résultat (à moins bien sûr qu'elle ne prenne beaucoup trop de temps !), et plus intéressé par le fait de pouvoir répondre à son retour, quel que soit le moment. Et bien sûr, il est agréable que cela ne bloque pas le reste de l'exécution du code.

- -

L'une des utilisations les plus courantes des promesses concerne les API web qui renvoient une promesse. Considérons une hypothétique application de chat vidéo. L'application dispose d'une fenêtre contenant une liste des amis de l'utilisateur, et un clic sur un bouton à côté d'un utilisateur lance un appel vidéo vers cet utilisateur.

- -

Le gestionnaire de ce bouton appelle getUserMedia() afin d'avoir accès à la caméra et au microphone de l'utilisateur. Puisque getUserMedia() doit s'assurer que l'utilisateur a la permission d'utiliser ces dispositifs et lui demander quel microphone utiliser et quelle caméra utiliser (ou s'il s'agit d'un appel vocal uniquement, parmi d'autres options possibles), il peut bloquer jusqu'à ce que non seulement toutes ces décisions soient prises, mais aussi que la caméra et le microphone soient activés. En outre, l'utilisateur peut ne pas répondre immédiatement à ces demandes d'autorisation. Cela peut potentiellement prendre beaucoup de temps.

- -

Puisque l'appel à getUserMedia() est effectué depuis le processus principal du navigateur, l'ensemble du navigateur est bloqué jusqu'à ce que getUserMedia() retourne une réponse ! Évidemment, ce n'est pas une option viable ; sans les promesses, tout dans le navigateur devient inutilisable jusqu'à ce que l'utilisateur décide ce qu'il faut faire de la caméra et du microphone. Ainsi, au lieu d'attendre l'utilisateur, d'obtenir l'activation des périphériques choisis et de retourner directement le MediaStream pour le flux créé à partir des sources sélectionnées, getUserMedia() retourne une promesse qui est résolue avec le MediaStream une fois qu'il est disponible.

- -

Le code qu'utiliserait l'application de chat vidéo pourrait ressembler à ceci :

- -
function handleCallButton(evt) {
-  setStatusMessage("Appel...");
-  navigator.mediaDevices.getUserMedia({video: true, audio: true})
-    .then(chatStream => {
-      selfViewElem.srcObject = chatStream;
-      chatStream.getTracks().forEach(track => myPeerConnection.addTrack(track, chatStream));
-      setStatusMessage("Connecté");
-    }).catch(err => {
-      setStatusMessage("Échec de la connexion");
-    });
-}
-
- -

Cette fonction commence par utiliser une fonction appelée setStatusMessage() pour mettre à jour un affichage d'état avec le message "Appel...", indiquant qu'un appel est tenté.Il appelle ensuite getUserMedia(), demandant un flux qui a à la fois des pistes vidéo et audio, puis une fois que cela a été obtenu, configure un élément vidéo pour montrer le flux provenant de la caméra comme une "vue de soi", puis prend chacune des pistes du flux et les ajoute à la RTCPeerConnection WebRTC représentant une connexion à un autre utilisateur. Après cela, l'affichage de l'état est mis à jour pour indiquer "Connecté".

- -

Si getUserMedia() échoue, le bloc catch s'exécute. Celui-ci utilise setStatusMessage() pour mettre à jour la case d'état afin d'indiquer qu'une erreur s'est produite.

- -

La chose importante ici est que l'appel getUserMedia() revient presque immédiatement, même si le flux de la caméra n'a pas encore été obtenu. Même si la fonction handleCallButton() est déjà retournée au code qui l'a appelée, lorsque getUserMedia() a fini de travailler, elle appelle le gestionnaire que vous fournissez. Tant que l'application ne suppose pas que le flux a commencé, elle peut continuer à fonctionner.

- -
-

Note : Vous pouvez en apprendre davantage sur ce sujet quelque peu avancé, si cela vous intéresse, dans l'article L'essentiel du WebRTC. Un code similaire à celui-ci, mais beaucoup plus complet, est utilisé dans cet exemple.

-
- -

Le problème des fonctions de rappel

- -

Pour bien comprendre pourquoi les promesses sont une bonne chose, il est utile de repenser aux anciennes fonctions de rappel (callback) et de comprendre pourquoi elles sont problématiques.

- -

Prenons l'exemple de la commande d'une pizza. Il y a certaines étapes que vous devez franchir pour que votre commande soit réussie, et cela n'a pas vraiment de sens d'essayer de les exécuter dans le désordre, ou dans l'ordre mais avant que chaque étape précédente ne soit tout à fait terminée :

- -
    -
  1. Vous choisissez les garnitures que vous voulez. Cela peut prendre un certain temps si vous êtes indécis, et peut échouer si vous n'arrivez pas à vous décider, ou si vous décidez de prendre un curry à la place.
  2. -
  3. Vous passez ensuite votre commande. Le retour d'une pizza peut prendre un certain temps et peut échouer si le restaurant ne dispose pas des ingrédients nécessaires à sa cuisson.
  4. -
  5. Vous récupérez ensuite votre pizza et la mangez. Cela peut échouer si, par exemple, vous avez oublié votre portefeuille et ne pouvez pas payer la pizza !
  6. -
- -

Avec l'ancien modèle de rappels, une représentation en pseudo-code de la fonctionnalité ci-dessus pourrait ressembler à quelque chose comme ceci :

- -
chooseToppings(function(toppings) {
-  placeOrder(toppings, function(order) {
-    collectOrder(order, function(pizza) {
-      eatPizza(pizza);
-    }, failureCallback);
-  }, failureCallback);
-}, failureCallback);
- -

Cela est désordonné et difficile à lire (souvent appelé « callback hell »), nécessite que le failureCallback() soit appelé plusieurs fois (une fois pour chaque fonction imbriquée), avec d'autres problèmes en plus.

- -

Améliorations avec des promesses

- -

Les promesses facilitent grandement l'écriture, l'analyse et l'exécution de situations telles que celle décrite ci-dessus. Si nous avions représenté le pseudo-code ci-dessus en utilisant des promesses asynchrones à la place, nous aurions obtenu quelque chose comme ceci :

- -
chooseToppings()
-.then(function(toppings) {
-  return placeOrder(toppings);
-})
-.then(function(order) {
-  return collectOrder(order);
-})
-.then(function(pizza) {
-  eatPizza(pizza);
-})
-.catch(failureCallback);
- -

C'est bien mieux - il est plus facile de voir ce qui se passe, nous n'avons besoin que d'un seul bloc .catch() pour gérer toutes les erreurs, cela ne bloque pas le processus principal (nous pouvons donc continuer à jouer à des jeux vidéo en attendant que la pizza soit prête à être collectée), et chaque opération a la garantie d'attendre que les opérations précédentes soient terminées avant de s'exécuter. Nous sommes en mesure d'enchaîner plusieurs actions asynchrones pour qu'elles se produisent les unes après les autres de cette façon, car chaque bloc .then() renvoie une nouvelle promesse qui se résout lorsque le bloc .then() a fini de s'exécuter. Astucieux, non ?

- -

En utilisant les fonctions flèches, vous pouvez simplifier encore plus le code :

- -
chooseToppings()
-.then(toppings =>
-  placeOrder(toppings)
-)
-.then(order =>
-  collectOrder(order)
-)
-.then(pizza =>
-  eatPizza(pizza)
-)
-.catch(failureCallback);
- -

Ou encore ça :

- -
chooseToppings()
-.then(toppings => placeOrder(toppings))
-.then(order => collectOrder(order))
-.then(pizza => eatPizza(pizza))
-.catch(failureCallback);
- -

Cela fonctionne car avec les fonctions flèches () => x est un raccourci valide pour () => { return x ; }.

- -

Vous pourriez même le faire ainsi, puisque les fonctions ne font que passer leurs arguments directement, et qu'il n'y a donc pas besoin de cette couche supplémentaire de fonctions :

- -
chooseToppings().then(placeOrder).then(collectOrder).then(eatPizza).catch(failureCallback);
- -

Cependant, la lecture n'est pas aussi facile et cette syntaxe peut ne pas être utilisable si vos blocs sont plus complexes que ce que nous avons montré ici.

- -
-

Note : Vous pouvez apporter d'autres améliorations avec la syntaxe async/await, que nous aborderons dans le prochain article.

-
- -

Dans leur forme la plus basique, les promesses sont similaires aux écouteurs d'événements, mais avec quelques différences :

- - - -

Explication de la syntaxe de base des promesses : exemple concret

- -

Il est important de comprendre les promesses, car la plupart des API Web modernes les utilisent pour les fonctions qui exécutent des tâches potentiellement longues. Pour utiliser les technologies Web modernes, vous devrez utiliser des promesses. Plus loin dans ce chapitre, nous verrons comment écrire votre propre promesse, mais pour l'instant, nous allons nous pencher sur quelques exemples simples que vous rencontrerez dans les API Web.

- -

Dans le premier exemple, nous allons utiliser la méthode fetch() pour récupérer une image sur le web, la méthode blob() pour transformer le contenu brut du corps de la réponse fetch en un objet Blob, puis afficher ce blob à l'intérieur d'un élément <img>. Cet exemple est très similaire à celui que nous avons examiné dans le premier article, mais nous le ferons un peu différemment au fur et à mesure que nous vous ferons construire votre propre code basé sur des promesses.

- -
-

Note : L'exemple suivant ne fonctionnera pas si vous l'exécutez directement à partir du fichier (c'est-à-dire via une URL file://). Vous devez l'exécuter via un serveur de test local, ou utiliser une solution en ligne telle que Glitch ou les pages GitHub.

-
- -
    -
  1. -

    Tout d'abord, téléchargez notre modèle HTML simple et le fichier image que nous allons récupérer.

    -
  2. -
  3. -

    Ajoutez un élément <script> au bas de l'élément HTML <body>.

    -
  4. -
  5. -

    À l'intérieur de votre élément <script>, ajoutez la ligne suivante :

    -
    let promise = fetch('coffee.jpg');
    -

    Cela appelle la méthode fetch(), en lui passant en paramètre l'URL de l'image à récupérer sur le réseau. Cette méthode peut également prendre un objet d'options comme second paramètre facultatif, mais nous n'utilisons que la version la plus simple pour le moment. Nous stockons l'objet promesse retourné par fetch() à l'intérieur d'une variable appelée promise. Comme nous l'avons dit précédemment, cet objet représente un état intermédiaire qui n'est initialement ni un succès ni un échec - le terme officiel pour une promesse dans cet état est en attente (pending en anglais).

    -
  6. -
  7. -

    Pour répondre à l'achèvement réussi de l'opération lorsque cela se produit (dans ce cas, lorsqu'une réponse est retournée), nous invoquons la méthode .then() de l'objet promesse. La fonction de rappel à l'intérieur du bloc .then() s'exécute uniquement lorsque l'appel de la promesse se termine avec succès et retourne l'objet Response — en langage de promesse, lorsqu'elle a été remplie (fullfilled en anglais). On lui passe l'objet Response retourné en tant que paramètre.

    -
    -

    Note : Le fonctionnement d'un bloc .then() est similaire à celui de l'ajout d'un écouteur d'événements à un objet à l'aide de AddEventListener(). Il ne s'exécute pas avant qu'un événement ne se produise (lorsque la promesse se réalise). La différence la plus notable est qu'un .then() ne s'exécutera qu'une fois à chaque fois qu'il sera utilisé, alors qu'un écouteur d'événements pourrait être invoqué plusieurs fois.

    -
    -

    Nous exécutons immédiatement la méthode blob() sur cette réponse pour nous assurer que le corps de la réponse est entièrement téléchargé, et lorsqu'il est disponible, le transformer en un objet Blob avec lequel nous pouvons faire quelque chose. Le résultat de cette méthode est retourné comme suit :

    -
    response => response.blob()
    -

    qui est un raccourci de

    -
    function(response) {
    -  return response.blob();
    -}
    -

    Malheureusement, nous devons faire un peu plus que cela. Les promesses de récupération n'échouent pas sur les erreurs 404 ou 500 - seulement sur quelque chose de catastrophique comme une panne de réseau. Au lieu de cela, elles réussissent, mais avec la propriété response.ok définie à false. Pour produire une erreur sur un 404, par exemple, nous devons vérifier la valeur de response.ok, et si c'est false, lancer une erreur, ne renvoyant le blob que si elle est à true. Cela peut être fait comme suit - ajoutez les lignes suivantes sous votre première ligne de JavaScript.

    -
    let promise2 = promise.then(response => {
    -  if (!response.ok) {
    -    throw new Error(`erreur HTTP ! statut : ${response.status}`);
    -  } else {
    -    return response.blob();
    -  }
    -});
    -
  8. -
  9. -

    Chaque appel à la méthode .then() crée une nouvelle promesse. Ceci est très utile ; parce que la méthode blob() renvoie également une promesse, nous pouvons manipuler l'objet Blob qu'elle renvoie sur l'accomplissement en invoquant la méthode .then() de la seconde promesse. Parce que nous voulons faire quelque chose d'un peu plus complexe au blob que de simplement exécuter une seule méthode sur lui et renvoyer le résultat, nous devrons envelopper le corps de la fonction dans des accolades cette fois (sinon, ça lancera une erreur).

    -

    Ajoutez ce qui suit à la fin de votre code :

    -
    let promise3 = promise2.then(myBlob => {
    -})
    -
  10. -
  11. -

    Maintenant, remplissons le corps de la fonction de rappel .then(). Ajoutez les lignes suivantes à l'intérieur des accolades :

    -
    let objectURL = URL.createObjectURL(myBlob);
    -let image = document.createElement('img');
    -image.src = objectURL;
    -document.body.appendChild(image);
    -

    Nous exécutons ici la méthode URL.createObjectURL(), en lui passant en paramètre le Blob renvoyé lors de la réalisation de la deuxième promesse. Cela permettra de renvoyer une URL pointant vers l'objet. Ensuite, nous créons un élément <img>, définissons son attribut src comme étant égal à l'URL de l'objet et l'ajoutons au DOM, de sorte que l'image s'affiche sur la page !

    -
  12. -
- -

Si vous enregistrez le fichier HTML que vous venez de créer et le chargez dans votre navigateur, vous verrez que l'image s'affiche dans la page comme prévu. Bon travail !

- -
-

Note : Vous remarquerez probablement que ces exemples sont quelque peu artificiels. Vous pourriez tout simplement vous passer de toute la chaîne fetch() et blob(), et simplement créer un élément <img> et définir la valeur de son attribut src à l'URL du fichier image, coffee.jpg. Nous avons toutefois choisi cet exemple parce qu'il démontre les promesses d'une manière simple et agréable, plutôt que pour sa pertinence dans le monde réel.

-
- -

Réagir à un échec

- -

Il manque quelque chose — actuellement, il n'y a rien pour gérer explicitement les erreurs si l'une des promesses échoue (rejects, en anglais). Nous pouvons ajouter la gestion des erreurs en exécutant la méthode .catch() de la promesse précédente. Ajoutez ceci maintenant :

- -
let errorCase = promise3.catch(e => {
-  console.log('Il y a eu un problème avec votre opération de récupération : ' + e.message);
-});
- -

Pour voir cela en action, essayez de mal orthographier l'URL de l'image et de recharger la page. L'erreur sera signalée dans la console des outils de développement de votre navigateur.

- -

Cela ne fait pas beaucoup plus que si vous ne preniez pas la peine d'inclure le bloc .catch() du tout, mais pensez-y - cela nous permet de contrôler la gestion des erreurs exactement comme nous le voulons. Dans une application réelle, votre bloc .catch() pourrait réessayer de récupérer l'image, ou afficher une image par défaut, ou demander à l'utilisateur de fournir une URL d'image différente, ou autre.

- -
-

Note : Vous pouvez voir notre version de l'exemple en direct (voir également son code source).

-
- -

Enchaîner les blocs

- -

C'est une façon très manuscrite d'écrire cela ; nous l'avons délibérément fait pour vous aider à comprendre clairement ce qui se passe. Comme nous l'avons montré plus haut dans l'article, vous pouvez enchaîner les blocs .then() (et aussi les blocs .catch()). Le code ci-dessus pourrait aussi être écrit comme ceci (voir aussi simple-fetch-chained.html sur GitHub) :

- -
fetch('coffee.jpg')
-.then(response => {
-  if (!response.ok) {
-    throw new Error(`Erreur HTTP ! statut : ${response.status}`);
-  } else {
-    return response.blob();
-  }
-})
-.then(myBlob => {
-  let objectURL = URL.createObjectURL(myBlob);
-  let image = document.createElement('img');
-  image.src = objectURL;
-  document.body.appendChild(image);
-})
-.catch(e => {
-  console.log('Il y a eu un problème avec votre opération de récupération : ' + e.message);
-});
- -

Gardez à l'esprit que la valeur renvoyée par une promesse remplie devient le paramètre transmis à la fonction de rappel du bloc .then() suivant.

- -
-

Note : Les blocs .then()/.catch() dans les promesses sont essentiellement l'équivalent asynchrone d'un bloc try...catch dans du code synchrone. Gardez à l'esprit que le try...catch synchrone ne fonctionnera pas dans du code asynchrone.

-
- -

Récapitulatif de la terminologie des promesses

- -

Il y avait beaucoup à couvrir dans la section ci-dessus, alors revenons-y rapidement pour vous donner un court guide que vous pouvez mettre en favoris et utiliser pour vous rafraîchir la mémoire à l'avenir. Vous devriez également revoir la section ci-dessus quelques fois de plus pour vous assurer que ces concepts sont bien assimilés.

- -
    -
  1. Lorsqu'une promesse est créée, elle n'est ni dans un état de réussite ni dans un état d'échec. On dit qu'elle est en attente.
  2. -
  3. Lorsqu'une promesse est retournée, on dit qu'elle est résolue. -
      -
    1. Une promesse résolue avec succès est dite remplie. Elle retourne une valeur, à laquelle on peut accéder en enchaînant un bloc .then() à la fin de la chaîne de promesses. La fonction de rappel à l'intérieur du bloc .then() contiendra la valeur de retour de la promesse.
    2. -
    3. Une promesse résolue non aboutie est dite rejetée. Elle renvoie une raison, un message d'erreur indiquant pourquoi la promesse a été rejetée. On peut accéder à cette raison en enchaînant un bloc .catch() à la fin de la chaîne de promesses.
    4. -
    -
  4. -
- -

Exécution du code en réponse à des promesses multiples remplies

- -

L'exemple ci-dessus nous a montré certaines des bases réelles de l'utilisation des promesses. Voyons maintenant quelques fonctionnalités plus avancées. Pour commencer, enchaîner des processus pour qu'ils se réalisent l'un après l'autre, c'est très bien, mais que faire si vous voulez exécuter du code seulement après que tout un tas de promesses aient toutes été remplies ?

- -

Vous pouvez le faire avec la méthode statique ingénieusement nommée Promise.all(). Celle-ci prend un tableau de promesses comme paramètre d'entrée et retourne un nouvel objet Promise qui ne se réalisera que si et quand toutes les promesses du tableau se réaliseront. Cela ressemble à quelque chose comme ceci :

- -
Promise.all([a, b, c]).then(values => {
-  ...
-});
- -

Si elles se réalisent toutes, la fonction de callback du bloc .then() enchaîné se verra passer un tableau contenant tous ces résultats en paramètre. Si l'une des promesses passées à Promise.all() rejette, le bloc entier sera rejeté.

- -

Cela peut être très utile. Imaginez que nous récupérions des informations pour alimenter dynamiquement en contenu une fonction de l'interface utilisateur de notre page. Dans de nombreux cas, il est préférable de recevoir toutes les données et de n'afficher que le contenu complet, plutôt que d'afficher des informations partielles.

- -

Construisons un autre exemple pour montrer cela en action.

- -
    -
  1. -

    Téléchargez une nouvelle copie de notre modèle de page, et mettez à nouveau un élément <script> juste avant la balise de fermeture </body>.

    -
  2. -
  3. -

    Téléchargez nos fichiers sources (coffee.jpg, tea.jpg, et description.txt), ou n'hésitez pas à y substituer les vôtres.

    -
  4. -
  5. -

    Dans notre script, nous allons d'abord définir une fonction qui retourne les promesses que nous voulons envoyer à Promise.all(). Cela serait facile si nous voulions simplement exécuter le bloc Promise.all() en réponse à trois opérations fetch() qui se terminent. Nous pourrions simplement faire quelque chose comme :

    -
    let a = fetch(url1);
    -let b = fetch(url2);
    -let c = fetch(url3);
    -
    -Promise.all([a, b, c]).then(values => {
    -  ...
    -});
    -

    Lorsque la promesse est réalisée, les values (valeurs) passées dans le gestionnaire de réalisation contiendraient trois objets Response, un pour chacune des opérations fetch() qui se sont terminées.

    -

    Cependant, nous ne voulons pas faire cela. Notre code ne se soucie pas de savoir quand les opérations fetch() sont effectuées. Au lieu de cela, ce que nous voulons, ce sont les données chargées. Cela signifie que nous voulons exécuter le bloc Promise.all() lorsque nous récupérons des blobs utilisables représentant les images, et une chaîne de texte utilisable. Nous pouvons écrire une fonction qui fait cela ; ajoutez ce qui suit à l'intérieur de votre élément <script> :

    -
    function fetchAndDecode(url, type) {
    -  return fetch(url).then(response => {
    -    if(!response.ok) {
    -      throw new Error(`Erreur HTTP ! statut : ${response.status}`);
    -    } else {
    -      if(type === 'blob') {
    -        return response.blob();
    -      } else if(type === 'text') {
    -        return response.text();
    -      }
    -    }
    -  })
    -  .catch(e => {
    -    console.log(
    -      `Il y a eu un problème avec votre opération de récupération de la ressource "${url}" : ` + e.message);
    -  });
    -}
    -

    Cela semble un peu complexe, alors nous allons le faire étape par étape :

    -
      -
    1. Tout d'abord, nous définissons la fonction, en lui passant une URL et une chaîne représentant le type de ressource qu'elle va chercher.
    2. -
    3. À l'intérieur du corps de la fonction, nous avons une structure similaire à celle que nous avons vue dans le premier exemple — nous appelons la fonction fetch() pour récupérer la ressource à l'URL spécifiée, puis nous l'enchaînons sur une autre promesse qui renvoie le corps de réponse décodé (ou « lu »). Il s'agissait toujours de la méthode blob() dans l'exemple précédent.
    4. -
    5. Cependant, deux choses sont différentes ici : -
        -
      • Tout d'abord, la deuxième promesse que nous retournons est différente en fonction de la valeur type. À l'intérieur de la fonction de rappel .then(), nous incluons une simple déclaration if ... else if pour retourner une promesse différente selon le type de fichier que nous devons décoder (dans ce cas, nous avons le choix entre blob et text, mais il serait facile d'étendre cela pour traiter d'autres types également).
      • -
      • Deuxièmement, nous avons ajouté le mot-clé return avant l'appel fetch(). Cela a pour effet d'exécuter toute la chaîne, puis d'exécuter le résultat final (c'est-à-dire la promesse retournée par blob() ou text()) comme valeur de retour de la fonction que nous venons de définir. En effet, les instructions return font remonter les résultats au sommet de la chaîne.
      • -
      -
    6. -
    7. -

      À la fin du bloc, nous enchaînons sur un appel .catch(), pour gérer les cas d'erreur qui peuvent survenir avec l'une des promesses passées dans le tableau à .all(). Si l'une des promesses est rejetée, le bloc .catch() vous fera savoir laquelle avait un problème. Le bloc .all() (voir ci-dessous) s'exécutera quand même, mais il n'affichera pas les ressources qui ont eu des problèmes. Rappelez-vous que, une fois que vous avez traité la promesse avec un bloc .catch(), la promesse résultante est considérée comme résolue mais avec une valeur de undefined ; c'est pourquoi, dans ce cas, le bloc .all() sera toujours rempli. Si vous vouliez que le bloc .all() rejette, vous devriez plutôt enchaîner le bloc .catch() à la fin du .all().

      -
    8. -
    -

    Le code à l'intérieur du corps de la fonction est asynchrone et basé sur une promesse, donc en fait, la fonction entière agit comme une promesse — pratique.

    -
  6. -
  7. -

    Ensuite, nous appelons notre fonction trois fois pour commencer le processus de récupération et de décodage des images et du texte et nous stockons chacune des promesses retournées dans une variable. Ajoutez le texte suivant sous votre code précédent :

    -
    let coffee = fetchAndDecode('coffee.jpg', 'blob');
    -let tea = fetchAndDecode('tea.jpg', 'blob');
    -let description = fetchAndDecode('description.txt', 'text');
    -
  8. -
  9. -

    Ensuite, nous allons définir un bloc Promesse.all() pour exécuter un certain code uniquement lorsque les trois promesses stockées ci-dessus se sont réalisées avec succès. Pour commencer, ajoutez un bloc avec une fonction de rappel vide à l'intérieur de l'appel .then(), comme ceci :

    -
    Promise.all([coffee, tea, description]).then(values => {
    -
    -});
    -

    Vous pouvez voir qu'elle prend un tableau contenant les promesses comme paramètre. La fonction de rappel .then() ne sera exécutée que lorsque les trois promesses seront résolues ; lorsque cela se produira, on lui transmettra un tableau contenant les résultats des promesses individuelles (c'est-à-dire les corps de réponse décodés), un peu comme suit [coffee-results, tea-results, description-results].

    -
  10. -
  11. -

    Enfin, ajoutez ce qui suit à l'intérieur de la fonction de rappel. Nous utilisons ici un code de synchronisation assez simple pour stocker les résultats dans des variables séparées (en créant des URL d'objets à partir des blobs), puis nous affichons les images et le texte sur la page.

    -
    console.log(values);
    -// Stocke chaque valeur renvoyée par les promesses dans
    -// des variables distinctes ; crée des URL d'objets à partir des blobs.
    -let objectURL1 = URL.createObjectURL(values[0]);
    -let objectURL2 = URL.createObjectURL(values[1]);
    -let descText = values[2];
    -
    -// Affiche les images dans les éléments <img>
    -let image1 = document.createElement('img');
    -let image2 = document.createElement('img');
    -image1.src = objectURL1;
    -image2.src = objectURL2;
    -document.body.appendChild(image1);
    -document.body.appendChild(image2);
    -
    -// Affiche le texte d'un paragraphe
    -let para = document.createElement('p');
    -para.textContent = descText;
    -document.body.appendChild(para);
    -
  12. -
  13. -

    Sauvegardez et actualisez et vous devriez voir vos composants d'interface utilisateur chargés, bien que d'une manière peu attrayante !

    -
  14. -
- -

Le code que nous avons fourni ici pour l'affichage des articles est assez rudimentaire, mais il fonctionne comme une explication pour le moment.

- -
-

Note : Si vous êtes bloqué, vous pouvez comparer votre version du code à la nôtre, pour voir à quoi elle est censée ressembler - voir en direct, et voir le code source.

-
- -
-

Note : Si vous amélioriez ce code, vous pourriez vouloir boucler sur une liste d'éléments à afficher, en récupérant et en décodant chacun d'eux, puis boucler sur les résultats à l'intérieur de Promise.all(), en exécutant une fonction différente pour afficher chacun d'eux en fonction du type de code. Cela permettrait de fonctionner pour n'importe quel nombre d'éléments, pas seulement trois.

-

De plus, vous pourriez déterminer quel est le type de fichier récupéré sans avoir besoin d'une propriété type explicite. Vous pourriez, par exemple, vérifier l'en-tête HTTP Content-Type de la réponse dans chaque cas en utilisant response.headers.get("content-type"), puis agir en conséquence.

-
- -

Exécution d'un code final après l'accomplissement/le rejet d'une promesse.

- -

Il y aura des cas où vous voudrez exécuter un bloc de code final après la fin d'une promesse, qu'elle se soit réalisée ou rejetée. Auparavant, vous deviez inclure le même code dans les deux fonctions de rappel .then() et .catch(), par exemple :

- -
myPromise
-.then(response => {
-  doSomething(response);
-  runFinalCode();
-})
-.catch(e => {
-  returnError(e);
-  runFinalCode();
-});
- -

Dans les navigateurs modernes plus récents, la méthode .finally() est disponible, et peut être enchaînée à la fin de votre chaîne de promesses régulière, ce qui vous permet de réduire les répétitions de code et de faire les choses de manière plus élégante. Le code ci-dessus peut maintenant être écrit comme suit :

- -
myPromise
-.then(response => {
-  doSomething(response);
-})
-.catch(e => {
-  returnError(e);
-})
-.finally(() => {
-  runFinalCode();
-});
- -

Pour un exemple réel, jetez un œil à notre démonstration promesse-finally.html (voir le code source). Cela fonctionne de la même manière que la démo Promesse.all() que nous avons examinée dans la section ci-dessus, sauf que dans la fonction fetchAndDecode(), nous enchaînons un appel finally() à la fin de la chaîne :

- -
function fetchAndDecode(url, type) {
-  return fetch(url).then(response => {
-    if(!response.ok) {
-      throw new Error(`Erreur HTTP ! statut : ${response.status}`);
-    } else {
-      if(type === 'blob') {
-        return response.blob();
-      } else if(type === 'text') {
-        return response.text();
-      }
-    }
-  })
-  .catch(e => {
-    console.log(`Il y a eu un problème avec votre opération de récupération de la ressource "${url}" : ` + e.message);
-  })
-  .finally(() => {
-    console.log(`La tentative de récupération de "${url}" est terminée.`);
-  });
-}
- -

Cela permet d'envoyer un simple message à la console pour nous dire quand chaque tentative de récupération est terminée.

- -
-

Note : then()/catch()/finally() est l'équivalent asynchrone de try/catch/finally en code synchrone.

-
- -

Construire vos propres promesses personnalisées

- -

La bonne nouvelle est que, d'une certaine manière, vous avez déjà construit vos propres promesses. Lorsque vous avez enchaîné plusieurs promesses avec des blocs .then(), ou que vous les avez autrement combinées pour créer une fonctionnalité personnalisée, vous créez déjà vos propres fonctions asynchrones personnalisées basées sur des promesses. Prenez notre fonction fetchAndDecode() des exemples précédents, par exemple.

- -

La combinaison de différentes API basées sur les promesses pour créer des fonctionnalités personnalisées est de loin la façon la plus courante dont vous ferez des choses personnalisées avec les promesses, et montre la flexibilité et la puissance de la base de la plupart des API modernes autour du même principe. Il existe toutefois un autre moyen.

- -

Utilisation du constructeur Promise()

- -

Il est possible de construire vos propres promesses en utilisant le constructeur Promise(). La principale situation dans laquelle vous voudrez faire cela est lorsque vous avez du code basé sur une API asynchrone de la vieille école qui n'est pas basée sur les promesses, et que vous voulez promettre. Cela s'avère pratique lorsque vous avez besoin d'utiliser du code, des bibliothèques ou des frameworks existants, plus anciens, avec du code moderne basé sur les promesses.

- -

Examinons un exemple simple pour vous aider à démarrer — ici, nous enveloppons un appel setTimeout() avec une promesse — cela exécute une fonction après deux secondes qui résout la promesse (en passant l'appel resolve()) avec une chaîne de caractères de "Succès ! ".

- -
let timeoutPromise = new Promise((resolve, reject) => {
-  setTimeout(() => {
-    resolve('Succès !');
-  }, 2000);
-});
- -

resolve() et reject() sont des fonctions que vous appelez pour réaliser ou rejeter la promesse nouvellement créée. Dans ce cas, la promesse se réalise avec une chaîne de caractères "Succès !".

- -

Ainsi, lorsque vous appelez cette promesse, vous pouvez enchaîner un bloc .then() à la fin de celle-ci et on lui passera une chaîne de caractères "Succès !". Dans le code ci-dessous, nous alertons ce message :

- -
timeoutPromise
-.then((message) => {
-  alert(message);
-})
- -

ou même simplement

- -
timeoutPromise.then(alert);
- -

Essayez de l'exécuter en direct pour voir le résultat (voir aussi le code source).

- -

L'exemple ci-dessus n'est pas très flexible - la promesse ne peut jamais s'accomplir qu'avec une seule chaîne de caractères, et elle n'a aucune sorte de condition reject() spécifiée (il est vrai que setTimeout() n'a pas vraiment de condition d'échec, donc cela n'a pas d'importance pour cet exemple).

- -
-

Note : Pourquoi resolve(), et pas fulfill() ? La réponse que nous vous donnerons, pour l'instant, est c'est compliqué.

-
- -

Rejeter une promesse personnalisée

- -

Nous pouvons créer une promesse rejetée à l'aide de la méthode reject() — tout comme resolve(), celle-ci prend une seule valeur, mais dans ce cas, c'est la raison du rejet, c'est-à-dire l'erreur qui sera transmise dans le bloc .catch().

- -

Étendons l'exemple précédent pour avoir quelques conditions reject() ainsi que pour permettre de transmettre différents messages en cas de succès.

- -

Prenez une copie de l'exemple précédent, et remplacez la définition de timeoutPromise() existante par celle-ci :

- -
function timeoutPromise(message, interval) {
-  return new Promise((resolve, reject) => {
-    if (message === '' || typeof message !== 'string') {
-      reject('Le message est vide ou n'est pas une chaîne de caractères');
-    } else if (interval < 0 || typeof interval !== 'number') {
-      reject(`L'intervalle est négatif ou n'est pas un nombre`);
-    } else {
-      setTimeout(() => {
-        resolve(message);
-      }, interval);
-    }
-  });
-};
- -

Ici, nous passons deux arguments dans une fonction personnalisée - un message pour faire quelque chose avec, et l'intervalle de temps à passer avant de faire la chose. À l'intérieur de la fonction, nous renvoyons un nouvel objet Promise — l'invocation de la fonction renverra la promesse que nous voulons utiliser.

- -

À l'intérieur du constructeur Promise, nous effectuons plusieurs vérifications dans des structures if ... else :

- -
    -
  1. Tout d'abord, nous vérifions si le message est approprié pour être alerté. Si c'est une chaîne de caractères vide ou pas une chaîne de caractères du tout, nous rejetons la promesse avec un message d'erreur approprié.
  2. -
  3. Ensuite, nous vérifions si l'intervalle est une valeur d'intervalle appropriée. S'il est négatif ou n'est pas un nombre, nous rejetons la promesse avec un message d'erreur approprié.
  4. -
  5. Enfin, si les paramètres semblent tous deux OK, nous résolvons la promesse avec le message spécifié après que l'intervalle spécifié se soit écoulé en utilisant setTimeout().
  6. -
- -

Puisque la fonction timeoutPromise() renvoie un objet Promise, nous pouvons enchaîner .then(), .catch(), etc. sur elle pour faire usage de ses fonctionnalités. Utilisons-le maintenant - remplaçons l'utilisation précédente de timeoutPromise par celle-ci :

- -
timeoutPromise('Bonjour à tous !', 1000)
-.then(message => {
-   alert(message);
-})
-.catch(e => {
-  console.log('Erreur : ' + e);
-});
- -

Lorsque vous enregistrez et exécutez le code tel quel, après une seconde, vous obtiendrez le message d'alerte. Essayez maintenant de définir le message sur une chaîne vide ou l'intervalle sur un nombre négatif, par exemple, et vous pourrez voir la promesse rejetée avec les messages d'erreur appropriés ! Vous pouvez également essayer de faire autre chose avec le message résolu plutôt que de simplement créer une alerte.

- -
-

Note : Vous pouvez trouver notre version de cet exemple sur GitHub sur custom-promise2.html (voir aussi le code source).

-
- -

Un exemple plus concret

- -

L'exemple ci-dessus est resté volontairement simple pour rendre les concepts faciles à comprendre, mais il n'est pas vraiment très asynchrone. La nature asynchrone est essentiellement feinte en utilisant setTimeout(), bien qu'il montre quand même que les promesses sont utiles pour créer une fonction personnalisée avec un flux d'opérations raisonnable, une bonne gestion des erreurs, etc.

- -

Un exemple que nous aimerions vous inviter à étudier, qui montre effectivement une application asynchrone utile du constructeur Promise(), est la bibliothèque idb de Jake Archibald. Celle-ci prend l'API IndexedDB API, qui est une API à l'ancienne basée sur des callbacks pour stocker et récupérer des données côté client, et vous permet de l'utiliser avec des promesses. Dans le code, vous verrez que le même type de techniques que nous avons évoqué plus haut est utilisé. Le bloc suivant convertit le modèle de requête de base utilisé par de nombreuses méthodes IndexedDB pour utiliser des promesses (voir ce code, par exemple).

- -

Conclusion

- -

Les promesses sont un bon moyen de construire des applications asynchrones lorsque nous ne connaissons pas la valeur de retour d'une fonction ou le temps qu'elle prendra pour retourner une réponse. Elles permettent d'exprimer et de raisonner plus facilement sur des séquences d'opérations asynchrones sans callbacks profondément imbriqués, et elles supportent un style de gestion des erreurs qui est similaire à l'instruction synchrone try...catch.

- -

Les promesses fonctionnent dans les dernières versions de tous les navigateurs modernes ; le seul endroit où la prise en charge des promesses posera problème est Opera Mini et IE11 et les versions antérieures.

- -

Nous n'avons pas abordé toutes les fonctionnalités des promesses dans cet article, mais seulement les plus intéressantes et les plus utiles. Au fur et à mesure que vous vous familiariserez avec les promesses, vous découvrirez d'autres fonctionnalités et techniques.

- -

La plupart des API Web modernes sont basées sur des promesses, vous devrez donc comprendre les promesses pour en tirer le meilleur parti. Parmi ces API, citons WebRTC, Web Audio API, MediaStream API, et bien d'autres encore. Les promesses seront de plus en plus importantes au fil du temps, donc apprendre à les utiliser et à les comprendre est une étape importante dans l'apprentissage du JavaScript moderne.

- -

Voir aussi

- - - -

{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Timeouts_and_intervals", "Learn/JavaScript/Asynchronous/Async_await", "Learn/JavaScript/Asynchronous")}}

- -

Dans ce module

- - diff --git a/files/fr/learn/javascript/asynchronous/promises/index.md b/files/fr/learn/javascript/asynchronous/promises/index.md new file mode 100644 index 0000000000..69f6de6a21 --- /dev/null +++ b/files/fr/learn/javascript/asynchronous/promises/index.md @@ -0,0 +1,576 @@ +--- +title: Gérer les opérations asynchrones avec élégance grâce aux promesses +slug: Learn/JavaScript/Asynchronous/Promises +tags: + - Beginner + - CodingScripting + - Guide + - JavaScript + - Learn + - Promises + - async + - asynchronous + - catch + - finally + - then +translation_of: Learn/JavaScript/Asynchronous/Promises +--- +
{{LearnSidebar}}
+ +
{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Timeouts_and_intervals", "Learn/JavaScript/Asynchronous/Async_await", "Learn/JavaScript/Asynchronous")}}
+ +

Les promesses sont une fonctionnalité relativement nouvelle du langage JavaScript qui vous permet de reporter d'autres actions jusqu'à ce qu'une action précédente soit terminée, ou de répondre à son échec. Ceci est utile pour mettre en place une séquence d'opérations asynchrones afin qu'elles fonctionnent correctement. Cet article vous montre comment les promesses fonctionnent, comment vous les verrez utilisées avec les API Web, et comment écrire les vôtres.

+ + + + + + + + + + + + +
Prérequis :Connaissances informatiques de base, compréhension raisonnable des principes fondamentaux de JavaScript.
Objectif :Comprendre les promesses et savoir comment les utiliser.
+ +

Que sont les promesses ?

+ +

Nous avons examiné les promesses (Promise) brièvement dans le premier article du cours, mais ici nous allons les examiner de manière beaucoup plus approfondie.

+ +

Essentiellement, une promesse est un objet qui représente un état intermédiaire d'une opération - en fait, c'est une promesse qu'un résultat d'une certaine nature sera retourné à un moment donné dans le futur. Il n'y a aucune garantie du moment exact où l'opération se terminera et où le résultat sera renvoyé, mais il est une garantie que lorsque le résultat est disponible, ou que la promesse échoue, le code que vous fournissez sera exécuté afin de faire autre chose avec un résultat réussi, ou de gérer gracieusement un cas d'échec.

+ +

En général, vous êtes moins intéressé par le temps qu'une opération asynchrone prendra pour renvoyer son résultat (à moins bien sûr qu'elle ne prenne beaucoup trop de temps !), et plus intéressé par le fait de pouvoir répondre à son retour, quel que soit le moment. Et bien sûr, il est agréable que cela ne bloque pas le reste de l'exécution du code.

+ +

L'une des utilisations les plus courantes des promesses concerne les API web qui renvoient une promesse. Considérons une hypothétique application de chat vidéo. L'application dispose d'une fenêtre contenant une liste des amis de l'utilisateur, et un clic sur un bouton à côté d'un utilisateur lance un appel vidéo vers cet utilisateur.

+ +

Le gestionnaire de ce bouton appelle getUserMedia() afin d'avoir accès à la caméra et au microphone de l'utilisateur. Puisque getUserMedia() doit s'assurer que l'utilisateur a la permission d'utiliser ces dispositifs et lui demander quel microphone utiliser et quelle caméra utiliser (ou s'il s'agit d'un appel vocal uniquement, parmi d'autres options possibles), il peut bloquer jusqu'à ce que non seulement toutes ces décisions soient prises, mais aussi que la caméra et le microphone soient activés. En outre, l'utilisateur peut ne pas répondre immédiatement à ces demandes d'autorisation. Cela peut potentiellement prendre beaucoup de temps.

+ +

Puisque l'appel à getUserMedia() est effectué depuis le processus principal du navigateur, l'ensemble du navigateur est bloqué jusqu'à ce que getUserMedia() retourne une réponse ! Évidemment, ce n'est pas une option viable ; sans les promesses, tout dans le navigateur devient inutilisable jusqu'à ce que l'utilisateur décide ce qu'il faut faire de la caméra et du microphone. Ainsi, au lieu d'attendre l'utilisateur, d'obtenir l'activation des périphériques choisis et de retourner directement le MediaStream pour le flux créé à partir des sources sélectionnées, getUserMedia() retourne une promesse qui est résolue avec le MediaStream une fois qu'il est disponible.

+ +

Le code qu'utiliserait l'application de chat vidéo pourrait ressembler à ceci :

+ +
function handleCallButton(evt) {
+  setStatusMessage("Appel...");
+  navigator.mediaDevices.getUserMedia({video: true, audio: true})
+    .then(chatStream => {
+      selfViewElem.srcObject = chatStream;
+      chatStream.getTracks().forEach(track => myPeerConnection.addTrack(track, chatStream));
+      setStatusMessage("Connecté");
+    }).catch(err => {
+      setStatusMessage("Échec de la connexion");
+    });
+}
+
+ +

Cette fonction commence par utiliser une fonction appelée setStatusMessage() pour mettre à jour un affichage d'état avec le message "Appel...", indiquant qu'un appel est tenté.Il appelle ensuite getUserMedia(), demandant un flux qui a à la fois des pistes vidéo et audio, puis une fois que cela a été obtenu, configure un élément vidéo pour montrer le flux provenant de la caméra comme une "vue de soi", puis prend chacune des pistes du flux et les ajoute à la RTCPeerConnection WebRTC représentant une connexion à un autre utilisateur. Après cela, l'affichage de l'état est mis à jour pour indiquer "Connecté".

+ +

Si getUserMedia() échoue, le bloc catch s'exécute. Celui-ci utilise setStatusMessage() pour mettre à jour la case d'état afin d'indiquer qu'une erreur s'est produite.

+ +

La chose importante ici est que l'appel getUserMedia() revient presque immédiatement, même si le flux de la caméra n'a pas encore été obtenu. Même si la fonction handleCallButton() est déjà retournée au code qui l'a appelée, lorsque getUserMedia() a fini de travailler, elle appelle le gestionnaire que vous fournissez. Tant que l'application ne suppose pas que le flux a commencé, elle peut continuer à fonctionner.

+ +
+

Note : Vous pouvez en apprendre davantage sur ce sujet quelque peu avancé, si cela vous intéresse, dans l'article L'essentiel du WebRTC. Un code similaire à celui-ci, mais beaucoup plus complet, est utilisé dans cet exemple.

+
+ +

Le problème des fonctions de rappel

+ +

Pour bien comprendre pourquoi les promesses sont une bonne chose, il est utile de repenser aux anciennes fonctions de rappel (callback) et de comprendre pourquoi elles sont problématiques.

+ +

Prenons l'exemple de la commande d'une pizza. Il y a certaines étapes que vous devez franchir pour que votre commande soit réussie, et cela n'a pas vraiment de sens d'essayer de les exécuter dans le désordre, ou dans l'ordre mais avant que chaque étape précédente ne soit tout à fait terminée :

+ +
    +
  1. Vous choisissez les garnitures que vous voulez. Cela peut prendre un certain temps si vous êtes indécis, et peut échouer si vous n'arrivez pas à vous décider, ou si vous décidez de prendre un curry à la place.
  2. +
  3. Vous passez ensuite votre commande. Le retour d'une pizza peut prendre un certain temps et peut échouer si le restaurant ne dispose pas des ingrédients nécessaires à sa cuisson.
  4. +
  5. Vous récupérez ensuite votre pizza et la mangez. Cela peut échouer si, par exemple, vous avez oublié votre portefeuille et ne pouvez pas payer la pizza !
  6. +
+ +

Avec l'ancien modèle de rappels, une représentation en pseudo-code de la fonctionnalité ci-dessus pourrait ressembler à quelque chose comme ceci :

+ +
chooseToppings(function(toppings) {
+  placeOrder(toppings, function(order) {
+    collectOrder(order, function(pizza) {
+      eatPizza(pizza);
+    }, failureCallback);
+  }, failureCallback);
+}, failureCallback);
+ +

Cela est désordonné et difficile à lire (souvent appelé « callback hell »), nécessite que le failureCallback() soit appelé plusieurs fois (une fois pour chaque fonction imbriquée), avec d'autres problèmes en plus.

+ +

Améliorations avec des promesses

+ +

Les promesses facilitent grandement l'écriture, l'analyse et l'exécution de situations telles que celle décrite ci-dessus. Si nous avions représenté le pseudo-code ci-dessus en utilisant des promesses asynchrones à la place, nous aurions obtenu quelque chose comme ceci :

+ +
chooseToppings()
+.then(function(toppings) {
+  return placeOrder(toppings);
+})
+.then(function(order) {
+  return collectOrder(order);
+})
+.then(function(pizza) {
+  eatPizza(pizza);
+})
+.catch(failureCallback);
+ +

C'est bien mieux - il est plus facile de voir ce qui se passe, nous n'avons besoin que d'un seul bloc .catch() pour gérer toutes les erreurs, cela ne bloque pas le processus principal (nous pouvons donc continuer à jouer à des jeux vidéo en attendant que la pizza soit prête à être collectée), et chaque opération a la garantie d'attendre que les opérations précédentes soient terminées avant de s'exécuter. Nous sommes en mesure d'enchaîner plusieurs actions asynchrones pour qu'elles se produisent les unes après les autres de cette façon, car chaque bloc .then() renvoie une nouvelle promesse qui se résout lorsque le bloc .then() a fini de s'exécuter. Astucieux, non ?

+ +

En utilisant les fonctions flèches, vous pouvez simplifier encore plus le code :

+ +
chooseToppings()
+.then(toppings =>
+  placeOrder(toppings)
+)
+.then(order =>
+  collectOrder(order)
+)
+.then(pizza =>
+  eatPizza(pizza)
+)
+.catch(failureCallback);
+ +

Ou encore ça :

+ +
chooseToppings()
+.then(toppings => placeOrder(toppings))
+.then(order => collectOrder(order))
+.then(pizza => eatPizza(pizza))
+.catch(failureCallback);
+ +

Cela fonctionne car avec les fonctions flèches () => x est un raccourci valide pour () => { return x ; }.

+ +

Vous pourriez même le faire ainsi, puisque les fonctions ne font que passer leurs arguments directement, et qu'il n'y a donc pas besoin de cette couche supplémentaire de fonctions :

+ +
chooseToppings().then(placeOrder).then(collectOrder).then(eatPizza).catch(failureCallback);
+ +

Cependant, la lecture n'est pas aussi facile et cette syntaxe peut ne pas être utilisable si vos blocs sont plus complexes que ce que nous avons montré ici.

+ +
+

Note : Vous pouvez apporter d'autres améliorations avec la syntaxe async/await, que nous aborderons dans le prochain article.

+
+ +

Dans leur forme la plus basique, les promesses sont similaires aux écouteurs d'événements, mais avec quelques différences :

+ + + +

Explication de la syntaxe de base des promesses : exemple concret

+ +

Il est important de comprendre les promesses, car la plupart des API Web modernes les utilisent pour les fonctions qui exécutent des tâches potentiellement longues. Pour utiliser les technologies Web modernes, vous devrez utiliser des promesses. Plus loin dans ce chapitre, nous verrons comment écrire votre propre promesse, mais pour l'instant, nous allons nous pencher sur quelques exemples simples que vous rencontrerez dans les API Web.

+ +

Dans le premier exemple, nous allons utiliser la méthode fetch() pour récupérer une image sur le web, la méthode blob() pour transformer le contenu brut du corps de la réponse fetch en un objet Blob, puis afficher ce blob à l'intérieur d'un élément <img>. Cet exemple est très similaire à celui que nous avons examiné dans le premier article, mais nous le ferons un peu différemment au fur et à mesure que nous vous ferons construire votre propre code basé sur des promesses.

+ +
+

Note : L'exemple suivant ne fonctionnera pas si vous l'exécutez directement à partir du fichier (c'est-à-dire via une URL file://). Vous devez l'exécuter via un serveur de test local, ou utiliser une solution en ligne telle que Glitch ou les pages GitHub.

+
+ +
    +
  1. +

    Tout d'abord, téléchargez notre modèle HTML simple et le fichier image que nous allons récupérer.

    +
  2. +
  3. +

    Ajoutez un élément <script> au bas de l'élément HTML <body>.

    +
  4. +
  5. +

    À l'intérieur de votre élément <script>, ajoutez la ligne suivante :

    +
    let promise = fetch('coffee.jpg');
    +

    Cela appelle la méthode fetch(), en lui passant en paramètre l'URL de l'image à récupérer sur le réseau. Cette méthode peut également prendre un objet d'options comme second paramètre facultatif, mais nous n'utilisons que la version la plus simple pour le moment. Nous stockons l'objet promesse retourné par fetch() à l'intérieur d'une variable appelée promise. Comme nous l'avons dit précédemment, cet objet représente un état intermédiaire qui n'est initialement ni un succès ni un échec - le terme officiel pour une promesse dans cet état est en attente (pending en anglais).

    +
  6. +
  7. +

    Pour répondre à l'achèvement réussi de l'opération lorsque cela se produit (dans ce cas, lorsqu'une réponse est retournée), nous invoquons la méthode .then() de l'objet promesse. La fonction de rappel à l'intérieur du bloc .then() s'exécute uniquement lorsque l'appel de la promesse se termine avec succès et retourne l'objet Response — en langage de promesse, lorsqu'elle a été remplie (fullfilled en anglais). On lui passe l'objet Response retourné en tant que paramètre.

    +
    +

    Note : Le fonctionnement d'un bloc .then() est similaire à celui de l'ajout d'un écouteur d'événements à un objet à l'aide de AddEventListener(). Il ne s'exécute pas avant qu'un événement ne se produise (lorsque la promesse se réalise). La différence la plus notable est qu'un .then() ne s'exécutera qu'une fois à chaque fois qu'il sera utilisé, alors qu'un écouteur d'événements pourrait être invoqué plusieurs fois.

    +
    +

    Nous exécutons immédiatement la méthode blob() sur cette réponse pour nous assurer que le corps de la réponse est entièrement téléchargé, et lorsqu'il est disponible, le transformer en un objet Blob avec lequel nous pouvons faire quelque chose. Le résultat de cette méthode est retourné comme suit :

    +
    response => response.blob()
    +

    qui est un raccourci de

    +
    function(response) {
    +  return response.blob();
    +}
    +

    Malheureusement, nous devons faire un peu plus que cela. Les promesses de récupération n'échouent pas sur les erreurs 404 ou 500 - seulement sur quelque chose de catastrophique comme une panne de réseau. Au lieu de cela, elles réussissent, mais avec la propriété response.ok définie à false. Pour produire une erreur sur un 404, par exemple, nous devons vérifier la valeur de response.ok, et si c'est false, lancer une erreur, ne renvoyant le blob que si elle est à true. Cela peut être fait comme suit - ajoutez les lignes suivantes sous votre première ligne de JavaScript.

    +
    let promise2 = promise.then(response => {
    +  if (!response.ok) {
    +    throw new Error(`erreur HTTP ! statut : ${response.status}`);
    +  } else {
    +    return response.blob();
    +  }
    +});
    +
  8. +
  9. +

    Chaque appel à la méthode .then() crée une nouvelle promesse. Ceci est très utile ; parce que la méthode blob() renvoie également une promesse, nous pouvons manipuler l'objet Blob qu'elle renvoie sur l'accomplissement en invoquant la méthode .then() de la seconde promesse. Parce que nous voulons faire quelque chose d'un peu plus complexe au blob que de simplement exécuter une seule méthode sur lui et renvoyer le résultat, nous devrons envelopper le corps de la fonction dans des accolades cette fois (sinon, ça lancera une erreur).

    +

    Ajoutez ce qui suit à la fin de votre code :

    +
    let promise3 = promise2.then(myBlob => {
    +})
    +
  10. +
  11. +

    Maintenant, remplissons le corps de la fonction de rappel .then(). Ajoutez les lignes suivantes à l'intérieur des accolades :

    +
    let objectURL = URL.createObjectURL(myBlob);
    +let image = document.createElement('img');
    +image.src = objectURL;
    +document.body.appendChild(image);
    +

    Nous exécutons ici la méthode URL.createObjectURL(), en lui passant en paramètre le Blob renvoyé lors de la réalisation de la deuxième promesse. Cela permettra de renvoyer une URL pointant vers l'objet. Ensuite, nous créons un élément <img>, définissons son attribut src comme étant égal à l'URL de l'objet et l'ajoutons au DOM, de sorte que l'image s'affiche sur la page !

    +
  12. +
+ +

Si vous enregistrez le fichier HTML que vous venez de créer et le chargez dans votre navigateur, vous verrez que l'image s'affiche dans la page comme prévu. Bon travail !

+ +
+

Note : Vous remarquerez probablement que ces exemples sont quelque peu artificiels. Vous pourriez tout simplement vous passer de toute la chaîne fetch() et blob(), et simplement créer un élément <img> et définir la valeur de son attribut src à l'URL du fichier image, coffee.jpg. Nous avons toutefois choisi cet exemple parce qu'il démontre les promesses d'une manière simple et agréable, plutôt que pour sa pertinence dans le monde réel.

+
+ +

Réagir à un échec

+ +

Il manque quelque chose — actuellement, il n'y a rien pour gérer explicitement les erreurs si l'une des promesses échoue (rejects, en anglais). Nous pouvons ajouter la gestion des erreurs en exécutant la méthode .catch() de la promesse précédente. Ajoutez ceci maintenant :

+ +
let errorCase = promise3.catch(e => {
+  console.log('Il y a eu un problème avec votre opération de récupération : ' + e.message);
+});
+ +

Pour voir cela en action, essayez de mal orthographier l'URL de l'image et de recharger la page. L'erreur sera signalée dans la console des outils de développement de votre navigateur.

+ +

Cela ne fait pas beaucoup plus que si vous ne preniez pas la peine d'inclure le bloc .catch() du tout, mais pensez-y - cela nous permet de contrôler la gestion des erreurs exactement comme nous le voulons. Dans une application réelle, votre bloc .catch() pourrait réessayer de récupérer l'image, ou afficher une image par défaut, ou demander à l'utilisateur de fournir une URL d'image différente, ou autre.

+ +
+

Note : Vous pouvez voir notre version de l'exemple en direct (voir également son code source).

+
+ +

Enchaîner les blocs

+ +

C'est une façon très manuscrite d'écrire cela ; nous l'avons délibérément fait pour vous aider à comprendre clairement ce qui se passe. Comme nous l'avons montré plus haut dans l'article, vous pouvez enchaîner les blocs .then() (et aussi les blocs .catch()). Le code ci-dessus pourrait aussi être écrit comme ceci (voir aussi simple-fetch-chained.html sur GitHub) :

+ +
fetch('coffee.jpg')
+.then(response => {
+  if (!response.ok) {
+    throw new Error(`Erreur HTTP ! statut : ${response.status}`);
+  } else {
+    return response.blob();
+  }
+})
+.then(myBlob => {
+  let objectURL = URL.createObjectURL(myBlob);
+  let image = document.createElement('img');
+  image.src = objectURL;
+  document.body.appendChild(image);
+})
+.catch(e => {
+  console.log('Il y a eu un problème avec votre opération de récupération : ' + e.message);
+});
+ +

Gardez à l'esprit que la valeur renvoyée par une promesse remplie devient le paramètre transmis à la fonction de rappel du bloc .then() suivant.

+ +
+

Note : Les blocs .then()/.catch() dans les promesses sont essentiellement l'équivalent asynchrone d'un bloc try...catch dans du code synchrone. Gardez à l'esprit que le try...catch synchrone ne fonctionnera pas dans du code asynchrone.

+
+ +

Récapitulatif de la terminologie des promesses

+ +

Il y avait beaucoup à couvrir dans la section ci-dessus, alors revenons-y rapidement pour vous donner un court guide que vous pouvez mettre en favoris et utiliser pour vous rafraîchir la mémoire à l'avenir. Vous devriez également revoir la section ci-dessus quelques fois de plus pour vous assurer que ces concepts sont bien assimilés.

+ +
    +
  1. Lorsqu'une promesse est créée, elle n'est ni dans un état de réussite ni dans un état d'échec. On dit qu'elle est en attente.
  2. +
  3. Lorsqu'une promesse est retournée, on dit qu'elle est résolue. +
      +
    1. Une promesse résolue avec succès est dite remplie. Elle retourne une valeur, à laquelle on peut accéder en enchaînant un bloc .then() à la fin de la chaîne de promesses. La fonction de rappel à l'intérieur du bloc .then() contiendra la valeur de retour de la promesse.
    2. +
    3. Une promesse résolue non aboutie est dite rejetée. Elle renvoie une raison, un message d'erreur indiquant pourquoi la promesse a été rejetée. On peut accéder à cette raison en enchaînant un bloc .catch() à la fin de la chaîne de promesses.
    4. +
    +
  4. +
+ +

Exécution du code en réponse à des promesses multiples remplies

+ +

L'exemple ci-dessus nous a montré certaines des bases réelles de l'utilisation des promesses. Voyons maintenant quelques fonctionnalités plus avancées. Pour commencer, enchaîner des processus pour qu'ils se réalisent l'un après l'autre, c'est très bien, mais que faire si vous voulez exécuter du code seulement après que tout un tas de promesses aient toutes été remplies ?

+ +

Vous pouvez le faire avec la méthode statique ingénieusement nommée Promise.all(). Celle-ci prend un tableau de promesses comme paramètre d'entrée et retourne un nouvel objet Promise qui ne se réalisera que si et quand toutes les promesses du tableau se réaliseront. Cela ressemble à quelque chose comme ceci :

+ +
Promise.all([a, b, c]).then(values => {
+  ...
+});
+ +

Si elles se réalisent toutes, la fonction de callback du bloc .then() enchaîné se verra passer un tableau contenant tous ces résultats en paramètre. Si l'une des promesses passées à Promise.all() rejette, le bloc entier sera rejeté.

+ +

Cela peut être très utile. Imaginez que nous récupérions des informations pour alimenter dynamiquement en contenu une fonction de l'interface utilisateur de notre page. Dans de nombreux cas, il est préférable de recevoir toutes les données et de n'afficher que le contenu complet, plutôt que d'afficher des informations partielles.

+ +

Construisons un autre exemple pour montrer cela en action.

+ +
    +
  1. +

    Téléchargez une nouvelle copie de notre modèle de page, et mettez à nouveau un élément <script> juste avant la balise de fermeture </body>.

    +
  2. +
  3. +

    Téléchargez nos fichiers sources (coffee.jpg, tea.jpg, et description.txt), ou n'hésitez pas à y substituer les vôtres.

    +
  4. +
  5. +

    Dans notre script, nous allons d'abord définir une fonction qui retourne les promesses que nous voulons envoyer à Promise.all(). Cela serait facile si nous voulions simplement exécuter le bloc Promise.all() en réponse à trois opérations fetch() qui se terminent. Nous pourrions simplement faire quelque chose comme :

    +
    let a = fetch(url1);
    +let b = fetch(url2);
    +let c = fetch(url3);
    +
    +Promise.all([a, b, c]).then(values => {
    +  ...
    +});
    +

    Lorsque la promesse est réalisée, les values (valeurs) passées dans le gestionnaire de réalisation contiendraient trois objets Response, un pour chacune des opérations fetch() qui se sont terminées.

    +

    Cependant, nous ne voulons pas faire cela. Notre code ne se soucie pas de savoir quand les opérations fetch() sont effectuées. Au lieu de cela, ce que nous voulons, ce sont les données chargées. Cela signifie que nous voulons exécuter le bloc Promise.all() lorsque nous récupérons des blobs utilisables représentant les images, et une chaîne de texte utilisable. Nous pouvons écrire une fonction qui fait cela ; ajoutez ce qui suit à l'intérieur de votre élément <script> :

    +
    function fetchAndDecode(url, type) {
    +  return fetch(url).then(response => {
    +    if(!response.ok) {
    +      throw new Error(`Erreur HTTP ! statut : ${response.status}`);
    +    } else {
    +      if(type === 'blob') {
    +        return response.blob();
    +      } else if(type === 'text') {
    +        return response.text();
    +      }
    +    }
    +  })
    +  .catch(e => {
    +    console.log(
    +      `Il y a eu un problème avec votre opération de récupération de la ressource "${url}" : ` + e.message);
    +  });
    +}
    +

    Cela semble un peu complexe, alors nous allons le faire étape par étape :

    +
      +
    1. Tout d'abord, nous définissons la fonction, en lui passant une URL et une chaîne représentant le type de ressource qu'elle va chercher.
    2. +
    3. À l'intérieur du corps de la fonction, nous avons une structure similaire à celle que nous avons vue dans le premier exemple — nous appelons la fonction fetch() pour récupérer la ressource à l'URL spécifiée, puis nous l'enchaînons sur une autre promesse qui renvoie le corps de réponse décodé (ou « lu »). Il s'agissait toujours de la méthode blob() dans l'exemple précédent.
    4. +
    5. Cependant, deux choses sont différentes ici : +
        +
      • Tout d'abord, la deuxième promesse que nous retournons est différente en fonction de la valeur type. À l'intérieur de la fonction de rappel .then(), nous incluons une simple déclaration if ... else if pour retourner une promesse différente selon le type de fichier que nous devons décoder (dans ce cas, nous avons le choix entre blob et text, mais il serait facile d'étendre cela pour traiter d'autres types également).
      • +
      • Deuxièmement, nous avons ajouté le mot-clé return avant l'appel fetch(). Cela a pour effet d'exécuter toute la chaîne, puis d'exécuter le résultat final (c'est-à-dire la promesse retournée par blob() ou text()) comme valeur de retour de la fonction que nous venons de définir. En effet, les instructions return font remonter les résultats au sommet de la chaîne.
      • +
      +
    6. +
    7. +

      À la fin du bloc, nous enchaînons sur un appel .catch(), pour gérer les cas d'erreur qui peuvent survenir avec l'une des promesses passées dans le tableau à .all(). Si l'une des promesses est rejetée, le bloc .catch() vous fera savoir laquelle avait un problème. Le bloc .all() (voir ci-dessous) s'exécutera quand même, mais il n'affichera pas les ressources qui ont eu des problèmes. Rappelez-vous que, une fois que vous avez traité la promesse avec un bloc .catch(), la promesse résultante est considérée comme résolue mais avec une valeur de undefined ; c'est pourquoi, dans ce cas, le bloc .all() sera toujours rempli. Si vous vouliez que le bloc .all() rejette, vous devriez plutôt enchaîner le bloc .catch() à la fin du .all().

      +
    8. +
    +

    Le code à l'intérieur du corps de la fonction est asynchrone et basé sur une promesse, donc en fait, la fonction entière agit comme une promesse — pratique.

    +
  6. +
  7. +

    Ensuite, nous appelons notre fonction trois fois pour commencer le processus de récupération et de décodage des images et du texte et nous stockons chacune des promesses retournées dans une variable. Ajoutez le texte suivant sous votre code précédent :

    +
    let coffee = fetchAndDecode('coffee.jpg', 'blob');
    +let tea = fetchAndDecode('tea.jpg', 'blob');
    +let description = fetchAndDecode('description.txt', 'text');
    +
  8. +
  9. +

    Ensuite, nous allons définir un bloc Promesse.all() pour exécuter un certain code uniquement lorsque les trois promesses stockées ci-dessus se sont réalisées avec succès. Pour commencer, ajoutez un bloc avec une fonction de rappel vide à l'intérieur de l'appel .then(), comme ceci :

    +
    Promise.all([coffee, tea, description]).then(values => {
    +
    +});
    +

    Vous pouvez voir qu'elle prend un tableau contenant les promesses comme paramètre. La fonction de rappel .then() ne sera exécutée que lorsque les trois promesses seront résolues ; lorsque cela se produira, on lui transmettra un tableau contenant les résultats des promesses individuelles (c'est-à-dire les corps de réponse décodés), un peu comme suit [coffee-results, tea-results, description-results].

    +
  10. +
  11. +

    Enfin, ajoutez ce qui suit à l'intérieur de la fonction de rappel. Nous utilisons ici un code de synchronisation assez simple pour stocker les résultats dans des variables séparées (en créant des URL d'objets à partir des blobs), puis nous affichons les images et le texte sur la page.

    +
    console.log(values);
    +// Stocke chaque valeur renvoyée par les promesses dans
    +// des variables distinctes ; crée des URL d'objets à partir des blobs.
    +let objectURL1 = URL.createObjectURL(values[0]);
    +let objectURL2 = URL.createObjectURL(values[1]);
    +let descText = values[2];
    +
    +// Affiche les images dans les éléments <img>
    +let image1 = document.createElement('img');
    +let image2 = document.createElement('img');
    +image1.src = objectURL1;
    +image2.src = objectURL2;
    +document.body.appendChild(image1);
    +document.body.appendChild(image2);
    +
    +// Affiche le texte d'un paragraphe
    +let para = document.createElement('p');
    +para.textContent = descText;
    +document.body.appendChild(para);
    +
  12. +
  13. +

    Sauvegardez et actualisez et vous devriez voir vos composants d'interface utilisateur chargés, bien que d'une manière peu attrayante !

    +
  14. +
+ +

Le code que nous avons fourni ici pour l'affichage des articles est assez rudimentaire, mais il fonctionne comme une explication pour le moment.

+ +
+

Note : Si vous êtes bloqué, vous pouvez comparer votre version du code à la nôtre, pour voir à quoi elle est censée ressembler - voir en direct, et voir le code source.

+
+ +
+

Note : Si vous amélioriez ce code, vous pourriez vouloir boucler sur une liste d'éléments à afficher, en récupérant et en décodant chacun d'eux, puis boucler sur les résultats à l'intérieur de Promise.all(), en exécutant une fonction différente pour afficher chacun d'eux en fonction du type de code. Cela permettrait de fonctionner pour n'importe quel nombre d'éléments, pas seulement trois.

+

De plus, vous pourriez déterminer quel est le type de fichier récupéré sans avoir besoin d'une propriété type explicite. Vous pourriez, par exemple, vérifier l'en-tête HTTP Content-Type de la réponse dans chaque cas en utilisant response.headers.get("content-type"), puis agir en conséquence.

+
+ +

Exécution d'un code final après l'accomplissement/le rejet d'une promesse.

+ +

Il y aura des cas où vous voudrez exécuter un bloc de code final après la fin d'une promesse, qu'elle se soit réalisée ou rejetée. Auparavant, vous deviez inclure le même code dans les deux fonctions de rappel .then() et .catch(), par exemple :

+ +
myPromise
+.then(response => {
+  doSomething(response);
+  runFinalCode();
+})
+.catch(e => {
+  returnError(e);
+  runFinalCode();
+});
+ +

Dans les navigateurs modernes plus récents, la méthode .finally() est disponible, et peut être enchaînée à la fin de votre chaîne de promesses régulière, ce qui vous permet de réduire les répétitions de code et de faire les choses de manière plus élégante. Le code ci-dessus peut maintenant être écrit comme suit :

+ +
myPromise
+.then(response => {
+  doSomething(response);
+})
+.catch(e => {
+  returnError(e);
+})
+.finally(() => {
+  runFinalCode();
+});
+ +

Pour un exemple réel, jetez un œil à notre démonstration promesse-finally.html (voir le code source). Cela fonctionne de la même manière que la démo Promesse.all() que nous avons examinée dans la section ci-dessus, sauf que dans la fonction fetchAndDecode(), nous enchaînons un appel finally() à la fin de la chaîne :

+ +
function fetchAndDecode(url, type) {
+  return fetch(url).then(response => {
+    if(!response.ok) {
+      throw new Error(`Erreur HTTP ! statut : ${response.status}`);
+    } else {
+      if(type === 'blob') {
+        return response.blob();
+      } else if(type === 'text') {
+        return response.text();
+      }
+    }
+  })
+  .catch(e => {
+    console.log(`Il y a eu un problème avec votre opération de récupération de la ressource "${url}" : ` + e.message);
+  })
+  .finally(() => {
+    console.log(`La tentative de récupération de "${url}" est terminée.`);
+  });
+}
+ +

Cela permet d'envoyer un simple message à la console pour nous dire quand chaque tentative de récupération est terminée.

+ +
+

Note : then()/catch()/finally() est l'équivalent asynchrone de try/catch/finally en code synchrone.

+
+ +

Construire vos propres promesses personnalisées

+ +

La bonne nouvelle est que, d'une certaine manière, vous avez déjà construit vos propres promesses. Lorsque vous avez enchaîné plusieurs promesses avec des blocs .then(), ou que vous les avez autrement combinées pour créer une fonctionnalité personnalisée, vous créez déjà vos propres fonctions asynchrones personnalisées basées sur des promesses. Prenez notre fonction fetchAndDecode() des exemples précédents, par exemple.

+ +

La combinaison de différentes API basées sur les promesses pour créer des fonctionnalités personnalisées est de loin la façon la plus courante dont vous ferez des choses personnalisées avec les promesses, et montre la flexibilité et la puissance de la base de la plupart des API modernes autour du même principe. Il existe toutefois un autre moyen.

+ +

Utilisation du constructeur Promise()

+ +

Il est possible de construire vos propres promesses en utilisant le constructeur Promise(). La principale situation dans laquelle vous voudrez faire cela est lorsque vous avez du code basé sur une API asynchrone de la vieille école qui n'est pas basée sur les promesses, et que vous voulez promettre. Cela s'avère pratique lorsque vous avez besoin d'utiliser du code, des bibliothèques ou des frameworks existants, plus anciens, avec du code moderne basé sur les promesses.

+ +

Examinons un exemple simple pour vous aider à démarrer — ici, nous enveloppons un appel setTimeout() avec une promesse — cela exécute une fonction après deux secondes qui résout la promesse (en passant l'appel resolve()) avec une chaîne de caractères de "Succès ! ".

+ +
let timeoutPromise = new Promise((resolve, reject) => {
+  setTimeout(() => {
+    resolve('Succès !');
+  }, 2000);
+});
+ +

resolve() et reject() sont des fonctions que vous appelez pour réaliser ou rejeter la promesse nouvellement créée. Dans ce cas, la promesse se réalise avec une chaîne de caractères "Succès !".

+ +

Ainsi, lorsque vous appelez cette promesse, vous pouvez enchaîner un bloc .then() à la fin de celle-ci et on lui passera une chaîne de caractères "Succès !". Dans le code ci-dessous, nous alertons ce message :

+ +
timeoutPromise
+.then((message) => {
+  alert(message);
+})
+ +

ou même simplement

+ +
timeoutPromise.then(alert);
+ +

Essayez de l'exécuter en direct pour voir le résultat (voir aussi le code source).

+ +

L'exemple ci-dessus n'est pas très flexible - la promesse ne peut jamais s'accomplir qu'avec une seule chaîne de caractères, et elle n'a aucune sorte de condition reject() spécifiée (il est vrai que setTimeout() n'a pas vraiment de condition d'échec, donc cela n'a pas d'importance pour cet exemple).

+ +
+

Note : Pourquoi resolve(), et pas fulfill() ? La réponse que nous vous donnerons, pour l'instant, est c'est compliqué.

+
+ +

Rejeter une promesse personnalisée

+ +

Nous pouvons créer une promesse rejetée à l'aide de la méthode reject() — tout comme resolve(), celle-ci prend une seule valeur, mais dans ce cas, c'est la raison du rejet, c'est-à-dire l'erreur qui sera transmise dans le bloc .catch().

+ +

Étendons l'exemple précédent pour avoir quelques conditions reject() ainsi que pour permettre de transmettre différents messages en cas de succès.

+ +

Prenez une copie de l'exemple précédent, et remplacez la définition de timeoutPromise() existante par celle-ci :

+ +
function timeoutPromise(message, interval) {
+  return new Promise((resolve, reject) => {
+    if (message === '' || typeof message !== 'string') {
+      reject('Le message est vide ou n'est pas une chaîne de caractères');
+    } else if (interval < 0 || typeof interval !== 'number') {
+      reject(`L'intervalle est négatif ou n'est pas un nombre`);
+    } else {
+      setTimeout(() => {
+        resolve(message);
+      }, interval);
+    }
+  });
+};
+ +

Ici, nous passons deux arguments dans une fonction personnalisée - un message pour faire quelque chose avec, et l'intervalle de temps à passer avant de faire la chose. À l'intérieur de la fonction, nous renvoyons un nouvel objet Promise — l'invocation de la fonction renverra la promesse que nous voulons utiliser.

+ +

À l'intérieur du constructeur Promise, nous effectuons plusieurs vérifications dans des structures if ... else :

+ +
    +
  1. Tout d'abord, nous vérifions si le message est approprié pour être alerté. Si c'est une chaîne de caractères vide ou pas une chaîne de caractères du tout, nous rejetons la promesse avec un message d'erreur approprié.
  2. +
  3. Ensuite, nous vérifions si l'intervalle est une valeur d'intervalle appropriée. S'il est négatif ou n'est pas un nombre, nous rejetons la promesse avec un message d'erreur approprié.
  4. +
  5. Enfin, si les paramètres semblent tous deux OK, nous résolvons la promesse avec le message spécifié après que l'intervalle spécifié se soit écoulé en utilisant setTimeout().
  6. +
+ +

Puisque la fonction timeoutPromise() renvoie un objet Promise, nous pouvons enchaîner .then(), .catch(), etc. sur elle pour faire usage de ses fonctionnalités. Utilisons-le maintenant - remplaçons l'utilisation précédente de timeoutPromise par celle-ci :

+ +
timeoutPromise('Bonjour à tous !', 1000)
+.then(message => {
+   alert(message);
+})
+.catch(e => {
+  console.log('Erreur : ' + e);
+});
+ +

Lorsque vous enregistrez et exécutez le code tel quel, après une seconde, vous obtiendrez le message d'alerte. Essayez maintenant de définir le message sur une chaîne vide ou l'intervalle sur un nombre négatif, par exemple, et vous pourrez voir la promesse rejetée avec les messages d'erreur appropriés ! Vous pouvez également essayer de faire autre chose avec le message résolu plutôt que de simplement créer une alerte.

+ +
+

Note : Vous pouvez trouver notre version de cet exemple sur GitHub sur custom-promise2.html (voir aussi le code source).

+
+ +

Un exemple plus concret

+ +

L'exemple ci-dessus est resté volontairement simple pour rendre les concepts faciles à comprendre, mais il n'est pas vraiment très asynchrone. La nature asynchrone est essentiellement feinte en utilisant setTimeout(), bien qu'il montre quand même que les promesses sont utiles pour créer une fonction personnalisée avec un flux d'opérations raisonnable, une bonne gestion des erreurs, etc.

+ +

Un exemple que nous aimerions vous inviter à étudier, qui montre effectivement une application asynchrone utile du constructeur Promise(), est la bibliothèque idb de Jake Archibald. Celle-ci prend l'API IndexedDB API, qui est une API à l'ancienne basée sur des callbacks pour stocker et récupérer des données côté client, et vous permet de l'utiliser avec des promesses. Dans le code, vous verrez que le même type de techniques que nous avons évoqué plus haut est utilisé. Le bloc suivant convertit le modèle de requête de base utilisé par de nombreuses méthodes IndexedDB pour utiliser des promesses (voir ce code, par exemple).

+ +

Conclusion

+ +

Les promesses sont un bon moyen de construire des applications asynchrones lorsque nous ne connaissons pas la valeur de retour d'une fonction ou le temps qu'elle prendra pour retourner une réponse. Elles permettent d'exprimer et de raisonner plus facilement sur des séquences d'opérations asynchrones sans callbacks profondément imbriqués, et elles supportent un style de gestion des erreurs qui est similaire à l'instruction synchrone try...catch.

+ +

Les promesses fonctionnent dans les dernières versions de tous les navigateurs modernes ; le seul endroit où la prise en charge des promesses posera problème est Opera Mini et IE11 et les versions antérieures.

+ +

Nous n'avons pas abordé toutes les fonctionnalités des promesses dans cet article, mais seulement les plus intéressantes et les plus utiles. Au fur et à mesure que vous vous familiariserez avec les promesses, vous découvrirez d'autres fonctionnalités et techniques.

+ +

La plupart des API Web modernes sont basées sur des promesses, vous devrez donc comprendre les promesses pour en tirer le meilleur parti. Parmi ces API, citons WebRTC, Web Audio API, MediaStream API, et bien d'autres encore. Les promesses seront de plus en plus importantes au fil du temps, donc apprendre à les utiliser et à les comprendre est une étape importante dans l'apprentissage du JavaScript moderne.

+ +

Voir aussi

+ + + +

{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Timeouts_and_intervals", "Learn/JavaScript/Asynchronous/Async_await", "Learn/JavaScript/Asynchronous")}}

+ +

Dans ce module

+ + diff --git a/files/fr/learn/javascript/asynchronous/timeouts_and_intervals/index.html b/files/fr/learn/javascript/asynchronous/timeouts_and_intervals/index.html deleted file mode 100644 index e2f90a12fb..0000000000 --- a/files/fr/learn/javascript/asynchronous/timeouts_and_intervals/index.html +++ /dev/null @@ -1,599 +0,0 @@ ---- -title: 'JavaScript asynchrone coopératif : délais et intervalles' -slug: Learn/JavaScript/Asynchronous/Timeouts_and_intervals -tags: - - Animation - - Beginner - - CodingScripting - - Guide - - Intervals - - JavaScript - - Loops - - asynchronous - - requestAnimationFrame - - setInterval - - setTimeout - - timeouts -translation_of: Learn/JavaScript/Asynchronous/Timeouts_and_intervals ---- -
{{LearnSidebar}}
- -
{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Introducing", "Learn/JavaScript/Asynchronous/Promises", "Learn/JavaScript/Asynchronous")}}
- -

Ce tutoriel présente les méthodes traditionnelles dont dispose JavaScript pour exécuter du code de manière asynchrone après l'écoulement d'une période de temps déterminée ou à un intervalle régulier (par exemple, un nombre déterminé de fois par seconde), discute de leur utilité et examine leurs problèmes inhérents.

- - - - - - - - - - - - -
Prérequis :Connaissances informatiques de base, compréhension raisonnable des principes fondamentaux de JavaScript.
Objectif :Comprendre les boucles et les intervalles asynchrones et leur utilité.
- -

Introduction

- -

Depuis longtemps, la plate-forme Web offre aux programmeurs JavaScript un certain nombre de fonctions qui leur permettent d'exécuter du code de manière asynchrone après un certain intervalle de temps, et d'exécuter un bloc de code de manière asynchrone jusqu'à ce que vous lui demandiez de s'arrêter.

- -

Ces fonctions sont :

- -
-
setTimeout()
-
Exécuter un bloc de code spécifié une fois, après qu'un temps spécifié se soit écoulé.
-
setInterval()
-
Exécuter un bloc de code spécifique de manière répétée, avec un délai fixe entre chaque appel.
-
requestAnimationFrame()
-
La version moderne de setInterval(). Exécute un bloc de code spécifié avant que le navigateur ne repeigne ensuite l'affichage, ce qui permet à une animation d'être exécutée à une fréquence d'images appropriée, quel que soit l'environnement dans lequel elle est exécutée.
-
- -

Le code asynchrone mis en place par ces fonctions s'exécute sur le processus principal (après l'écoulement de leur minuterie spécifiée).

- -

Il est important de savoir que vous pouvez (et allez souvent) exécuter un autre code avant qu'un appel setTimeout() ne s'exécute, ou entre les itérations de setInterval(). Selon l'intensité du processeur de ces opérations, elles peuvent retarder encore plus votre code asynchrone, car tout code asynchrone ne s'exécutera qu'après la disponibilité du processus principal. (En d'autres termes, lorsque la pile est vide.) Vous en apprendrez davantage à ce sujet au fur et à mesure que vous progresserez dans cet article.

- -

Dans tous les cas, ces fonctions sont utilisées pour exécuter des animations constantes et d'autres traitements en arrière-plan sur un site Web ou une application. Dans les sections suivantes, nous allons vous montrer comment les utiliser.

- -

setTimeout()

- -

Comme nous l'avons dit précédemment, setTimeout() exécute un bloc de code particulier une fois qu'un temps spécifié s'est écoulé. Il prend les paramètres suivants :

- - - -
-

Note : La quantité de temps spécifiée (ou le délai) n'est pas le temps garanti à l'exécution, mais plutôt le temps minimum à l'exécution. Les rappels que vous passez à ces fonctions ne peuvent pas s'exécuter tant que la pile du processus principal n'est pas vide.

-

En conséquence, un code comme setTimeout(fn, 0) s'exécutera dès que la pile sera vide, pas immédiatement. Si vous exécutez un code comme setTimeout(fn, 0) mais qu'immédiatement après vous exécutez une boucle qui compte de 1 à 10 milliards, votre rappel sera exécuté après quelques secondes.

-
- -

Dans l'exemple suivant, le navigateur attendra deux secondes avant d'exécuter la fonction anonyme, puis affichera le message d'alerte (voir son exécution en direct, et voir le code source) :

- -
let maSalutation = setTimeout(() => {
-  console.log('Bonjour, M. Univers !');
-}, 2000);
- -

Les fonctions que vous spécifiez n'ont pas besoin d'être anonymes. Vous pouvez donner un nom à votre fonction, et même la définir ailleurs et passer une référence de fonction à setTimeout(). Les deux versions suivantes de l'extrait de code sont équivalentes à la première :

- -
// Avec une fonction nommée
-let maSalutation = setTimeout(function direBonjour() {
-  console.log('Bonjour, M. Univers !');
-}, 2000);
-
-// Avec une fonction définie séparément
-function direBonjour() {
-  console.log('Bonjour, M. Univers !');
-}
-
-let maSalutation = setTimeout(direBonjour, 2000);
- -

Cela peut être utile si vous avez une fonction qui doit être appelée à la fois à partir d'un délai d'attente et en réponse à un événement, par exemple. Mais cela peut aussi vous aider à garder votre code en ordre, surtout si le rappel du délai d'attente représente plus de quelques lignes de code.

- -

setTimeout() renvoie une valeur d'identifiant qui peut être utilisée pour faire référence au délai d'attente ultérieurement, par exemple lorsque vous souhaitez l'arrêter. Voir Effacement des délais d'attente (ci-dessous) pour apprendre comment faire cela.

- -

Passage de paramètres à une fonction setTimeout()

- -

Tous les paramètres que vous voulez passer à la fonction en cours d'exécution à l'intérieur du setTimeout() doivent lui être passés comme paramètres supplémentaires à la fin de la liste.

- -

Par exemple, vous pouvez remanier la fonction précédente pour qu'elle dise bonjour à la personne dont le nom lui est transmis :

- -
function direBonjour(who) {
-  console.log(`Bonjour ${who} !`);
-}
- -

Maintenant, vous pouvez passer le nom de la personne dans l'appel setTimeout() comme troisième paramètre :

- -
let maSalutation = setTimeout(direBonjour, 2000, 'M. Univers');
- -

Effacement des délais d'attente

- -

Enfin, si un timeout a été créé, vous pouvez l'annuler avant que le temps spécifié ne se soit écoulé en appelant clearTimeout(), en lui passant en paramètre l'identifiant de l'appel setTimeout(). Donc pour annuler notre timeout ci-dessus, vous feriez ceci :

- -
clearTimeout(maSalutation);
- -
-

Note : Voir greeter-app.html pour une démo un peu plus élaborée qui permet de définir le nom de la personne à saluer dans un formulaire, et d'annuler la salutation à l'aide d'un bouton séparé (voir aussi le code source).

-
- -

setInterval()

- -

setTimeout() fonctionne parfaitement lorsque vous devez exécuter du code une fois après une période de temps définie. Mais que se passe-t-il lorsque vous avez besoin d'exécuter le code encore et encore - par exemple, dans le cas d'une animation ?

- -

C'est là qu'intervient le setInterval(). Cela fonctionne de manière très similaire à setTimeout(), sauf que la fonction que vous passez comme premier paramètre est exécutée de manière répétée à une fréquence égale au nombre de millisecondes donné par le deuxième paramètre distinct, plutôt qu'une seule fois. Vous pouvez également passer tous les paramètres requis par la fonction en cours d'exécution comme paramètres ultérieurs de l'appel setInterval().

- -

Prenons un exemple. La fonction suivante crée un nouvel objet Date(), en extrait une chaîne de temps en utilisant toLocaleTimeString(), puis l'affiche dans l'interface utilisateur. Elle exécute ensuite la fonction une fois par seconde à l'aide de setInterval(), créant l'effet d'une horloge numérique qui se met à jour une fois par seconde (voir cela en direct, et aussi voir la source) :

- -
function displayTime() {
-   let date = new Date();
-   let time = date.toLocaleTimeString();
-   document.getElementById('demo').textContent = time;
-}
-
-const createClock = setInterval(displayTime, 1000);
- -

Tout comme setTimeout(), setInterval() renvoie une valeur d'identification que vous pouvez utiliser plus tard lorsque vous devez effacer l'intervalle.

- -

Effacement des intervalles

- -

setInterval() continue à exécuter une tâche pour toujours, à moins que vous ne fassiez quelque chose à ce sujet. Vous voudrez probablement un moyen d'arrêter de telles tâches, sinon vous pouvez finir par obtenir des erreurs lorsque le navigateur ne peut pas compléter d'autres versions de la tâche, ou si l'animation gérée par la tâche est terminée. Vous pouvez le faire de la même manière que vous arrêtez les temporisations - en passant l'identifiant renvoyé par l'appel setInterval() à la fonction clearInterval() :

- -
const myInterval = setInterval(myFunction, 2000);
-
-clearInterval(myInterval);
- -

Apprentissage actif : Créez votre propre chronomètre !

- -

Tout ceci étant dit, nous avons un défi à vous proposer. Prenez une copie de notre exemple setInterval-clock.html, et modifiez-le pour créer votre propre chronomètre simple.

- -

Vous devez afficher une heure comme précédemment, mais dans cet exemple, vous avez besoin :

- - - -

Voici quelques conseils pour vous :

- - - -
-

Note : Si vous êtes bloqué, vous pouvez trouver notre version ici (voir le code source).

-
- -

Choses à garder à l'esprit concernant setTimeout() et setInterval()

- -

Il y a quelques éléments à garder à l'esprit lorsque vous travaillez avec setTimeout() et setInterval(). Passons-les en revue maintenant.

- -

Délais récursifs

- -

Il existe une autre façon d'utiliser setTimeout() : vous pouvez l'appeler de manière récursive pour exécuter le même code de manière répétée, au lieu d'utiliser setInterval().

- -

L'exemple ci-dessous utilise un setTimeout() récursif pour exécuter la fonction passée toutes les 100 millisecondes :

- -
let i = 1;
-
-setTimeout(function run() {
-  console.log(i);
-  i++;
-  setTimeout(run, 100);
-}, 100);
- -

Comparez l'exemple ci-dessus à celui qui suit - celui-ci utilise setInterval() pour accomplir le même effet :

- -
let i = 1;
-
-setInterval(function run() {
-  console.log(i);
-  i++;
-}, 100);
- -

Quelle est la différence entre le setTimeout() récursif et le setInterval() ?

- -

La différence entre les deux versions du code ci-dessus est subtile.

- - - -

Lorsque votre code a le potentiel de prendre plus de temps à s'exécuter que l'intervalle de temps que vous avez assigné, il est préférable d'utiliser le setTimeout() récursif - cela maintiendra l'intervalle de temps constant entre les exécutions, quelle que soit la durée d'exécution du code, et vous n'obtiendrez pas d'erreurs.

- -

Délais immédiats

- -

En utilisant 0 comme valeur pour setTimeout(), on planifie l'exécution de la fonction de rappel spécifiée dès que possible, mais seulement après l'exécution du processus de code principal.

- -

Par exemple, le code ci-dessous (voir en direct) produit une alerte contenant "Hello", puis une alerte contenant "World" dès que vous cliquez sur OK sur la première alerte.

- -
setTimeout(() => {
-  alert('World');
-}, 0);
-
-alert('Hello');
- -

Cela peut être utile dans les cas où vous souhaitez définir un bloc de code à exécuter dès que l'ensemble du thread principal a terminé son exécution - placez-le dans la boucle d'événement asynchrone, de sorte qu'il s'exécutera immédiatement après.

- -

Effacement avec clearTimeout() ou clearInterval()

- -

clearTimeout() et clearInterval() utilisent toutes deux la même liste d'entrées pour effacer. Il est intéressant de noter que cela signifie que vous pouvez utiliser l'une comme l'autre méthode pour effacer un setTimeout() ou setInterval().

- -

Par souci de cohérence, vous devriez utiliser clearTimeout() pour effacer les entrées setTimeout() et clearInterval() pour effacer les entrées setInterval(). Cela permettra d'éviter toute confusion.

- -

requestAnimationFrame()

- -

requestAnimationFrame() est une fonction de bouclage spécialisée, créée pour exécuter des animations de manière efficace dans le navigateur. Elle exécute un bloc de code spécifié avant que le navigateur ne repeigne ensuite l'affichage, ce qui permet d'exécuter une animation à une fréquence de rafraîchissement appropriée, quel que soit l'environnement dans lequel elle est exécutée.

- -

Elle a été créée en réponse aux problèmes perçus avec les fonctions asynchrones antérieures comme setInterval(), qui, par exemple, ne s'exécute pas à une fréquence d'images optimisée pour le matériel et continue à s'exécuter alors qu'elle pourrait s'arrêter lorsque l'onglet n'est plus actif ou si l'animation se déroule hors de la page, etc.

- -

(Plus d'informations à ce sujet sur CreativeJS (en).)

- -
-

Note : Vous trouverez des exemples d'utilisation de requestAnimationFrame() ailleurs dans le cours - voir par exemple Dessiner des éléments graphiques, et La construction d'objet en pratique.

-
- -

La méthode prend comme argument un rappel à invoquer avant le repeignage. C'est le modèle général dans lequel vous le verrez utilisé :

- -
function draw() {
-   // Le code du dessin va ici
-   requestAnimationFrame(draw);
-}
-
-draw();
- -

L'idée est de définir une fonction dans laquelle votre animation est mise à jour (par exemple, vos sprites sont déplacés, le score est mis à jour, les données sont rafraîchies, ou autre). Ensuite, vous l'appelez pour lancer le processus. À la fin du bloc de fonctions, vous appelez requestAnimationFrame() avec la référence de la fonction passée en paramètre, et cela indique au navigateur de rappeler la fonction lors du prochain rafraîchissement de l'affichage. Ceci est ensuite exécuté en continu, car le code appelle requestAnimationFrame() de manière récursive.

- -
-

Note : Si vous souhaitez réaliser une sorte d'animation DOM simple et constante, les animations CSS sont probablement plus rapides. Elles sont calculées directement par le code interne du navigateur, plutôt que par JavaScript.

-

Si, toutefois, vous faites quelque chose de plus complexe et impliquant des objets qui ne sont pas directement accessibles à l'intérieur du DOM (comme les objets 2D Canvas API ou WebGL), requestAnimationFrame() est la meilleure option dans la plupart des cas.

-
- -

Quelle est la vitesse de votre animation ?

- -

La fluidité de votre animation dépend directement de la fréquence d'images de votre animation, qui est mesurée en images par seconde (ips). Plus ce nombre est élevé, plus votre animation sera fluide, jusqu'à un certain point.

- -

Comme la plupart des écrans ont une fréquence de rafraîchissement de 60 Hz, la fréquence d'images la plus rapide que vous pouvez viser est de 60 images par seconde (IPS) lorsque vous travaillez avec des navigateurs Web. Cependant, plus d'images signifie plus de traitement, ce qui peut souvent provoquer des saccades et des sauts - également connus sous le nom de dégradation des images, ou saccades.

- -

Si vous disposez d'un moniteur avec une fréquence de rafraîchissement de 60 Hz et que vous souhaitez obtenir 60 IPS, vous disposez d'environ 16,7 millisecondes (1000 / 60) pour exécuter votre code d'animation et rendre chaque image. Ceci est un rappel que vous devrez être attentif à la quantité de code que vous essayez d'exécuter pendant chaque passage dans la boucle d'animation.

- -

requestAnimationFrame() essaie toujours de se rapprocher le plus possible de cette valeur magique de 60 IPS. Parfois, ce n'est pas possible - si vous avez une animation vraiment complexe et que vous l'exécutez sur un ordinateur lent, votre fréquence d'images sera inférieure. Dans tous les cas, requestAnimationFrame() fera toujours du mieux qu'il peut avec ce dont il dispose.

- -

En quoi requestAnimationFrame() diffère-t-il de setInterval() et setTimeout() ?

- -

Parlons un peu plus de la façon dont la méthode requestAnimationFrame() diffère des autres méthodes utilisées précédemment. En regardant notre code d'en haut :

- -
function draw() {
-   // Le code du dessin va ici
-   requestAnimationFrame(draw);
-}
-
-draw();
- -

Voyons maintenant comment faire la même chose en utilisant setInterval() :

- -
function draw() {
-   // Le code du dessin va ici
-}
-
-setInterval(draw, 17);
- -

Comme nous l'avons couvert précédemment, vous ne spécifiez pas d'intervalle de temps pour requestAnimationFrame(). Il l'exécute simplement aussi vite et aussi bien que possible dans les conditions actuelles. Le navigateur ne perd pas non plus de temps à l'exécuter si l'animation est hors écran pour une raison quelconque, etc.

- -

setInterval(), d'autre part exige qu'un intervalle soit spécifié. Nous sommes arrivés à notre valeur finale de 17 via la formule 1000 millisecondes / 60Hz, puis nous l'avons arrondie. Arrondir vers le haut est une bonne idée ; si vous arrondissez vers le bas, le navigateur pourrait essayer d'exécuter l'animation à une vitesse supérieure à 60 FPS, et cela ne ferait de toute façon aucune différence pour la fluidité de l'animation. Comme nous l'avons déjà dit, 60 Hz est la fréquence de rafraîchissement standard.

- -

Inclure un horodatage

- -

Le rappel réel passé à la fonction requestAnimationFrame() peut également recevoir un paramètre : une valeur timestamp, qui représente le temps depuis que le requestAnimationFrame() a commencé à s'exécuter.

- -

C'est utile car cela vous permet d'exécuter des choses à des moments précis et à un rythme constant, quelle que soit la vitesse ou la lenteur de votre appareil. Le modèle général que vous utiliserez ressemble à quelque chose comme ceci :

- -
let startTime = null;
-
-function draw(timestamp) {
-    if (!startTime) {
-      startTime = timestamp;
-    }
-
-   currentTime = timestamp - startTime;
-
-   // Faire quelque chose en fonction du temps actuel
-
-   requestAnimationFrame(draw);
-}
-
-draw();
- -

Prise en charge des navigateurs

- -

requestAnimationFrame() est supporté par des navigateurs plus récents que pour setInterval()/setTimeout(). Il est intéressant de noter qu'elle est disponible dans Internet Explorer 10 et plus.

- -

Ainsi, à moins que vous ne deviez prendre en charge d'anciennes versions d'IE, il y a peu de raisons de ne pas utiliser requestAnimationFrame().

- -

Un exemple simple

- -

Assez avec la théorie ! Construisons votre propre exemple personnel de requestAnimationFrame(). Vous allez créer une simple "animation de toupie" - le genre que vous pourriez voir affiché dans une application lorsqu'elle est occupée à se connecter au serveur, etc.

- -
-

Note : Dans un exemple du monde réel, vous devriez probablement utiliser des animations CSS pour exécuter ce type d'animation simple. Cependant, ce genre d'exemple est très utile pour démontrer l'utilisation de requestAnimationFrame(), et vous seriez plus susceptible d'utiliser ce genre de technique lorsque vous faites quelque chose de plus complexe comme la mise à jour de l'affichage d'un jeu à chaque image.

-
- -
    -
  1. Prenez un modèle HTML de base (comme celui-ci).

  2. -
  3. Placez un élément <div> vide à l'intérieur de l'élément <body>, puis ajoutez un caractère ↻ à l'intérieur. Ce caractère de flèche circulaire fera office de notre toupie pour cet exemple.

  4. -
  5. Appliquez le CSS suivant au modèle HTML (de la manière que vous préférez). Cela définit un fond rouge sur la page, définit la hauteur du <body> à 100% de la hauteur de <html>, et centre le <div> à l'intérieur du <body>, horizontalement et verticalement.

    -
    html {
    -  background-color: white;
    -  height: 100%;
    -}
    -
    -body {
    -  height: inherit;
    -  background-color: red;
    -  margin: 0;
    -  display: flex;
    -  justify-content: center;
    -  align-items: center;
    -}
    -
    -div {
    -  display: inline-block;
    -  font-size: 10rem;
    -}
    -
  6. -
  7. Insérez un élément <script> juste au-dessus de la balise de fermeture </body>.

  8. -
  9. Insérez le JavaScript suivant dans votre élément <script>. Ici, vous stockez une référence à l'élément <div> ; à l'intérieur d'une constante, définissez une variable rotateCount à 0, définissez une variable non initialisée qui sera utilisée plus tard pour contenir une référence à l'appel requestAnimationFrame(), et en définissant une variable startTime à null, qui sera plus tard utilisée pour stocker l'heure de début de l'appel requestAnimationFrame().

    -
    const spinner = document.querySelector('div');
    -let rotateCount = 0;
    -let startTime = null;
    -let rAF;
    -
    -
  10. -
  11. -

    Sous le code précédent, insérez une fonction draw() qui sera utilisée pour contenir notre code d'animation, qui inclut le paramètre timestamp :

    -
    function draw(timestamp) {
    -
    -}
    -
  12. -
  13. À l'intérieur de draw(), ajoutez les lignes suivantes. Elles définiront l'heure de départ si elle n'est pas déjà définie (cela ne se produira que lors de la première itération de la boucle), et définiront le rotateCount à une valeur pour faire tourner le spinner par (l'horodatage actuel, prendre l'horodatage de départ, divisé par trois pour que cela n'aille pas trop vite) :

    -
      if(!startTime) {
    -   startTime = timestamp;
    -  }
    -
    -  rotateCount = (timestamp - startTime) / 3;
    -
    -
  14. -
  15. Sous la ligne précédente à l'intérieur de draw(), ajoutez le bloc suivant - il s'assure que la valeur de rotateCount est comprise entre 0 et 360 en utilisant le modulo à 360 (c'est-à-dire le reste restant lorsque la valeur est divisée par 360) afin que l'animation du cercle puisse continuer sans interruption, à une valeur basse et raisonnable. Notez que ce n'est pas strictement nécessaire, mais il est plus facile de travailler avec des valeurs de 0-359 degrés que des valeurs comme "128000 degrés".

    -
    rotateCount %= 360;
    -
  16. -
  17. Ensuite, sous le bloc précédent, ajoutez la ligne suivante pour faire tourner le spinner : -
    spinner.style.transform = `rotate(${rotateCount}deg)`;
    -
  18. -
  19. Tout en bas, à l'intérieur de la fonction draw(), insérez la ligne suivante. C'est la clé de toute l'opération - vous définissez la variable définie précédemment à un appel actif requestAnimation(), qui prend la fonction draw() comme paramètre. Cela fait démarrer l'animation, en exécutant constamment la fonction draw() à un taux aussi proche que possible de 60 IPS.

    -
    rAF = requestAnimationFrame(draw);
    -
  20. -
  21. Sous la définition de la fonction draw(), ajoutez un appel à la fonction draw() pour lancer l'animation.

    -
    draw();
    -
  22. -
- -
-

Note : Vous pouvez trouver l'exemple terminé en direct sur GitHub. (Vous pouvez également voir le code source).

-
- -

Effacer un appel de requestAnimationFrame()

- -

Effacer un appel de requestAnimationFrame() peut être fait en appelant la méthode cancelAnimationFrame() correspondante. (Notez que le nom de la fonction commence par "cancel", et non par "clear" comme pour les méthodes "set...").

- -

Il suffit de lui passer la valeur renvoyée par l'appel requestAnimationFrame() à annuler, que vous avez stockée dans la variable rAF :

- -
cancelAnimationFrame(rAF);
- -

Apprentissage actif : Démarrer et arrêter la toupie

- -

Dans cet exercice, nous aimerions que vous testiez la méthode cancelAnimationFrame() en prenant notre exemple précédent et en le mettant à jour, en ajoutant un écouteur d'événements pour démarrer et arrêter le spinner lorsque la souris est cliquée n'importe où sur la page.

- -

Quelques conseils :

- - - -
-

Note : Essayez d'abord vous-même ; si vous êtes vraiment bloqué, consultez nos pages exemple en direct et code source.

-
- -

Ralentissement d'une animation requestAnimationFrame()

- -

Une limitation de requestAnimationFrame() est que vous ne pouvez pas choisir votre fréquence d'images. Ce n'est pas un problème la plupart du temps, car en général, vous voulez que votre animation se déroule de la manière la plus fluide possible. Mais qu'en est-il lorsque vous souhaitez créer une animation old school, de style 8 bits ?

- -

C'était un problème, par exemple, dans l'animation de marche inspirée de Monkey Island de notre article Dessiner des éléments graphiques :

- -

{{EmbedGHLiveSample("learning-area/javascript/apis/drawing-graphics/loops_animation/7_canvas_walking_animation.html", '100%', 260)}}

- -

Dans cet exemple, vous devez animer à la fois la position du personnage à l'écran et le sprite affiché. Il n'y a que 6 images dans l'animation du sprite. Si vous montriez une image différente du sprite pour chaque image affichée à l'écran par requestAnimationFrame(), Guybrush bougerait ses membres trop vite et l'animation aurait l'air ridicule. Cet exemple étrangle la vitesse à laquelle le sprite cycle ses images en utilisant le code suivant :

- -
if (posX % 13 === 0) {
-  if (sprite === 5) {
-    sprite = 0;
-  } else {
-    sprite++;
-  }
-}
- -

Ainsi, le code ne cycle le sprite qu'une fois toutes les 13 images d'animation.

- -

...En fait, c'est environ toutes les 6,5 images, car nous mettons à jour posX (position du personnage sur l'écran) par deux à chaque image :

- -
if (posX > width/2) {
-  newStartPos = -( (width/2) + 102 );
-  posX = Math.ceil(newStartPos / 13) * 13;
-  console.log(posX);
-} else {
-  posX += 2;
-}
- -

C'est le code qui calcule comment mettre à jour la position dans chaque image d'animation.

- -

La méthode que vous utilisez pour accélérer votre animation dépendra de votre code particulier. Par exemple, dans l'exemple précédent de la toupie, vous pourriez faire en sorte qu'elle semble se déplacer plus lentement en n'augmentant le rotateCount que de un à chaque image, au lieu de deux.

- -

Apprentissage actif : un jeu de réaction

- -

Pour la dernière section de cet article, vous allez créer un jeu de réaction à 2 joueurs. Le jeu aura deux joueurs, dont l'un contrôlera le jeu à l'aide de la touche A, et l'autre avec la touche L.

- -

Lorsque l'on appuie sur le bouton Start, une toupie comme celle que nous avons vue précédemment s'affiche pendant un temps aléatoire compris entre 5 et 10 secondes. Après ce temps, un message apparaîtra disant "PLAYERS GO !!" - une fois que cela se produit, le premier joueur à appuyer sur son bouton de contrôle gagnera la partie.

- -

{{EmbedGHLiveSample("learning-area/javascript/asynchronous/loops-and-intervals/reaction-game.html", '100%', 500)}}

- -

Travaillons ensemble :

- -
    -
  1. Tout d'abord, téléchargez le fichier de démarrage de l'application. Celui-ci contient la structure HTML et le style CSS finis, ce qui nous donne un plateau de jeu qui affiche les informations des deux joueurs (comme vu ci-dessus), mais avec le spinner et le paragraphe des résultats affichés l'un au-dessus de l'autre. Il ne vous reste plus qu'à écrire le code JavaScript.

  2. -
  3. À l'intérieur de l'élément vide <script> de votre page, commencez par ajouter les lignes de code suivantes qui définissent certaines constantes et variables dont vous aurez besoin dans le reste du code :

    -
    const spinner = document.querySelector('.spinner p');
    -const spinnerContainer = document.querySelector('.spinner');
    -let rotateCount = 0;
    -let startTime = null;
    -let rAF;
    -const btn = document.querySelector('button');
    -const result = document.querySelector('.result');
    -

    Dans l'ordre, ce sont :

    -
      -
    1. Une référence à la toupie, afin que vous puissiez l'animer.
    2. -
    3. Une référence à l'élément <div> qui contient la toupie, utilisée pour l'afficher et la masquer.
    4. -
    5. Un nombre de rotations. Ce paramètre détermine le nombre de rotations de la toupie que vous souhaitez afficher à chaque image de l'animation.
    6. -
    7. Un temps de démarrage nul. Il sera rempli avec une heure de début lorsque la toupie commencera à tourner.
    8. -
    9. Une variable non initialisée pour stocker ultérieurement l'appel requestAnimationFrame() qui anime la toupie.
    10. -
    11. Une référence au bouton de démarrage.
    12. -
    13. Une référence au paragraphe des résultats.
    14. -
    -
  4. -
  5. Ensuite, sous les lignes de code précédentes, ajoutez la fonction suivante. Elle prend deux nombres et retourne un nombre aléatoire entre les deux. Vous en aurez besoin pour générer un intervalle de temps aléatoire plus tard.

    -
    function random(min,max) {
    -  var num = Math.floor(Math.random()*(max-min)) + min;
    -  return num;
    -}
    -
  6. -
  7. Ajoutez ensuite la fonction draw(), qui anime la toupie. Cette version est très similaire à celle de l'exemple simple de la toupie, plus haut :

    -
    function draw(timestamp) {
    -  if(!startTime) {
    -   startTime = timestamp;
    -  }
    -
    -  rotateCount = (timestamp - startTime) / 3;
    -
    -  rotateCount %= 360;
    -
    -  spinner.style.transform = 'rotate(' + rotateCount + 'deg)';
    -  rAF = requestAnimationFrame(draw);
    -}
    -
  8. -
  9. Il est maintenant temps de mettre en place l'état initial de l'application lors du premier chargement de la page. Ajoutez les deux lignes suivantes, qui masquent le paragraphe des résultats et le conteneur de la toupie en utilisant display : none;.

    -
    result.style.display = 'none';
    -spinnerContainer.style.display = 'none';
    -
  10. -
  11. Ensuite, définissez une fonction reset(), qui remet l'application dans l'état initial nécessaire pour relancer le jeu après qu'il a été joué. Ajoutez ce qui suit au bas de votre code :

    -
    function reset() {
    -  btn.style.display = 'block';
    -  result.textContent = '';
    -  result.style.display = 'none';
    -}
    -
  12. -
  13. Bon, assez de préparation ! Il est temps de rendre le jeu jouable ! Ajoutez le bloc suivant à votre code. La fonction start() appelle draw() pour commencer à faire tourner la toupie et l'afficher dans l'interface utilisateur, cache le bouton Start pour que vous ne puissiez pas perturber le jeu en le démarrant plusieurs fois simultanément, et exécute un appel setTimeout() qui exécute une fonction setEndgame() après qu'un intervalle aléatoire entre 5 et 10 secondes se soit écoulé. Le bloc suivant ajoute également un écouteur d'événements à votre bouton pour exécuter la fonction start() lorsqu'il est cliqué.

    -
    btn.addEventListener('click', start);
    -
    -function start() {
    -  draw();
    -  spinnerContainer.style.display = 'block';
    -  btn.style.display = 'none';
    -  setTimeout(setEndgame, random(5000,10000));
    -}
    -
    -

    Note : Vous verrez que cet exemple appelle setTimeout() sans stocker la valeur de retour. (Donc, pas let myTimeout = setTimeout(functionName, interval).)

    -

    Cela fonctionne très bien, tant que vous n'avez pas besoin d'effacer votre intervalle/temps d'attente à un moment donné. Si vous le faites, vous devrez sauvegarder l'identifiant renvoyé !

    -
    -

    Le résultat net du code précédent est que lorsque le bouton Start est pressé, la toupie est affichée et les joueurs sont amenés à attendre un temps aléatoire avant d'être invités à appuyer sur leur bouton. Cette dernière partie est gérée par la fonction setEndgame(), que vous allez définir ensuite.

    -
  14. -
  15. Ajoutez ensuite la fonction suivante à votre code :

    -
    function setEndgame() {
    -  cancelAnimationFrame(rAF);
    -  spinnerContainer.style.display = 'none';
    -  result.style.display = 'block';
    -  result.textContent = 'JOUEURS : ALLEZ-Y !!';
    -
    -  document.addEventListener('keydown', keyHandler);
    -
    -  function keyHandler(e) {
    -    let isOver = false;
    -    console.log(e.key);
    -
    -    if (e.key === "a") {
    -      result.textContent = 'Le joueur 1 a gagné !!';
    -      isOver = true;
    -    } else if (e.key === "l") {
    -      result.textContent = 'Le joueur 2 a gagné !!';
    -      isOver = true;
    -    }
    -
    -    if (isOver) {
    -      document.removeEventListener('keydown', keyHandler);
    -      setTimeout(reset, 5000);
    -    }
    -  };
    -}
    -

    Son fonctionnement :

    -
      -
    1. Tout d'abord, annule l'animation de la toupie avec cancelAnimationFrame() (il est toujours bon de nettoyer les processus inutiles), et cache le conteneur de la toupie.
    2. -
    3. Ensuite, affiche le paragraphe des résultats et définissez son contenu textuel sur "JOUEURS : ALLEZ-Y !!" pour signaler aux joueurs qu'ils peuvent maintenant appuyer sur leur bouton pour gagner.
    4. -
    5. Attache un écouteur d'événements keydown au document. Lorsqu'un bouton quelconque est enfoncé, la fonction keyHandler() est exécutée.
    6. -
    7. À l'intérieur de keyHandler(), le code inclut l'objet événement en tant que paramètre (représenté par e) - sa propriété key contient la touche qui vient d'être pressée, et vous pouvez l'utiliser pour répondre à des pressions de touche spécifiques par des actions spécifiques.
    8. -
    9. Définit la variable isOver à false, afin que nous puissions suivre si les bonnes touches ont été pressées pour que le joueur 1 ou 2 gagne. Nous ne voulons pas que le jeu se termine lorsqu'une mauvaise touche a été pressée.
    10. -
    11. Enregistre e.key dans la console, ce qui est un moyen utile de connaître la valeur key des différentes touches sur lesquelles vous appuyez.
    12. -
    13. Lorsque e.key est "a", affiche un message pour dire que le joueur 1 a gagné, et lorsque e.key est "l", affiche un message pour dire que le joueur 2 a gagné. (Note: Cela ne fonctionnera qu'avec les minuscules a et l - si un A ou un L majuscule est soumis (la touche plus Shift), il est compté comme une touche différente !). Si une de ces touches a été pressée, mettez isOver à true.
    14. -
    15. Seulement si isOver est true, supprime l'écouteur d'événements keydown en utilisant removeEventListener() de sorte qu'une fois que l'appui gagnant s'est produit, plus aucune entrée clavier n'est possible pour perturber le résultat final du jeu. Vous utilisez également setTimeout() pour appeler reset() après 5 secondes - comme expliqué précédemment, cette fonction réinitialise le jeu à son état initial afin qu'une nouvelle partie puisse être lancée.
    16. -
    -
  16. -
- -

Voilà, c'est fait !

- -
-

Note : Si vous êtes bloqué, consultez notre version du jeu en live (voir également le code source).

-
- -

Conclusion

- -

Voilà, tous les éléments essentiels des boucles et des intervalles asynchrones sont couverts dans un seul article. Vous trouverez ces méthodes utiles dans de nombreuses situations, mais veillez à ne pas en abuser ! Parce qu'ils s'exécutent toujours sur le processus principal, les rappels lourds et intensifs (en particulier ceux qui manipulent le DOM) peuvent vraiment ralentir une page si vous ne faites pas attention.

- -

{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Introducing", "Learn/JavaScript/Asynchronous/Promises", "Learn/JavaScript/Asynchronous")}}

- -

Dans ce module

- - diff --git a/files/fr/learn/javascript/asynchronous/timeouts_and_intervals/index.md b/files/fr/learn/javascript/asynchronous/timeouts_and_intervals/index.md new file mode 100644 index 0000000000..e2f90a12fb --- /dev/null +++ b/files/fr/learn/javascript/asynchronous/timeouts_and_intervals/index.md @@ -0,0 +1,599 @@ +--- +title: 'JavaScript asynchrone coopératif : délais et intervalles' +slug: Learn/JavaScript/Asynchronous/Timeouts_and_intervals +tags: + - Animation + - Beginner + - CodingScripting + - Guide + - Intervals + - JavaScript + - Loops + - asynchronous + - requestAnimationFrame + - setInterval + - setTimeout + - timeouts +translation_of: Learn/JavaScript/Asynchronous/Timeouts_and_intervals +--- +
{{LearnSidebar}}
+ +
{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Introducing", "Learn/JavaScript/Asynchronous/Promises", "Learn/JavaScript/Asynchronous")}}
+ +

Ce tutoriel présente les méthodes traditionnelles dont dispose JavaScript pour exécuter du code de manière asynchrone après l'écoulement d'une période de temps déterminée ou à un intervalle régulier (par exemple, un nombre déterminé de fois par seconde), discute de leur utilité et examine leurs problèmes inhérents.

+ + + + + + + + + + + + +
Prérequis :Connaissances informatiques de base, compréhension raisonnable des principes fondamentaux de JavaScript.
Objectif :Comprendre les boucles et les intervalles asynchrones et leur utilité.
+ +

Introduction

+ +

Depuis longtemps, la plate-forme Web offre aux programmeurs JavaScript un certain nombre de fonctions qui leur permettent d'exécuter du code de manière asynchrone après un certain intervalle de temps, et d'exécuter un bloc de code de manière asynchrone jusqu'à ce que vous lui demandiez de s'arrêter.

+ +

Ces fonctions sont :

+ +
+
setTimeout()
+
Exécuter un bloc de code spécifié une fois, après qu'un temps spécifié se soit écoulé.
+
setInterval()
+
Exécuter un bloc de code spécifique de manière répétée, avec un délai fixe entre chaque appel.
+
requestAnimationFrame()
+
La version moderne de setInterval(). Exécute un bloc de code spécifié avant que le navigateur ne repeigne ensuite l'affichage, ce qui permet à une animation d'être exécutée à une fréquence d'images appropriée, quel que soit l'environnement dans lequel elle est exécutée.
+
+ +

Le code asynchrone mis en place par ces fonctions s'exécute sur le processus principal (après l'écoulement de leur minuterie spécifiée).

+ +

Il est important de savoir que vous pouvez (et allez souvent) exécuter un autre code avant qu'un appel setTimeout() ne s'exécute, ou entre les itérations de setInterval(). Selon l'intensité du processeur de ces opérations, elles peuvent retarder encore plus votre code asynchrone, car tout code asynchrone ne s'exécutera qu'après la disponibilité du processus principal. (En d'autres termes, lorsque la pile est vide.) Vous en apprendrez davantage à ce sujet au fur et à mesure que vous progresserez dans cet article.

+ +

Dans tous les cas, ces fonctions sont utilisées pour exécuter des animations constantes et d'autres traitements en arrière-plan sur un site Web ou une application. Dans les sections suivantes, nous allons vous montrer comment les utiliser.

+ +

setTimeout()

+ +

Comme nous l'avons dit précédemment, setTimeout() exécute un bloc de code particulier une fois qu'un temps spécifié s'est écoulé. Il prend les paramètres suivants :

+ + + +
+

Note : La quantité de temps spécifiée (ou le délai) n'est pas le temps garanti à l'exécution, mais plutôt le temps minimum à l'exécution. Les rappels que vous passez à ces fonctions ne peuvent pas s'exécuter tant que la pile du processus principal n'est pas vide.

+

En conséquence, un code comme setTimeout(fn, 0) s'exécutera dès que la pile sera vide, pas immédiatement. Si vous exécutez un code comme setTimeout(fn, 0) mais qu'immédiatement après vous exécutez une boucle qui compte de 1 à 10 milliards, votre rappel sera exécuté après quelques secondes.

+
+ +

Dans l'exemple suivant, le navigateur attendra deux secondes avant d'exécuter la fonction anonyme, puis affichera le message d'alerte (voir son exécution en direct, et voir le code source) :

+ +
let maSalutation = setTimeout(() => {
+  console.log('Bonjour, M. Univers !');
+}, 2000);
+ +

Les fonctions que vous spécifiez n'ont pas besoin d'être anonymes. Vous pouvez donner un nom à votre fonction, et même la définir ailleurs et passer une référence de fonction à setTimeout(). Les deux versions suivantes de l'extrait de code sont équivalentes à la première :

+ +
// Avec une fonction nommée
+let maSalutation = setTimeout(function direBonjour() {
+  console.log('Bonjour, M. Univers !');
+}, 2000);
+
+// Avec une fonction définie séparément
+function direBonjour() {
+  console.log('Bonjour, M. Univers !');
+}
+
+let maSalutation = setTimeout(direBonjour, 2000);
+ +

Cela peut être utile si vous avez une fonction qui doit être appelée à la fois à partir d'un délai d'attente et en réponse à un événement, par exemple. Mais cela peut aussi vous aider à garder votre code en ordre, surtout si le rappel du délai d'attente représente plus de quelques lignes de code.

+ +

setTimeout() renvoie une valeur d'identifiant qui peut être utilisée pour faire référence au délai d'attente ultérieurement, par exemple lorsque vous souhaitez l'arrêter. Voir Effacement des délais d'attente (ci-dessous) pour apprendre comment faire cela.

+ +

Passage de paramètres à une fonction setTimeout()

+ +

Tous les paramètres que vous voulez passer à la fonction en cours d'exécution à l'intérieur du setTimeout() doivent lui être passés comme paramètres supplémentaires à la fin de la liste.

+ +

Par exemple, vous pouvez remanier la fonction précédente pour qu'elle dise bonjour à la personne dont le nom lui est transmis :

+ +
function direBonjour(who) {
+  console.log(`Bonjour ${who} !`);
+}
+ +

Maintenant, vous pouvez passer le nom de la personne dans l'appel setTimeout() comme troisième paramètre :

+ +
let maSalutation = setTimeout(direBonjour, 2000, 'M. Univers');
+ +

Effacement des délais d'attente

+ +

Enfin, si un timeout a été créé, vous pouvez l'annuler avant que le temps spécifié ne se soit écoulé en appelant clearTimeout(), en lui passant en paramètre l'identifiant de l'appel setTimeout(). Donc pour annuler notre timeout ci-dessus, vous feriez ceci :

+ +
clearTimeout(maSalutation);
+ +
+

Note : Voir greeter-app.html pour une démo un peu plus élaborée qui permet de définir le nom de la personne à saluer dans un formulaire, et d'annuler la salutation à l'aide d'un bouton séparé (voir aussi le code source).

+
+ +

setInterval()

+ +

setTimeout() fonctionne parfaitement lorsque vous devez exécuter du code une fois après une période de temps définie. Mais que se passe-t-il lorsque vous avez besoin d'exécuter le code encore et encore - par exemple, dans le cas d'une animation ?

+ +

C'est là qu'intervient le setInterval(). Cela fonctionne de manière très similaire à setTimeout(), sauf que la fonction que vous passez comme premier paramètre est exécutée de manière répétée à une fréquence égale au nombre de millisecondes donné par le deuxième paramètre distinct, plutôt qu'une seule fois. Vous pouvez également passer tous les paramètres requis par la fonction en cours d'exécution comme paramètres ultérieurs de l'appel setInterval().

+ +

Prenons un exemple. La fonction suivante crée un nouvel objet Date(), en extrait une chaîne de temps en utilisant toLocaleTimeString(), puis l'affiche dans l'interface utilisateur. Elle exécute ensuite la fonction une fois par seconde à l'aide de setInterval(), créant l'effet d'une horloge numérique qui se met à jour une fois par seconde (voir cela en direct, et aussi voir la source) :

+ +
function displayTime() {
+   let date = new Date();
+   let time = date.toLocaleTimeString();
+   document.getElementById('demo').textContent = time;
+}
+
+const createClock = setInterval(displayTime, 1000);
+ +

Tout comme setTimeout(), setInterval() renvoie une valeur d'identification que vous pouvez utiliser plus tard lorsque vous devez effacer l'intervalle.

+ +

Effacement des intervalles

+ +

setInterval() continue à exécuter une tâche pour toujours, à moins que vous ne fassiez quelque chose à ce sujet. Vous voudrez probablement un moyen d'arrêter de telles tâches, sinon vous pouvez finir par obtenir des erreurs lorsque le navigateur ne peut pas compléter d'autres versions de la tâche, ou si l'animation gérée par la tâche est terminée. Vous pouvez le faire de la même manière que vous arrêtez les temporisations - en passant l'identifiant renvoyé par l'appel setInterval() à la fonction clearInterval() :

+ +
const myInterval = setInterval(myFunction, 2000);
+
+clearInterval(myInterval);
+ +

Apprentissage actif : Créez votre propre chronomètre !

+ +

Tout ceci étant dit, nous avons un défi à vous proposer. Prenez une copie de notre exemple setInterval-clock.html, et modifiez-le pour créer votre propre chronomètre simple.

+ +

Vous devez afficher une heure comme précédemment, mais dans cet exemple, vous avez besoin :

+ + + +

Voici quelques conseils pour vous :

+ + + +
+

Note : Si vous êtes bloqué, vous pouvez trouver notre version ici (voir le code source).

+
+ +

Choses à garder à l'esprit concernant setTimeout() et setInterval()

+ +

Il y a quelques éléments à garder à l'esprit lorsque vous travaillez avec setTimeout() et setInterval(). Passons-les en revue maintenant.

+ +

Délais récursifs

+ +

Il existe une autre façon d'utiliser setTimeout() : vous pouvez l'appeler de manière récursive pour exécuter le même code de manière répétée, au lieu d'utiliser setInterval().

+ +

L'exemple ci-dessous utilise un setTimeout() récursif pour exécuter la fonction passée toutes les 100 millisecondes :

+ +
let i = 1;
+
+setTimeout(function run() {
+  console.log(i);
+  i++;
+  setTimeout(run, 100);
+}, 100);
+ +

Comparez l'exemple ci-dessus à celui qui suit - celui-ci utilise setInterval() pour accomplir le même effet :

+ +
let i = 1;
+
+setInterval(function run() {
+  console.log(i);
+  i++;
+}, 100);
+ +

Quelle est la différence entre le setTimeout() récursif et le setInterval() ?

+ +

La différence entre les deux versions du code ci-dessus est subtile.

+ + + +

Lorsque votre code a le potentiel de prendre plus de temps à s'exécuter que l'intervalle de temps que vous avez assigné, il est préférable d'utiliser le setTimeout() récursif - cela maintiendra l'intervalle de temps constant entre les exécutions, quelle que soit la durée d'exécution du code, et vous n'obtiendrez pas d'erreurs.

+ +

Délais immédiats

+ +

En utilisant 0 comme valeur pour setTimeout(), on planifie l'exécution de la fonction de rappel spécifiée dès que possible, mais seulement après l'exécution du processus de code principal.

+ +

Par exemple, le code ci-dessous (voir en direct) produit une alerte contenant "Hello", puis une alerte contenant "World" dès que vous cliquez sur OK sur la première alerte.

+ +
setTimeout(() => {
+  alert('World');
+}, 0);
+
+alert('Hello');
+ +

Cela peut être utile dans les cas où vous souhaitez définir un bloc de code à exécuter dès que l'ensemble du thread principal a terminé son exécution - placez-le dans la boucle d'événement asynchrone, de sorte qu'il s'exécutera immédiatement après.

+ +

Effacement avec clearTimeout() ou clearInterval()

+ +

clearTimeout() et clearInterval() utilisent toutes deux la même liste d'entrées pour effacer. Il est intéressant de noter que cela signifie que vous pouvez utiliser l'une comme l'autre méthode pour effacer un setTimeout() ou setInterval().

+ +

Par souci de cohérence, vous devriez utiliser clearTimeout() pour effacer les entrées setTimeout() et clearInterval() pour effacer les entrées setInterval(). Cela permettra d'éviter toute confusion.

+ +

requestAnimationFrame()

+ +

requestAnimationFrame() est une fonction de bouclage spécialisée, créée pour exécuter des animations de manière efficace dans le navigateur. Elle exécute un bloc de code spécifié avant que le navigateur ne repeigne ensuite l'affichage, ce qui permet d'exécuter une animation à une fréquence de rafraîchissement appropriée, quel que soit l'environnement dans lequel elle est exécutée.

+ +

Elle a été créée en réponse aux problèmes perçus avec les fonctions asynchrones antérieures comme setInterval(), qui, par exemple, ne s'exécute pas à une fréquence d'images optimisée pour le matériel et continue à s'exécuter alors qu'elle pourrait s'arrêter lorsque l'onglet n'est plus actif ou si l'animation se déroule hors de la page, etc.

+ +

(Plus d'informations à ce sujet sur CreativeJS (en).)

+ +
+

Note : Vous trouverez des exemples d'utilisation de requestAnimationFrame() ailleurs dans le cours - voir par exemple Dessiner des éléments graphiques, et La construction d'objet en pratique.

+
+ +

La méthode prend comme argument un rappel à invoquer avant le repeignage. C'est le modèle général dans lequel vous le verrez utilisé :

+ +
function draw() {
+   // Le code du dessin va ici
+   requestAnimationFrame(draw);
+}
+
+draw();
+ +

L'idée est de définir une fonction dans laquelle votre animation est mise à jour (par exemple, vos sprites sont déplacés, le score est mis à jour, les données sont rafraîchies, ou autre). Ensuite, vous l'appelez pour lancer le processus. À la fin du bloc de fonctions, vous appelez requestAnimationFrame() avec la référence de la fonction passée en paramètre, et cela indique au navigateur de rappeler la fonction lors du prochain rafraîchissement de l'affichage. Ceci est ensuite exécuté en continu, car le code appelle requestAnimationFrame() de manière récursive.

+ +
+

Note : Si vous souhaitez réaliser une sorte d'animation DOM simple et constante, les animations CSS sont probablement plus rapides. Elles sont calculées directement par le code interne du navigateur, plutôt que par JavaScript.

+

Si, toutefois, vous faites quelque chose de plus complexe et impliquant des objets qui ne sont pas directement accessibles à l'intérieur du DOM (comme les objets 2D Canvas API ou WebGL), requestAnimationFrame() est la meilleure option dans la plupart des cas.

+
+ +

Quelle est la vitesse de votre animation ?

+ +

La fluidité de votre animation dépend directement de la fréquence d'images de votre animation, qui est mesurée en images par seconde (ips). Plus ce nombre est élevé, plus votre animation sera fluide, jusqu'à un certain point.

+ +

Comme la plupart des écrans ont une fréquence de rafraîchissement de 60 Hz, la fréquence d'images la plus rapide que vous pouvez viser est de 60 images par seconde (IPS) lorsque vous travaillez avec des navigateurs Web. Cependant, plus d'images signifie plus de traitement, ce qui peut souvent provoquer des saccades et des sauts - également connus sous le nom de dégradation des images, ou saccades.

+ +

Si vous disposez d'un moniteur avec une fréquence de rafraîchissement de 60 Hz et que vous souhaitez obtenir 60 IPS, vous disposez d'environ 16,7 millisecondes (1000 / 60) pour exécuter votre code d'animation et rendre chaque image. Ceci est un rappel que vous devrez être attentif à la quantité de code que vous essayez d'exécuter pendant chaque passage dans la boucle d'animation.

+ +

requestAnimationFrame() essaie toujours de se rapprocher le plus possible de cette valeur magique de 60 IPS. Parfois, ce n'est pas possible - si vous avez une animation vraiment complexe et que vous l'exécutez sur un ordinateur lent, votre fréquence d'images sera inférieure. Dans tous les cas, requestAnimationFrame() fera toujours du mieux qu'il peut avec ce dont il dispose.

+ +

En quoi requestAnimationFrame() diffère-t-il de setInterval() et setTimeout() ?

+ +

Parlons un peu plus de la façon dont la méthode requestAnimationFrame() diffère des autres méthodes utilisées précédemment. En regardant notre code d'en haut :

+ +
function draw() {
+   // Le code du dessin va ici
+   requestAnimationFrame(draw);
+}
+
+draw();
+ +

Voyons maintenant comment faire la même chose en utilisant setInterval() :

+ +
function draw() {
+   // Le code du dessin va ici
+}
+
+setInterval(draw, 17);
+ +

Comme nous l'avons couvert précédemment, vous ne spécifiez pas d'intervalle de temps pour requestAnimationFrame(). Il l'exécute simplement aussi vite et aussi bien que possible dans les conditions actuelles. Le navigateur ne perd pas non plus de temps à l'exécuter si l'animation est hors écran pour une raison quelconque, etc.

+ +

setInterval(), d'autre part exige qu'un intervalle soit spécifié. Nous sommes arrivés à notre valeur finale de 17 via la formule 1000 millisecondes / 60Hz, puis nous l'avons arrondie. Arrondir vers le haut est une bonne idée ; si vous arrondissez vers le bas, le navigateur pourrait essayer d'exécuter l'animation à une vitesse supérieure à 60 FPS, et cela ne ferait de toute façon aucune différence pour la fluidité de l'animation. Comme nous l'avons déjà dit, 60 Hz est la fréquence de rafraîchissement standard.

+ +

Inclure un horodatage

+ +

Le rappel réel passé à la fonction requestAnimationFrame() peut également recevoir un paramètre : une valeur timestamp, qui représente le temps depuis que le requestAnimationFrame() a commencé à s'exécuter.

+ +

C'est utile car cela vous permet d'exécuter des choses à des moments précis et à un rythme constant, quelle que soit la vitesse ou la lenteur de votre appareil. Le modèle général que vous utiliserez ressemble à quelque chose comme ceci :

+ +
let startTime = null;
+
+function draw(timestamp) {
+    if (!startTime) {
+      startTime = timestamp;
+    }
+
+   currentTime = timestamp - startTime;
+
+   // Faire quelque chose en fonction du temps actuel
+
+   requestAnimationFrame(draw);
+}
+
+draw();
+ +

Prise en charge des navigateurs

+ +

requestAnimationFrame() est supporté par des navigateurs plus récents que pour setInterval()/setTimeout(). Il est intéressant de noter qu'elle est disponible dans Internet Explorer 10 et plus.

+ +

Ainsi, à moins que vous ne deviez prendre en charge d'anciennes versions d'IE, il y a peu de raisons de ne pas utiliser requestAnimationFrame().

+ +

Un exemple simple

+ +

Assez avec la théorie ! Construisons votre propre exemple personnel de requestAnimationFrame(). Vous allez créer une simple "animation de toupie" - le genre que vous pourriez voir affiché dans une application lorsqu'elle est occupée à se connecter au serveur, etc.

+ +
+

Note : Dans un exemple du monde réel, vous devriez probablement utiliser des animations CSS pour exécuter ce type d'animation simple. Cependant, ce genre d'exemple est très utile pour démontrer l'utilisation de requestAnimationFrame(), et vous seriez plus susceptible d'utiliser ce genre de technique lorsque vous faites quelque chose de plus complexe comme la mise à jour de l'affichage d'un jeu à chaque image.

+
+ +
    +
  1. Prenez un modèle HTML de base (comme celui-ci).

  2. +
  3. Placez un élément <div> vide à l'intérieur de l'élément <body>, puis ajoutez un caractère ↻ à l'intérieur. Ce caractère de flèche circulaire fera office de notre toupie pour cet exemple.

  4. +
  5. Appliquez le CSS suivant au modèle HTML (de la manière que vous préférez). Cela définit un fond rouge sur la page, définit la hauteur du <body> à 100% de la hauteur de <html>, et centre le <div> à l'intérieur du <body>, horizontalement et verticalement.

    +
    html {
    +  background-color: white;
    +  height: 100%;
    +}
    +
    +body {
    +  height: inherit;
    +  background-color: red;
    +  margin: 0;
    +  display: flex;
    +  justify-content: center;
    +  align-items: center;
    +}
    +
    +div {
    +  display: inline-block;
    +  font-size: 10rem;
    +}
    +
  6. +
  7. Insérez un élément <script> juste au-dessus de la balise de fermeture </body>.

  8. +
  9. Insérez le JavaScript suivant dans votre élément <script>. Ici, vous stockez une référence à l'élément <div> ; à l'intérieur d'une constante, définissez une variable rotateCount à 0, définissez une variable non initialisée qui sera utilisée plus tard pour contenir une référence à l'appel requestAnimationFrame(), et en définissant une variable startTime à null, qui sera plus tard utilisée pour stocker l'heure de début de l'appel requestAnimationFrame().

    +
    const spinner = document.querySelector('div');
    +let rotateCount = 0;
    +let startTime = null;
    +let rAF;
    +
    +
  10. +
  11. +

    Sous le code précédent, insérez une fonction draw() qui sera utilisée pour contenir notre code d'animation, qui inclut le paramètre timestamp :

    +
    function draw(timestamp) {
    +
    +}
    +
  12. +
  13. À l'intérieur de draw(), ajoutez les lignes suivantes. Elles définiront l'heure de départ si elle n'est pas déjà définie (cela ne se produira que lors de la première itération de la boucle), et définiront le rotateCount à une valeur pour faire tourner le spinner par (l'horodatage actuel, prendre l'horodatage de départ, divisé par trois pour que cela n'aille pas trop vite) :

    +
      if(!startTime) {
    +   startTime = timestamp;
    +  }
    +
    +  rotateCount = (timestamp - startTime) / 3;
    +
    +
  14. +
  15. Sous la ligne précédente à l'intérieur de draw(), ajoutez le bloc suivant - il s'assure que la valeur de rotateCount est comprise entre 0 et 360 en utilisant le modulo à 360 (c'est-à-dire le reste restant lorsque la valeur est divisée par 360) afin que l'animation du cercle puisse continuer sans interruption, à une valeur basse et raisonnable. Notez que ce n'est pas strictement nécessaire, mais il est plus facile de travailler avec des valeurs de 0-359 degrés que des valeurs comme "128000 degrés".

    +
    rotateCount %= 360;
    +
  16. +
  17. Ensuite, sous le bloc précédent, ajoutez la ligne suivante pour faire tourner le spinner : +
    spinner.style.transform = `rotate(${rotateCount}deg)`;
    +
  18. +
  19. Tout en bas, à l'intérieur de la fonction draw(), insérez la ligne suivante. C'est la clé de toute l'opération - vous définissez la variable définie précédemment à un appel actif requestAnimation(), qui prend la fonction draw() comme paramètre. Cela fait démarrer l'animation, en exécutant constamment la fonction draw() à un taux aussi proche que possible de 60 IPS.

    +
    rAF = requestAnimationFrame(draw);
    +
  20. +
  21. Sous la définition de la fonction draw(), ajoutez un appel à la fonction draw() pour lancer l'animation.

    +
    draw();
    +
  22. +
+ +
+

Note : Vous pouvez trouver l'exemple terminé en direct sur GitHub. (Vous pouvez également voir le code source).

+
+ +

Effacer un appel de requestAnimationFrame()

+ +

Effacer un appel de requestAnimationFrame() peut être fait en appelant la méthode cancelAnimationFrame() correspondante. (Notez que le nom de la fonction commence par "cancel", et non par "clear" comme pour les méthodes "set...").

+ +

Il suffit de lui passer la valeur renvoyée par l'appel requestAnimationFrame() à annuler, que vous avez stockée dans la variable rAF :

+ +
cancelAnimationFrame(rAF);
+ +

Apprentissage actif : Démarrer et arrêter la toupie

+ +

Dans cet exercice, nous aimerions que vous testiez la méthode cancelAnimationFrame() en prenant notre exemple précédent et en le mettant à jour, en ajoutant un écouteur d'événements pour démarrer et arrêter le spinner lorsque la souris est cliquée n'importe où sur la page.

+ +

Quelques conseils :

+ + + +
+

Note : Essayez d'abord vous-même ; si vous êtes vraiment bloqué, consultez nos pages exemple en direct et code source.

+
+ +

Ralentissement d'une animation requestAnimationFrame()

+ +

Une limitation de requestAnimationFrame() est que vous ne pouvez pas choisir votre fréquence d'images. Ce n'est pas un problème la plupart du temps, car en général, vous voulez que votre animation se déroule de la manière la plus fluide possible. Mais qu'en est-il lorsque vous souhaitez créer une animation old school, de style 8 bits ?

+ +

C'était un problème, par exemple, dans l'animation de marche inspirée de Monkey Island de notre article Dessiner des éléments graphiques :

+ +

{{EmbedGHLiveSample("learning-area/javascript/apis/drawing-graphics/loops_animation/7_canvas_walking_animation.html", '100%', 260)}}

+ +

Dans cet exemple, vous devez animer à la fois la position du personnage à l'écran et le sprite affiché. Il n'y a que 6 images dans l'animation du sprite. Si vous montriez une image différente du sprite pour chaque image affichée à l'écran par requestAnimationFrame(), Guybrush bougerait ses membres trop vite et l'animation aurait l'air ridicule. Cet exemple étrangle la vitesse à laquelle le sprite cycle ses images en utilisant le code suivant :

+ +
if (posX % 13 === 0) {
+  if (sprite === 5) {
+    sprite = 0;
+  } else {
+    sprite++;
+  }
+}
+ +

Ainsi, le code ne cycle le sprite qu'une fois toutes les 13 images d'animation.

+ +

...En fait, c'est environ toutes les 6,5 images, car nous mettons à jour posX (position du personnage sur l'écran) par deux à chaque image :

+ +
if (posX > width/2) {
+  newStartPos = -( (width/2) + 102 );
+  posX = Math.ceil(newStartPos / 13) * 13;
+  console.log(posX);
+} else {
+  posX += 2;
+}
+ +

C'est le code qui calcule comment mettre à jour la position dans chaque image d'animation.

+ +

La méthode que vous utilisez pour accélérer votre animation dépendra de votre code particulier. Par exemple, dans l'exemple précédent de la toupie, vous pourriez faire en sorte qu'elle semble se déplacer plus lentement en n'augmentant le rotateCount que de un à chaque image, au lieu de deux.

+ +

Apprentissage actif : un jeu de réaction

+ +

Pour la dernière section de cet article, vous allez créer un jeu de réaction à 2 joueurs. Le jeu aura deux joueurs, dont l'un contrôlera le jeu à l'aide de la touche A, et l'autre avec la touche L.

+ +

Lorsque l'on appuie sur le bouton Start, une toupie comme celle que nous avons vue précédemment s'affiche pendant un temps aléatoire compris entre 5 et 10 secondes. Après ce temps, un message apparaîtra disant "PLAYERS GO !!" - une fois que cela se produit, le premier joueur à appuyer sur son bouton de contrôle gagnera la partie.

+ +

{{EmbedGHLiveSample("learning-area/javascript/asynchronous/loops-and-intervals/reaction-game.html", '100%', 500)}}

+ +

Travaillons ensemble :

+ +
    +
  1. Tout d'abord, téléchargez le fichier de démarrage de l'application. Celui-ci contient la structure HTML et le style CSS finis, ce qui nous donne un plateau de jeu qui affiche les informations des deux joueurs (comme vu ci-dessus), mais avec le spinner et le paragraphe des résultats affichés l'un au-dessus de l'autre. Il ne vous reste plus qu'à écrire le code JavaScript.

  2. +
  3. À l'intérieur de l'élément vide <script> de votre page, commencez par ajouter les lignes de code suivantes qui définissent certaines constantes et variables dont vous aurez besoin dans le reste du code :

    +
    const spinner = document.querySelector('.spinner p');
    +const spinnerContainer = document.querySelector('.spinner');
    +let rotateCount = 0;
    +let startTime = null;
    +let rAF;
    +const btn = document.querySelector('button');
    +const result = document.querySelector('.result');
    +

    Dans l'ordre, ce sont :

    +
      +
    1. Une référence à la toupie, afin que vous puissiez l'animer.
    2. +
    3. Une référence à l'élément <div> qui contient la toupie, utilisée pour l'afficher et la masquer.
    4. +
    5. Un nombre de rotations. Ce paramètre détermine le nombre de rotations de la toupie que vous souhaitez afficher à chaque image de l'animation.
    6. +
    7. Un temps de démarrage nul. Il sera rempli avec une heure de début lorsque la toupie commencera à tourner.
    8. +
    9. Une variable non initialisée pour stocker ultérieurement l'appel requestAnimationFrame() qui anime la toupie.
    10. +
    11. Une référence au bouton de démarrage.
    12. +
    13. Une référence au paragraphe des résultats.
    14. +
    +
  4. +
  5. Ensuite, sous les lignes de code précédentes, ajoutez la fonction suivante. Elle prend deux nombres et retourne un nombre aléatoire entre les deux. Vous en aurez besoin pour générer un intervalle de temps aléatoire plus tard.

    +
    function random(min,max) {
    +  var num = Math.floor(Math.random()*(max-min)) + min;
    +  return num;
    +}
    +
  6. +
  7. Ajoutez ensuite la fonction draw(), qui anime la toupie. Cette version est très similaire à celle de l'exemple simple de la toupie, plus haut :

    +
    function draw(timestamp) {
    +  if(!startTime) {
    +   startTime = timestamp;
    +  }
    +
    +  rotateCount = (timestamp - startTime) / 3;
    +
    +  rotateCount %= 360;
    +
    +  spinner.style.transform = 'rotate(' + rotateCount + 'deg)';
    +  rAF = requestAnimationFrame(draw);
    +}
    +
  8. +
  9. Il est maintenant temps de mettre en place l'état initial de l'application lors du premier chargement de la page. Ajoutez les deux lignes suivantes, qui masquent le paragraphe des résultats et le conteneur de la toupie en utilisant display : none;.

    +
    result.style.display = 'none';
    +spinnerContainer.style.display = 'none';
    +
  10. +
  11. Ensuite, définissez une fonction reset(), qui remet l'application dans l'état initial nécessaire pour relancer le jeu après qu'il a été joué. Ajoutez ce qui suit au bas de votre code :

    +
    function reset() {
    +  btn.style.display = 'block';
    +  result.textContent = '';
    +  result.style.display = 'none';
    +}
    +
  12. +
  13. Bon, assez de préparation ! Il est temps de rendre le jeu jouable ! Ajoutez le bloc suivant à votre code. La fonction start() appelle draw() pour commencer à faire tourner la toupie et l'afficher dans l'interface utilisateur, cache le bouton Start pour que vous ne puissiez pas perturber le jeu en le démarrant plusieurs fois simultanément, et exécute un appel setTimeout() qui exécute une fonction setEndgame() après qu'un intervalle aléatoire entre 5 et 10 secondes se soit écoulé. Le bloc suivant ajoute également un écouteur d'événements à votre bouton pour exécuter la fonction start() lorsqu'il est cliqué.

    +
    btn.addEventListener('click', start);
    +
    +function start() {
    +  draw();
    +  spinnerContainer.style.display = 'block';
    +  btn.style.display = 'none';
    +  setTimeout(setEndgame, random(5000,10000));
    +}
    +
    +

    Note : Vous verrez que cet exemple appelle setTimeout() sans stocker la valeur de retour. (Donc, pas let myTimeout = setTimeout(functionName, interval).)

    +

    Cela fonctionne très bien, tant que vous n'avez pas besoin d'effacer votre intervalle/temps d'attente à un moment donné. Si vous le faites, vous devrez sauvegarder l'identifiant renvoyé !

    +
    +

    Le résultat net du code précédent est que lorsque le bouton Start est pressé, la toupie est affichée et les joueurs sont amenés à attendre un temps aléatoire avant d'être invités à appuyer sur leur bouton. Cette dernière partie est gérée par la fonction setEndgame(), que vous allez définir ensuite.

    +
  14. +
  15. Ajoutez ensuite la fonction suivante à votre code :

    +
    function setEndgame() {
    +  cancelAnimationFrame(rAF);
    +  spinnerContainer.style.display = 'none';
    +  result.style.display = 'block';
    +  result.textContent = 'JOUEURS : ALLEZ-Y !!';
    +
    +  document.addEventListener('keydown', keyHandler);
    +
    +  function keyHandler(e) {
    +    let isOver = false;
    +    console.log(e.key);
    +
    +    if (e.key === "a") {
    +      result.textContent = 'Le joueur 1 a gagné !!';
    +      isOver = true;
    +    } else if (e.key === "l") {
    +      result.textContent = 'Le joueur 2 a gagné !!';
    +      isOver = true;
    +    }
    +
    +    if (isOver) {
    +      document.removeEventListener('keydown', keyHandler);
    +      setTimeout(reset, 5000);
    +    }
    +  };
    +}
    +

    Son fonctionnement :

    +
      +
    1. Tout d'abord, annule l'animation de la toupie avec cancelAnimationFrame() (il est toujours bon de nettoyer les processus inutiles), et cache le conteneur de la toupie.
    2. +
    3. Ensuite, affiche le paragraphe des résultats et définissez son contenu textuel sur "JOUEURS : ALLEZ-Y !!" pour signaler aux joueurs qu'ils peuvent maintenant appuyer sur leur bouton pour gagner.
    4. +
    5. Attache un écouteur d'événements keydown au document. Lorsqu'un bouton quelconque est enfoncé, la fonction keyHandler() est exécutée.
    6. +
    7. À l'intérieur de keyHandler(), le code inclut l'objet événement en tant que paramètre (représenté par e) - sa propriété key contient la touche qui vient d'être pressée, et vous pouvez l'utiliser pour répondre à des pressions de touche spécifiques par des actions spécifiques.
    8. +
    9. Définit la variable isOver à false, afin que nous puissions suivre si les bonnes touches ont été pressées pour que le joueur 1 ou 2 gagne. Nous ne voulons pas que le jeu se termine lorsqu'une mauvaise touche a été pressée.
    10. +
    11. Enregistre e.key dans la console, ce qui est un moyen utile de connaître la valeur key des différentes touches sur lesquelles vous appuyez.
    12. +
    13. Lorsque e.key est "a", affiche un message pour dire que le joueur 1 a gagné, et lorsque e.key est "l", affiche un message pour dire que le joueur 2 a gagné. (Note: Cela ne fonctionnera qu'avec les minuscules a et l - si un A ou un L majuscule est soumis (la touche plus Shift), il est compté comme une touche différente !). Si une de ces touches a été pressée, mettez isOver à true.
    14. +
    15. Seulement si isOver est true, supprime l'écouteur d'événements keydown en utilisant removeEventListener() de sorte qu'une fois que l'appui gagnant s'est produit, plus aucune entrée clavier n'est possible pour perturber le résultat final du jeu. Vous utilisez également setTimeout() pour appeler reset() après 5 secondes - comme expliqué précédemment, cette fonction réinitialise le jeu à son état initial afin qu'une nouvelle partie puisse être lancée.
    16. +
    +
  16. +
+ +

Voilà, c'est fait !

+ +
+

Note : Si vous êtes bloqué, consultez notre version du jeu en live (voir également le code source).

+
+ +

Conclusion

+ +

Voilà, tous les éléments essentiels des boucles et des intervalles asynchrones sont couverts dans un seul article. Vous trouverez ces méthodes utiles dans de nombreuses situations, mais veillez à ne pas en abuser ! Parce qu'ils s'exécutent toujours sur le processus principal, les rappels lourds et intensifs (en particulier ceux qui manipulent le DOM) peuvent vraiment ralentir une page si vous ne faites pas attention.

+ +

{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Introducing", "Learn/JavaScript/Asynchronous/Promises", "Learn/JavaScript/Asynchronous")}}

+ +

Dans ce module

+ + -- cgit v1.2.3-54-g00ecf