Description

Apprenons à quoi sert une Promise et comment l'utiliser pour mieux gérer nos appels asynchrones en javascript et éviter le callback hell
Arthur, l'apprenti développeurSalut, je viens vers toi aujourd'hui car je rencontre un problème. Pour un projet que je réalise dans mon entreprise, je dois faire des appels Ajax, et mon tuteur m'a dit d'utiliser une Promise, mais je ne comprends pas ce que c'est.

Ah les Promises, c'est un sujet qui parait toujours complexe au premier abord, mais je vais t'expliquer, tu vas voir c'est très simple. Tout d'abord, par de la traduction littéraire, sais-tu ce que veut dire Promise en Français ?

Arthur, l'apprenti développeurJe crois que c'est "promesse", mais je ne vois pas trop le rapport.

C'est exactement ça, en fait une Promise est un objet auquel on demande de réaliser un traitement, et qui nous "promet" de nous répondre dès que celui ci est terminé, qu'il se soit bien passé ou non.

Arthur, l'apprenti développeurD'accord, mais quelle est l'utilité ? Pourquoi ne pas faire directement le traitement ?

Tout simplement parce qu'elles sont prévues pour être utilisées avec des instructions asynchrones.

Arthur, l'apprenti développeurDe l'Ajax donc !

De l'Ajax par exemple, mais pas que de l'Ajax ! En fait en javascript beaucoup d'instructions peuvent être asynchrones. Ce qui signifie tout simplement que l'exécution du code va continuer après une instruction asynchrone et avant même que celle ci ne soit terminée. L'exemple le plus simple est le timeout, j'imagine que tu en as déjà faits en javascript?

Arthur, l'apprenti développeurJe crois que ça m'est déjà arrivé, si je me souviens bien, on utilise setTimeout pour exécuter du code après un certain temps.

Tout à fait, en voici un petit exemple :

{"language":"application/json","content":"setTimeout(function(){\n alert(\"Hello\");\n},1500);","filename":"exemple"}


Ici on va afficher un alert avec écrit "Hello" après 1,5 secondes. Ici la fonction passée en premier paramètre est appelée un callback, car son code va être appelé (call) après (back) certaines conditions.

Arthur, l'apprenti développeurJusque là c'est simple, mais je ne vois pas l'utilité d'une Promise.

Le problème est que dans certains cas, on va utiliser différentes instructions asynchrones les unes imbriquées dans les autres et qui chacune sont un callback de la précédente. Le code devient alors très rapidement difficile à lire, et surtout très complexe pour identifier dans quel ordre seront exécutées les instructions. Voici un exemple, relativement simple, mais déjà très pertinent pour comprendre le problème. Saurais-tu me dire dans quel ordre vont être exécutées les console.log 1,2,3 et 4?

{"language":"application/json","content":"document.getElementById(\"myButton\").addEventListener(\"click\",function(){\n \tvar xhr = new XMLHttpRequest();\n xhr.open(\"GET\", '/url', true);\n xhr.onreadystatechange = function() {\n if (this.readyState === XMLHttpRequest.DONE && this.status === 200) {\n setTimeout(function(){\n console.log(1);\n },500);\n \tconsole.log(2);\n }\n }\n xhr.send(); \n \tconsole.log(3);\n}\nconsole.log(4);","filename":"exemple"}


Arthur, l'apprenti développeurEuh, hé bien je dirais 1,2,3 et 4 dans l'ordre.

Hé bien pas du tout ! La console va afficher 4 au chargement du script. Puis quand on clique sur le bouton "myButton", on va voir s'afficher dans l'ordre 3,2 et 1.

Arthur, l'apprenti développeurMais... C'est totalement l'inverse! Comment ça se fait ?

Je ne vais pas entrer dans le détails, mais comme on l'a vu précédemment, il y a ici plusieurs callback les uns dans les autres, et l'ordre d'exécution du code en devient illogique par rapport à l'ordre de lecture du code. C'est ce que l'on appelle le callback hell, ou l'enfer des callback.

Et encore mon exemple est assez simple, on pourrait avoir beaucoup plus d'instructions imbriquées, certaines synchrones et d'autres asynchrones. Le code devient alors de plus en plus complexe à lire et à comprendre, et on passe donc plus de temps le jour où l'on doit le maintenir ou le faire évoluer.

Voilà pourquoi on utilise les promises, elles vont nous permettre de gérer des appels asynchrones, tout en gardant une certaine logique dans la lecture du code. Pour te donner un exemple, voici comment le code précédent pourrait être écrit avec des Promises, en conservant le même ordre d'affichage dans la console :

{"language":"application/json","content":"new Promise((resolve,reject) => {\n document.getElementById(\"myButton\").addEventListener(\"click\", function () {\n resolve();\n });\n }).then(function(){\n new Promise((resolve,reject) => {\n var xhr = new XMLHttpRequest();\n xhr.open(\"GET\", '/url', true);\n if (this.readyState === XMLHttpRequest.DONE && this.status === 200) {\n resolve();\n }\n console.log(3);\n }).then(function(){\n new Promise((resolve,reject) => {\n setTimeout(function () {\n resolve();\n }, 500);\n console.log(2);\n }).then(function(){\n console.log(1);\n });\n });\n });\n\nconsole.log(4);","filename":"exemple"}


Sans comprendre tout ce qu'il fait, on voit bien plus facilement dans ce code l'ordre d'exécution. Une Promise est créée, elle répondra plus tard, puis le console.log(4) est affiché. Et ensuite les callbacks successifs se dérouleront dans l'ordre de lecture du code, donc 3,2 et 1.

