3. Commençons par analyser en détail comment
Javascript gère un problème vieux comme le
développement : Le fonctionnement asynchrone.
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 )
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. 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 :
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. 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. 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. 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…
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. • 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. 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. 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.
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. 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. 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. 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. The Spaghetti Incident
Aka : « C’est normal que ton code
finisse avec une indentation de 658
espaces ? – Ouais.)
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)
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. 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. 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. 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. Async or not async ?
On a vu précédemment que :
Affichera :
1 … 3 … 2 … 4
31. Async or not async ?
Donc dans notre exemple précédent (épuré de la
gestion des erreurs) :
Affichera :
1 … 4 … 2 … 3
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 !
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. Async or not async ?
Lors des appels suivants, nous obtiendrons l’ordre :
1 … 2b … 4 … 3
=> Cas b (voir après)
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 !)
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)
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. 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. 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
44. Les Promesses !
Aka : « On avait pas dit point
Toubon, toussa ?
Donc Promises non ? – Ouais.)
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. 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.
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. Les Promises
Ces promesses pourront être soit (OU) :
• En cours de réalisation (pending)
• Tenues (fulfilled)
• Non tenues (rejected)
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. 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. Promises/A+
Si la promesse est tenue, elle devra alors appeler
setFulfilled
Sinon, elle devra appeler
setRejected
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]
)
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. 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. Promises/A+
Les spécifications des Promises précisent des points
particulièrement intéressants :
Relax ! C’est expliqué dans les slides suivante
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.
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. 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. 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. 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. Reprenons notre exemple de 3 fonctionnalités
asynchrones devant être synchronisées :
Promises es6
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. 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. 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. 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. Dans notre exemple, le code sera donc refactorisé de
cette manière :
Promises es6
Quand même beaucoup plus élégant, non ?
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. 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. 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