Advanced Typescript
Javascript/Promises

Promesses

Les environnements d'exécution JavaScript, que ce soit Node.js ou votre navigateur, fournissent de nombreuses fonctions qui permettent de planifier des actions asynchrones.
Autrement dit, des actions que nous initions maintenant, mais qui se terminent plus tard.

Qu'est ce qu'une promesse?

Imaginez que vous commandez un repas dans un fast-food. 🍔 Pour éviter que vous n'attendiez au comptoir, le caissier vous remet un buzzer vibrant une fois votre commande passée. Vous pouvez aller vous asseoir tranquillement. Lorsque votre plateau sera prêt, le buzzer s'allumera. Et même si quelque chose se passe mal, disons une panne de friteuse, un manager viendra vous voir pour vous prévenir.

Tout le monde est content : vous, car vous n'attendez pas debout, et le restaurant, car le comptoir n'est pas encombré. C'est une analogie concrète pour des situations que l'on rencontre souvent en programmation :

Un « code producteur » qui effectue une tâche qui prend du temps. Dans cet exemple, c'est la « cuisine » qui prépare votre commande.

Un « code consommateur » qui attend le résultat du « code producteur » une fois qu'il est prêt. C'est « vous, le client ».

Une promesse (Promise) est un objet spécial en JavaScript qui fait le lien entre le « code producteur » et le « code consommateur ». Dans notre analogie, c'est le « buzzer vibrant ». Le « code producteur » (la cuisine) prend le temps qu'il faut pour fournir le résultat promis, et la « promesse » (le buzzer) notifie le code consommateur (vous) lorsque ce résultat est enfin disponible.

En pratique

La syntaxe pour faire appel à une promesse ressemble à ça:

const kebabOrder = new Promise(function (resolve, reject) {
  // code qui s'éxécutera lorsque la promesse sera résolu (le code producteur)
  // la cuisine quoi
})

La fonction passée à new Promise est appelée l'executor. Quand une new Promise est créée, l'executor s'exécute automatiquement. Il contient le "code producteur" qui doit, à terme, fournir un résultat. Pour reprendre l'analogie précédente : l'executor est la préparation du kebab.

Ses arguments resolve et reject sont des callbacks fournis par JavaScript lui-même. Notre propre code se trouve uniquement à l'intérieur de l'executor.

Quand l'executor obtient le résultat (peu importe que ce soit tôt ou tard), il doit appeler l'un de ces callbacks :

  • resolve(value) — si la tâche s'est terminée avec succès, avec le résultat value.
  • reject(error) — si une erreur est survenue, error est l'objet représentant cette erreur.

Pour résumer : l'executor s'exécute automatiquement et tente d'accomplir une tâche. Lorsqu'il a terminé, il appelle resolve si tout s'est bien passé, ou reject en cas d'erreur.

L'objet promise retourné par le constructeur new Promise possède les propriétés internes suivantes :

  • state — initialement à "pending", puis change soit pour "fulfilled" (quand resolve est appelée), soit pour "rejected" (quand reject est appelée).

  • result — initialement à undefined, puis change pour la value (quand resolve(value) est appelée) ou pour l' error (quand reject(error) est appelée).

new Promise(executor)state: "pending" result: undefinedresolve(value)reject(error)state: "fulfilled" result: valuestate: "rejected" result: error

Utiliser une promesse

Maintenant que notre "cuisine" (l'executor) sait comment produire un résultat, il faut que "nous, le client" (le code consommateur) puissions le recevoir.

On peut "s'abonner" au résultat d'une promise en utilisant les méthodes .then(), .catch() et .finally().

.then()

C'est la méthode la plus importante.
Elle permet de définir ce qu'il faut faire une fois que la promesse est tenue.

.then() prend jusqu'à deux arguments: 1 - Une fonction à exécuter quand la promesse est fulfilled (résolue avec succès). Elle reçoit la value. 2 - Une fonction à exécuter quand la promesse est rejected (en erreur). Elle reçoit l'error.

// On reprend notre exemple de commande de kebab
const kebabOrder = new Promise(function (resolve, reject) {
  // On simule une préparation de 2 secondes
  setTimeout(() => {
    // Le kebab est prêt !
    resolve({ type: 'Kebab', sauces: ['Blanche'] }); 
  }, 2000);
});

// Le client attend...
kebabOrder.then( // [\!code highlight]
  // 1er argument : que faire en cas de succès
  function(kebab) { 
    console.log(`Miam, un bon ${kebab.type} sauce ${kebab.sauces.join(', ')} !`); // Affiche le message après 2s
  },
  // 2ème argument : que faire en cas d'erreur
  function(error) {
    console.error(`Oh non, il y a eu un problème : ${error.message}`);
  }
);

.catch()

Si on s'intéresse uniquement au cas d'erreur, on peut utiliser la méthode .catch(errorCallback).
C'est une syntaxe plus propre et plus lisible que .then(null, errorCallback).

const orderWithMistake = new Promise(function (resolve, reject) {
  setTimeout(() => {
    // Oh non, plus de pain!
    reject(new Error("Plus de pain à kebab!"));
  }, 1000);
});

orderWithMistake.catch(
  // La fonction ne s'exécute que si la promesse est rejetée
  function(error) { 
    console.error(`Une erreur est survenue dans la cuisine : ${error.message}`);
  }
);

.finally()

Parfois, on veut exécuter un code que la promesse soit resolue ou rejeté. Par exemple, pour arrêter une animation de chargement.
C'est le rôle de .finally().

La fonction passée à .finally() ne prend aucun argument, car elle ne sait pas si l'opération a réussi ou échoué.

orderWithMistake.finally(
  function() {
    console.log("La commande est terminée, le buzzer s'éteint.");
  }
);