Arthur, l'apprenti développeurEffectivement, je ne comprends pas tout le code, mais l'ordre a l'air plus logique comme ça.

Maintenant je vais t'expliquer comment ça fonctionne. On a donc dit qu'une Promise était une promesse, c'est à dire qu'on lui demande d'exécuter un code, et elle promet de répondre après. La réponse c'est le "then", qui signifie littéralement "puis", ou si tu préfères dans une phrase on dirait "exécute ce code puis celui là". En exemple :

{"language":"application/json","content":"new Promise(function(){\n\t//exécute ce code\n}).then(function(){\n\t//puis celui là\n});","filename":"exemple"}


Arthur, l'apprenti développeurJusque là c'est simple !

C'est le concept de base, après il y a quelques notions en plus, pour commencer le resolve, que l'on passe en paramètre de la fonction précisée dans le callback.

Arthur, l'apprenti développeurC'est quoi ce résolve?

C'est une fonction interne à la Promise, que l'ont peut appeler pour indique que notre code s'est bien passé, et on peut lui ajouter des paramètres pour qu'ils soient récupérés dans le then, par exemple :

{"language":"application/json","content":"new Promise(function(resolve){\n\tresolve(\"Tout s'est bien passé !\");\n}).then(function(result){\n\tconsole.log(result);\n});","filename":"exemple"}


Ce code va afficher dans la console "Tout s'est bien passé !", car ce qui est passé en paramètre de resolve sera reçu en paramètre de la fonction dans le then.

Arthur, l'apprenti développeurPas de problème, c'est toujours simple.

Il existe un autre cas, dans lequel le code de la promise ne s'est pas bien passé, par exemple une requête ajax n'a pas réussi à joindre le serveur, dans ce cas on va utiliser reject à la place de resolve pour indiquer qu'une erreur s'est produite, et catch à la place de then pour préciser le code à exécuter lorsque l'erreur se produit.

{"language":"application/json","content":"new Promise(function(resolve,reject){\n\treject(\"Une erreur s'est produite !\");\n}).catch(function(result){\n\tconsole.log(result);\n});","filename":"exemple"}


Arthur, l'apprenti développeurOk je comprends, par contre, pourquoi as-tu conservé resolve avant reject dans la fonction de la Promise ?

Ah bonne question ! En fait les deux paramètres passés systématiquement à la fonction de la Promise sont toujours dans cet ordre, d'abord le resolve puis le reject. Et à vrai dire ici ce sont des callbacks, comme on en a parlé tout à l'heure, on pourrait leur donner les noms que l'on souhaite et ça fonctionnerait tout aussi bien. Mais pour garder une cohérence et une lisibilité dans le code, on prendra l'habitude de les nommer resolve et reject.

D'ailleurs dans l'idéal, on pourrait utiliser les deux dans la Promise, pour déclencher le then quand le code s'est bien déroulé, et le catch quand il ne s'est pas bien déroulé.

{"language":"application/json","content":"new Promise(function(resolve,reject) => {\n\tvar xhr = new XMLHttpRequest();\n\txhr.onreadystatechange = function() {\n\t\tif (this.readyState == 4 && this.status == 200) {\n\t\t\tresolve(this.responseText);\n\t\t}else if(this.readyState == 4 && this.status != 200){\n\t\t\treject(\"Une erreur s'est produite\" + this.status);\t\n\t\t}\n\t};\n}).then(function(result){\n\tconsole.log(result);\n}).catch(function(error){\n\tconsole.log(error);\n});","filename":"exemple"}


Voilà notre code va exécuter une requête ajax, si elle se passe bien, le code du then sera exécuté après la réponse de la requête, et affichera le contenu retourner par la requête, et si jamais elle se passe mal, le catch affichera un message d'erreur.

Arthur, l'apprenti développeurOk, jusque là j'ai tout compris, on va passer à la partie plus compliquée maintenant ?

Et bien... Non, il n'y a pas de partie plus compliquée que ça, tu connais maintenant les Promises, dans quels cas on les utilise, ce qu'elles apportent et comment les utiliser, je n'ai rien de plus à t'apprendre sur le sujet pour le moment !

Arthur, l'apprenti développeurC'etait juste ça ? Mais ça fait des jours que je cherche à comprendre...

Pour être honnête avec toi, les Promises ce n'est pas que ça, il y a des utilisations plus avancées et des fonctionnalités particulières dont on pourra parler une autre fois. Mais ce que je viens de te présenter c'est la base du fonctionnement des Promises et leur utilisation. Avec ça, tu as tout ce qu'il te faut pour commencer à faire tes premières Promises.

Auteur

Aurélien Vaast
Diplômé d'un Bac +5 en ingénierie logiciel et fort de 10 ans d'expérience professionelle en tant que développeur web et architecte logiciel, je suis aujourd'hui formateur dans le monde du développement web depuis plus de 6 ans, et auteur de cours techniques et soft skills sur le site training-dev.fr que j'ai fondé.

Commentaires

Aurélien Vaast
08/01/2021 à 07:19
Bonjour Patrick, merci pour ton message, pour précision, il n'y a pas du tout de POO dans ce tuto, tu pourras retrouver un exemple sur le cours "les bases du javascript".
Patrick Chardavoine
07/01/2021 à 09:33
J'avais assisté à la session en live et c'était passé un peu vite pour moi. J'ai pris ensuite le temps de décortiquer l'exemple (depuis le replay), et du coup j'ai l'impression de comprendre un tout petit peu mieux la mécanique des promises et de la poo javascript. Merci pour cet exemple très formateur.