Promises Javascript

1 740 vues

Publié le

Présentation des Promises, l'une des principales fonctionnalités de ES6, la dernière version de Javascript.

Publié dans : Technologie
0 commentaire
1 j’aime
Statistiques
Remarques
  • Soyez le premier à commenter

Aucun téléchargement
Vues
Nombre de vues
1 740
Sur SlideShare
0
Issues des intégrations
0
Intégrations
20
Actions
Partages
0
Téléchargements
23
Commentaires
0
J’aime
1
Intégrations 0
Aucune incorporation

Aucune remarque pour cette diapositive

Promises Javascript

  1. 1. Les Promises Javascript
  2. 2. Javascript et son Event Loop
  3. 3. Commençons par analyser en détail comment Javascript gère un problème vieux comme le développement : Le fonctionnement asynchrone.
  4. 4. Event Loop • Javascript est essentiellement un langage asynchrone (en opposition à « procédural ») • On utilise souvent cette structure évènementielle en définissant des « écouteurs » d’évènements (Par exemple avec l’event object model  )
  5. 5. Event Loop
  6. 6. Event Loop L’ avantage du modèle est de ne pas bloquer le processus principal (comme par exemple le navigateur ou votre programme Node.js) pendant l’attente de l’évènement.
  7. 7. Event Loop En développement, on appelle une opération synchrone lorsqu’elle est exécutée « telle que l’on peut la lire » (barbarisme, mais bon). Prenons l’exemple en PHP de la récupération du contenu (HTML) d’une url :
  8. 8. Event Loop
  9. 9. Event Loop Ce script va être exécuté ligne par ligne et afficher le résultat suivant : On lance Etape 2 0.25 Le temps écoulé pendant le téléchargement de l’url (0,25 seconde) correspond également au temps pendant lequel le programme est bloqué. Si le téléchargement prend plusieurs secondes (voir plus), c’est donc problématique.
  10. 10. Event Loop Dans le cadre d’une application avec une interface graphique (par exemple), on ne pas peut se le permettre ! On a donc créé les threads.
  11. 11. Event Loop Sans rentrer dans les détails, il s’agit de créer une unité d’exécution parallèle à notre programme qui aurait ici le noble rôle d’attendre un évènement ou d’effectuer une tâche sans bloquer notre interface.
  12. 12. Event Loop Contrairement à beaucoup d’autres langages Javascript n’a pas de mécanismes de gestion des threads, et pour cause, il n’est exécuté que dans un seul(*) thread. (*) : Ce n’est pas tout à fait vrai, mais peu importe ici…
  13. 13. Event Loop En Javascript, on règle le problème avec une toute autre méthode : L’Event Loop !
  14. 14. Event Loop L’Event Loop (la boucle d’évènements, donc) est appelée ainsi puisqu’on peut la conceptualiser avec le pseudo code suivant : TANT_QUE boucle.attend_message() boucle.prochain_message() FIN_TANT_QUE
  15. 15. • L’Event Loop est donc une pile de messages où chaque message est une fonction à exécuter. • Les messages sont donc empilés à chaque fois qu’un évènement auquel on a attaché un ou plusieurs callbacks (écouteurs) survient. • Et ils sont donc dépilés (au fur et à mesure) par la boucle d’évènement. Event Loop
  16. 16. Event Loop Petit interlude « Loi Toubon » avant d’en arriver à parler de butineurs. • callback: Ecouteur (fonction a exécuter sur un évènement) • message: Fonction empilée lors d’un évènement (callback en attente donc!) • event loop: La boucle d’évènement permanente qui dépile les message. Voilà, ça c’est fait.
  17. 17. Event Loop Donc lorsque l’on ajoute le callback suivant : On précise « A chaque fois que l’évènement « resize » intervient sur l’objet window, empile le message replace_elements dans l’event loop.
  18. 18. Event Loop Deux points importants !
  19. 19. Event Loop Nous avons bien précisé replace_elements et non pas replace_elements() La deuxième notation reviendrait à dire exécute immédiatement la fonction replace_elements puis place sa valeur de retour comme callback de l’évènement. (Ceci représente 10^42 questions sur StackOverflow…)
  20. 20. Event Loop(Ceci représente aussi 10^42 questions sur StackOverflow…) Affichera : 1 … 3 … 2 … 4 ! Ne jamais confondre sens de lecture du code et logique d’event loop :
  21. 21. Event Loop Enfin, si l’on reprend notre pseudo code : TANT_QUE boucle.attend_message() boucle.prochain_message() FIN_TANT_QUE On remarque que les messages sont exécutés un par un ; il est donc d’usage de faire des callbacks rapides, sous peine de voir nos prochains évènements traités longtemps après être survenu. Et puis ça aussi !
  22. 22. Tout ceci normalement vous est familier si vous avez développé un tant soit peu en Javascript. Ca marche très bien. C’est très largement adopté. Mais… (car il y a toujours un mais) (et là il y en a plusieurs).
  23. 23. The Spaghetti Incident Aka : « C’est normal que ton code finisse avec une indentation de 658 espaces ? – Ouais.)
  24. 24. The spaghetti incident Prenons un exemple concret : Un script doit lire un document JSON en Ajax, l’analyser, récupérer une url dans ce document, puis la récupérer et afficher son contenu. (et pour tricher un peu on va se reposer sur jQuery)
  25. 25. The spaghetti incident On commence donc par récupérer ce flux JSON :
  26. 26. The spaghetti incident Oui mais on n’a pas géré le fait que ce document soit inaccessible ou du JSON mal formé !
  27. 27. The spaghetti incident Il est donc temps d’aller récupérer l’url contenue dans le document JSON (en gérant aussi les erreurs) :
  28. 28. The spaghetti incident Le code précédent est sur le point, si ce n’est déjà fait, d’obtenir le précieux badge « Spaghetti code » Donc un code difficile à lire, ou tout est plus ou moins embrouillé ressemblant à un plat de spaghetti ! (miam)
  29. 29. Is it Async or Sync or potato ? Aka : « C’est normal que l’ordre de tes callbacks ne soit pas toujours le même ? – Ouais.)
  30. 30. Async or not async ? On a vu précédemment que : Affichera : 1 … 3 … 2 … 4
  31. 31. Async or not async ? Donc dans notre exemple précédent (épuré de la gestion des erreurs) : Affichera : 1 … 4 … 2 … 3
  32. 32. Async or not async ? Seulement, un développeur bien élevé sait que les ressources sont chères et souhaite donc stocker l’url dans le document JSON en mémoire pour ne pas provoquer un appel $.ajax a chaque évènement, non ? Alors, allons-y !
  33. 33. Async or not async ?
  34. 34. Async or not async ? Lors de la première exécution du script précédent, nous gardons l’ordre : 1 … 4 … 2a … 3 L’url à récupérer contenue dans le doc JSON est alors sauvegardée dans la variable url_a_recuperer. => Cas a (voir après)
  35. 35. Async or not async ? Lors des appels suivants, nous obtiendrons l’ordre : 1 … 2b … 4 … 3 => Cas b (voir après)
  36. 36. Async or not async ? Asynchrone Synchrone On ne lit que le code synchrone : 1 … (2b si cas b) … 4 PUIS le code asynchrone (2a si cas a) … 3 (Vous l’avez ?) (Sinon, recommencez) (Si, si !)
  37. 37. Synchroniser l’asynchrone ? Aka : « On y va à 3 ou on compte 1 … 2 … 3 et là on y va ? – Ouais.)
  38. 38. Synchroniser l’asynchrone Autre problème, autre exemple : Nous souhaitons exécuter un callback quand plusieurs opérations asynchrones sont effectuées ; en l’occurrence : • Récupérer les 10 derniers tweets de l’utilisateur (get_tweets) • Récupérer ses amis Facebook (get_friends) • Récupérer les projets Github de l’utilisateur (get_projects)
  39. 39. Synchroniser l’asynchrone On définit 3 fonctions responsables de chaque tâche : Oui, à peu près hein.
  40. 40. Synchroniser l’asynchrone Chacune de ces fonctions fait appel à une API externe qui peut prendre un temps indéfini à répondre. Chacune de ces fonction peut générer une erreur à tout moment. Nous devons pourtant synchroniser tout ça …
  41. 41. Synchroniser l’asynchrone La solution va donc être de définir une fonction de vérification pour vérifier si tout est récupéré … Puis d’appeler cette fonction dans chaque callback de succès …
  42. 42. Synchroniser l’asynchrone Ici aussi, la solution marche, est répandue mais n’est pas très élégante : • La lisibilité du code est complexifiée • La taille du code est exponentielle au nombre de fonctions asynchrones à appeler
  43. 43. Bon, vous voyez ou je veux en venir ? Y’a mieux !
  44. 44. Les Promesses ! Aka : « On avait pas dit point Toubon, toussa ? Donc Promises non ? – Ouais.)
  45. 45. Commençons par préciser que c’est une fonctionnalité ECMAScript 6 (es6), donc pas disponible de partout. http://caniuse.com/#feat=promises Mais … parce que y’a … (hein ? Quoi ?, ha ok, pardon). Les Promises
  46. 46. Les Promises Il existe des (beaucoup) Polyfills pour browser ou node : • es6-promise • promise-polyfill • bluebird • Q • Promises, Promises • Etc, etc, etc. A mon avis (que personne n’a demandé), bluebird est le meilleur. Enfin, io.js gère les promises nativement.
  47. 47. Les Promises Alors les Promises, c’est quoi ?
  48. 48. Les Promises Au lieu de définir des fonctions avec callbacks en cas de succès ou d’erreur … On va définir des promesses. Ces promesses respecteront une interface commune que tout le monde peut utiliser de manière identique. « Les promesses n’engagent que ceux qui les écoutent. » J. Chirac
  49. 49. Les Promises Ces promesses pourront être soit (OU) : • En cours de réalisation (pending) • Tenues (fulfilled) • Non tenues (rejected)
  50. 50. Les Promises Ces promesses pourront être à la fois (ET) : • Synchronisées • Enchainées • Ordonnées
  51. 51. Les Promises C’est beau vu comme ça. Mais concrètement ?
  52. 52. Promises/A+ Une Promise est un object. Comme tout en Javascript. (Un petit rappel de temps en temps ne fait pas de mal) Commençons par « la base », l’implémentation de la spécification Promises/A+
  53. 53. Promises/A+ Son constructeur prend comme un argument une fonction dont le prototype est : f( {Function} setFulfilled , {Function} setRejected ) Son rôle est de tenir cette promesse. ()
  54. 54. Promises/A+ Si la promesse est tenue, elle devra alors appeler setFulfilled Sinon, elle devra appeler setRejected
  55. 55. Promises/A+ L’objet maPromesse est désormais une Promise que l’on peut attendre grâce à sa méthode then qui a le prototype suivant : Promise.prototype.then( [{Function} onFulfilled], [{Function] onRejected] )
  56. 56. Promises/A+
  57. 57. Promises/A+ Adaptons notre précédent exemple (aller chercher et analyser un document JSON) : Nous avons désormais une fonction recupere_mon_json qui retourne une promise dont la promesse est le document JSON analysé.
  58. 58. Promises/A+ Nous pouvons donc l’utiliser comme n’importe quelle Promise : Bon, ok. Comme ça, ça n’a pas l’air de changer grand-chose. Mais … (oui, je sais).
  59. 59. Promises/A+ Les spécifications des Promises précisent des points particulièrement intéressants : Relax ! C’est expliqué dans les slides suivante
  60. 60. Promises/A+ La méthode .then() retourne elle-même une nouvelle Promise ! Autrement dit, nous pouvons dans notre exemple enchainer deux Promises : celle de la récupération du JSON et celle de la récupération de l’url.
  61. 61. Promises/A+ Créons une nouvelle Promise pour la récupération de l’URL :
  62. 62. Promises/A+ Nous pouvons donc désormais réécrire le script sans faire un magnifique Spaghetti code !
  63. 63. Promises/A+ Avantage 1 : Le code est bien plus lisible. Pas convaincu ? Imaginez que nous ayons pas 2 requêtes Ajax à enchainer mais 6. D’un coté une pyramide d’indentation de 6 niveaux. De l’autre juste .then(func1).then(func2).then(func3)…
  64. 64. Promises/A+ Avantage 2 : Les erreurs ne sont gérées qu’à un seul endroit : au dernier .then() La première Promise non tenue, quelle qu’elle soit « zappera » alors la suite du processus jusqu’au dernier .then() ! Rappelez-vous la spécification : Comme nous avons enchainé des .then(func()) nous n’avons spécifié que le premier argument de Promise.prototype.then( [{Function} onFulfilled], [{Function] onRejected] ) Sauf pour le dernier .then(). CQFD.
  65. 65. Promises/A+ Avantage 3 : Il est possible de définir plusieurs .then() pour une même promise comme on le ferait pour des évènements. Il est donc possible de définir plusieurs fonctions (pour les promesses tenues ou non tenues).
  66. 66. Mais les spécifications des Promises de es6 vont _beaucoup_ plus loin ! Et là, ça gère sa race. (A ce stade, le lecteur peut ne pas comprendre la pauvreté de cette blague)
  67. 67. Reprenons notre exemple de 3 fonctionnalités asynchrones devant être synchronisées : Promises es6
  68. 68. Si ces trois fonctions sont « Promisifiées » (voir slides précédentes), la fonction Promise.all() nous permet de gérer automatiquement la concurrence. Promises es6 Promise.all( {Array} promisesList ) => Promise Cette fonction prend comme argument un Array de Promise, et retourne une nouvelle Promise.
  69. 69. Cette nouvelle Promise sera : • En attente (pending) en attendant que toute les promises du tableau en argument soient tenues ; • Tenue (fulFilled) si l’ensemble des promises du tableau en argument on été tenues ; • Non-tenue (rejected) dès que l’une des promises du tableau en argument n’est pas tenue. Promises es6
  70. 70. Si la nouvelle Promise est tenue La fonction onFulfilled reçoit comme argument un tableau correspondant aux valeurs passées à la fonction, en respectant l’ordre des promises dans le tableau initial. Promises es6
  71. 71. Si la nouvelle Promise n’est pas tenue La fonction onRejected reçoit comme argument l’erreur renvoyée par la première promise non tenue qui a provoqué cet état. Promises es6
  72. 72. Dans notre exemple, le code sera donc refactorisé de cette manière : Promises es6 Quand même beaucoup plus élégant, non ?
  73. 73. Les Promises offrent également une autre manière de gérer une liste de promises : La fonction race() (C’est bon, vous l’avez la blague nulle ?) Promises es6 Promise.all( {Array} promisesList ) => Promise De la même manière que all(), race() va lancer la résolution des Promises passées en argument et retourner une promise.
  74. 74. Si la nouvelle Promise est tenue La fonction onFulfilled reçoit comme argument la valeur passée à la fonction setFulfilled de la première Promise tenue. Promises es6
  75. 75. Si la nouvelle Promise n’est pas tenue La fonction onRejected reçoit comme argument l’erreur renvoyée par la première promise non tenue. Promises es6

×