Aller au contenu

1NSI : Les Promesses en JS⚓︎

Introduction⚓︎

En Javascript, dans l'ancien temps (pour les versions ES5 / ES2009, jusqu'en 2015), pour réaliser des requêtes asynchrones de données via le réseau, on utilisait des Callbacks.
Les Callbacks sont des fonctions JS qui n'avaient réellement de sens, que lorsqu'elles étaient déclenchées après qu'un certain événement se soit produit, comme par ex. après un clic de souris. Bien que beaucoup plus illisible dans ce cas, elles étaient également utilisées dans le cas où il fallait appeler une (autre) fonction APRÈS avoir fini d'exécuter une fonction.

Il est fréquent de souhaiter tirer parti des fonctionnalités de programmation asynchrone de Javascript, plus précisément, il est fréquent de faire des requêtes de données asynchrones vers des API (interfaces de programmation), externes (sites tiers qui vous offrent des services de livraison de données - gratuitement ou pas) ou internes (votre propre site, votre base de données, etc..). Ces données mettent donc un certain temps à arriver vers votre code javascript, et il est fréquent de souhaiter d'avoir la certitude que ces données sont bien arrivées dans votre code, AVANT de poursuivre vers d'autres tâches, par exemple une nouvelle requête asynchrone, etc..

Avec l'ancienne méthode, en utilisant les callbacks, on se retrouvait ainsi rapidement enfermé dans ce que l'on appelle l'enfer des callbacks (code illisible, et non maintainable).

Pour résoudre cela, on a inventé les Promesses en Javascript. Intuitivement, on pourra penser à une promesse de envoi/réception de données depuis un serveur (externe - avec une certaine API-, ou interne -depuis mon serveur perso-). Ou plus généralement, à la promesse d'un certain traitement à effectuer.

Les Promesses JS existent depuis ES2015.

  • Pourquoi on devrait les utiliser ?
  • Qu'est-ce qu'une promesse ?
  • Comment les utiliser ?

Promesses⚓︎

Fonctions renvoyant une Promesse⚓︎

Les fonctions synchrones ("normales") en javascript renvoient des résultats. Par opposition, les fonctions asynchrones renvoient des promesses (qui sont des objets, au sens du javascript). Pas toutes les fonctions javascript ne renvoient (ou ne peuvent renvoyer) des promesses. Il faut lire la documentation javascript, pour savoir si telle ou telle fonction renvoie une promesse (asynchrone) ou un résultat (synchrone). Il est possible également de créer/programmer nos propres fonctions asynchrones qui renvoient une promesse (avec la syntaxe new Promise que nous verrons plus tard). Pour le moment, voyons comment utiliser la fonction fetch (interne au javascript) qui demande des données sur un serveur situé à une certaine url :

de Syntaxe de la fonction fetch

La fonction fetch est une fonction interne au javascript, qui renvoie une promesse (de renvoi de données), dont voici une syntaxe possible :

let promesse = fetch(url)

Mais le principe d'une promesse est d'être un objet qui se trouve dans un certain état, parmi les suivants :

  1. pending : dans l'attente de recevoir les données
  2. fulfilled : la requête a bien été résolue par le serveur (celui à qui on a demandé les données), ET j'ai bien reçu les données
  3. rejected : une erreur s'est produite

mot-clé .then() : APRÈS la résolution de la promesse⚓︎

Le mot-clé .then(fonctionAExecuterApresReception) permet au javascript de gérer en interne le suivi de ma requête asynchrone (je n'ai pas besoin de régulièrement demander dans quel état de résolution elle se trouve, c'est alors le javascript qui s'en charge). La fonction fonctionAExecuterApresReception donnée en paramètre à l'intérieur (entre les parentèses) de .then() sera exécutée/appelée APRÈS QUE la requête sera fulfilled (un peu comme une callback..). En résumé :

  • on demande une promesse
  • PUIS / .then() on exécute la fonction fonctionAExecuterApresReception (si/lorsque la promesse est dans l'état fulfilled)

mot-clé .catch() : EN CAS D'ERREUR de la résolution de la promesse⚓︎

Le mot-clé .catch(fonctionAExecuterEnCasDErreur) permet au javascript de détecter en interne une éventuelle erreur lors du suivi de ma requête asynchrone (je n'ai pas besoin de régulièrement demander dans quel état de résolution elle se trouve, c'est alors le javascript qui s'en charge). La fonction fonctionAExecuterEnCasDErreur donnée en paramètre à l'intérieur (entre les parentèses) de .catch() sera exécutée/appelée EN CAS D'ERREUR de la requête, si jamais elle arrive dans l'état rejected (un peu comme une callback..). En résumé :

  • on demande une promesse
  • PUIS / .then() on exécute la suite du code (si/lorsque la promesse est dans l'état fulfilled)
  • ou bien, ON ATTRAPE/LÈVE (DÉTECTE ET GÈRE) UNE ERREUR / .catch() auquel cas, on exécute la fonction fonctionAExecuterEnCasDErreur(si/lorsque la promesse est dans l'étatrejected`)

Un exemple de requête asynchrone⚓︎

pour voir Promise (dans l'état pending)⚓︎

1
2
3
4
let wordnikAPI = "https://api.wordnik.com/v4/words.json/randomWord"

let promise = fetch(wordnikAPI)
console.log(promise);

pour voir Response (dans l'état fulfilled)⚓︎

Les données ont été reçues :

1
2
3
4
5
6
7
8
let wordnikAPI = "https://api.wordnik.com/v4/words.json/randomWord"

let promise = fetch(wordnikAPI);
promise.then(donneesRecues)

const donneesRecues = (data) => {
    console.log(data)
}

Usuellement, on ne prend même pas la peine de nommer les fonctions, et on préfère utiliser séquentiellement/chaîner des fonctions anonymes/non nommées, avec les notations/convention des fonctions fléchées. Le code qui suit est équivalent au code précédent :

1
2
3
4
5
let wordnikAPI = "https://api.wordnik.com/v4/words.json/randomWord"

let promise = fetch(wordnikAPI);
promise
    .then( (data) => console.log(data) )

MAIS elles ne sont pas encore exploitables en l'état, car elles ne sont pas dans un format de données lisible par javascript: il faut encore les convertir dans un format de données texte qui soit lisible par du javascript, classiquement il faut les convertir au format JSON (cf paragraphe plus bas)

Et s'il y avait une erreur .catch()⚓︎

Pour simuler une erreur, vous pouvez (ATTENTION) modifier l'URL : par ex. ajouter/supprimer un caractère dans l'URL .. (ATTENTION : savoir revenir à l'URL correcte..)

1
2
3
4
5
6
let wordnikAPI = "https://api.wordnik.com/v4/words.json/randomWord"

let promise = fetch(wordnikAPI);
promise
    .then( (reponse) => { return reponse.json() } )
    .catch( (err) => console.log(err) );

Vous devriez recevoir l'erreur : TypeError: fetch failed

Données au format JSON⚓︎

Remarquons tout d'abord, qu'en fait, ce que nous avons noté data dans le code ci-dessus, ne sont PAS les données reçues à proprement parler, mais plutôt la réponse à la promesse : c'est pourquoi nous la noterons désormais reponse (au lieu de data).
Ensuite, pour transformer la réponse reponse de la promesse, au format de données json, que nous noterons donnesJson: on peut faire cela en chaînant la fonction json() (qui est une fonction interne au javascript) à la réponse response.
Notons également que la fonction json() renvoie elle-aussi une promesse... D'où le code :

1
2
3
4
5
6
7
let wordnikAPI = "https://api.wordnik.com/v4/words.json/randomWord"

let promise = fetch(wordnikAPI);
promise
    .then( (reponse) => { return reponse.json() } )
    .then( (donneesJson) => { return donneesJson.word } )
    .catch( (err) => console.log(err) );

Warning

  • ⚠ ATTENTION ⚠ : Pour chaîner plusieurs .then() l'un derrière l'autre, il FAUT OBLIGATOIREMENT que CHAQUE .then() renvoie en return les données (ou les promesses de données) pour l'étape d'après, éventuellement modifiées selon notre convenance. Dans notre exemple, nous avons choisi de renvoyer donneesJson.word (plutôt que simplement donneesJson) car cela vient de l'API de wordnik
  • nous avons utilisé la syntaxe abrégée des fonctions fléchées, mais il va falloir ajouter des return dans chaque .then()

Références⚓︎