Ce diaporama a bien été signalé.
Le téléchargement de votre SlideShare est en cours. ×

Javascript les générateurs (generators)

Publicité
Publicité
Publicité
Publicité
Publicité
Publicité
Publicité
Publicité
Publicité
Publicité
Publicité
Publicité
Chargement dans…3
×

Consultez-les par la suite

1 sur 59 Publicité

Plus De Contenu Connexe

Diaporamas pour vous (19)

Similaire à Javascript les générateurs (generators) (20)

Publicité

Plus récents (20)

Javascript les générateurs (generators)

  1. 1. Javascript Les générateurs
  2. 2. Préambule ... Nous allons parler ici de fonctionnalités qui ne sont disponibles que depuis ECMAScript 6. Leurs support n’est pas généralisé ; par exemple pas de IE, ou bien pas de Node.js (sauf si activation de --harmony ) http://kangax.github.io/compat-table/es6/#generators https://github.com/google/traceur-compiler
  3. 3. Itérateurs ?
  4. 4. En programmation, nous utilisons des boucles. Itérateurs ? for (var i = 0; i < 10; i++) { // Faire quelque chose 10 fois. } while(condition) { // Faire quelque chose jusqu'à ce que ... }
  5. 5. Le principe est de répéter l'exécution d’un code un certain nombre de fois. Chaque “tour” de notre boucle est une itération. Parcourir cette boucle se dit donc itérer. Itérateurs ?
  6. 6. Il est aussi possible d’itérer sur un tableau : Itérateurs ? var valeurs = [ 'a', 'b', 'c' ]; // Itérer sur les indices du tableau : for (var indice in valeurs) { console.log(valeurs[indice ]); } // Ou utiliser la méthode Array.forEach() valeurs.forEach(function(valeur) { console.log(valeur); });
  7. 7. Ou un objet : Itérateurs ? var valeurs = {a : 1, b : 2, c : 3 }; // Itérer sur les propriétés : for (var indice in valeurs) { if (valeurs.hasOwnProperty(indice)) { console.log( '[%s] => %s', indice, valeurs[indice] ); } }
  8. 8. On considère alors que les tableaux et les objets sont des itérateurs. Mais comme on vient de le voir, on itère sur les indices ou propriétés. C’est pas très pratique. Itérateurs ?
  9. 9. ECMAScript 6 (es6) introduit une nouvelle syntaxe d’itération : for ( … of …) : Itérateurs ? var valeurs = [3, 4, 5] ; for (var indice in valeurs) { console.log(indice); } // 0 ... 1 ... 2 for (var valeur of valeurs) { console.log(valeur); } // 3 ... 4 ... 5
  10. 10. o/ joie, bonheur, alacrité nous pouvons donc désormais itérer sur des tableaux et objets sans recoder la roue ou utiliser des Frameworks (jQuery $.each, par exemple). Itérateurs ?
  11. 11. Seulement, dehors il y a le vrai monde... Itérateurs ? Et le vrai monde, il veut itérer, et si possible de manière optimisée et pas que sur des tableaux et des objets. Petit interlude philosophie, optionnel. Cliquez pour visionner.
  12. 12. Pourquoi c’est nul ?
  13. 13. L’algorithme consistant à remplir un tableau/objet puis d’itérer dessus a beau avoir fait la route avec nous depuis nos premières pages, il présente de sérieux défauts. Dénigrons !
  14. 14. 1. Il veut même pas rendre ses ressources. Radin. Dénigrons ! Le tableau/objet est généré puis stocké en mémoire entièrement pendant toute l’itération de notre boucle, même si nous n’avons plus besoin de ces valeurs.
  15. 15. Dénigrons ! // Création de ma liste. var liste = []; // Remplissage for (var i = 0; i < 1000000; i++) { liste.push(Math.random()); } // Affichage for (var val of liste) { console.log(val); } Si l’on considère le code suivant : L’importante partie de mémoire utilisée pour stocker les 1 million de valeurs va rester allouée pendant toute l’itération de la boucle ; même si nous n’avons plus besoin de ces valeurs après le console.log().
  16. 16. 2. Il a la manie de tout bloquer. Dénigrons ! Pendant toute la création puis l’itération du tableau/objet, le navigateur ou le processus est “occupé”. Cela peut retarder la gestion d’ évènements, ralentir des affichages et rendre instable le navigateur/programme.
  17. 17. Dénigrons ! // Création de ma liste. var liste = []; // Remplissage console.time('Remplissage du tableau'); for (var i = 0; i < 1000000; i++) { liste.push(Math.random()); } console.timeEnd('Remplissage du tableau'); // Affichage console.time('Affichage du tableau'); for (var val in liste) { console.log(val); } console.timeEnd('Affichage du tableau'); // Remplissage du tableau: 2054.109ms // Affichage du tableau: 2185.662ms Ajoutons un timer à notre code : Ici, on voit que l’on a été “bloqué” pendant deux fois 2 secondes. Faire mumuse sur JSFiddle
  18. 18. Dénigrons ! // Remplissage console.time('Remplissage du tableau'); for (var i = 0; i < 1000000; i++) { liste.push(Math.random()); } console.timeEnd('Remplissage du tableau'); // Affichage console.time('Affichage du tableau'); for (var val of liste) { console.log(val); } console.timeEnd('Affichage du tableau'); // Remplissage du tableau: 2067.762ms // Affichage du tableau: 238779.176ms A noter également que for(... of …) devient même problématique … On a quasiment 10x le temps d’affichage (On fait un reparcours du tableau à chaque itération vers la fin).
  19. 19. Dénigrons ! A tester vous-même : http://jsfiddle.net/jucrouzet/fhgkaLgt/
  20. 20. 3. Il n’est vraiment pas souple... Dénigrons ! Une seul choix nous est offert : du début à la fin du tableau / objet, si l’on souhaite itérer différemment, on est obligés de “bricoler”.
  21. 21. Dénigrons ! var liste = [ 1, 2, 3, ....]; // Incrémenter de deux en deux ? for (var i in liste) { if (i % 2) { // Mon code a éxecuter à 1 3 4 ... } // Autant écrire // } else { ignorer cette mémoire gaspillée } } // Sinon, il faut faire la même chose lors de la génération Toute coquetterie est interdite, on se limite à : Wow. Folie. Sinon le code devient vite illisible.
  22. 22. Et donc, les générateurs !
  23. 23. Le générateur est un concept assez répandu dans beaucoup d’autres langage. Générateurs Mainstream label !
  24. 24. Imaginez qu’au lieu d’itérer sur une liste déjà prête, vous allez générer des valeurs au fur et à mesure (et en parallèle) de votre itération ... Générateurs
  25. 25. Générateurs Processus d’itération classique : Génération d’ une liste Stockage de la liste Itération dans la liste Liste finie ? Non Oui
  26. 26. Générateurs Processus d’itération classique : Génération d’ une liste Stockage de la liste Itération dans la liste Liste finie ? Non Oui Opération synchrone/bloquante Opération synchrone/bloquante
  27. 27. Générateurs Processus générateur : Création d’un générateur Stockage du générateur en mémoire Attente / Récup prochain élément dans la pile du générateur Générateur Fini ? Non Oui Génération d’ un élément de la liste Empiler l’élément Fini ? Non Oui En parallèle
  28. 28. Générateurs Pour comprendre le “en parallèle”, voir “Event Loop” dans : http://slideshare.net/jucrouzet/promises-javascript
  29. 29. Générateurs On va donc itérer non plus sur une liste prédéfinie mais sur des valeurs que l’on peut générer de la manière que l’on veut.
  30. 30. Générateurs Le système de pile de données entre l’itération et la génération gérera la synchronisation lorsque l’un génère ou consomme plus vite que l’ autre.
  31. 31. Alors c’est parti !
  32. 32. Générateurs ECMAScript 6 Les générateurs en es6 sont définis grâce à l’ ajout d’une étoile (*) sur le mot-clé function function* genereValues(arg1, arg2) { //Genere mes valeurs. } // ou var genereValues = function* (quelquechose) { //Genere mes valeurs. };
  33. 33. Générateurs ECMAScript 6 Un appel à cette “fonction” va donc retourner un objet qui est un itérateur : for (var valeur of genereValues(42, 'chihuahua')) { // Faire quelque chose } Notez bien : for (... of …) et non for (... in …) !
  34. 34. Générateurs ECMAScript 6 Occupons-nous maintenant du corps de ce générateur afin qu’il génère effectivement quelque chose...
  35. 35. Générateurs ECMAScript 6 Le corps d’un générateur est identique à une fonction normale var generateur = function* (value) { // Corps du générateur var uneVariable = 42; while(true) { uneVariable += value; } // Fin corps du générateur };
  36. 36. Générateurs ECMAScript 6 A la différence près qu’il ne retourne pas de valeur mais qu’il en génère. On utilise donc ici le le mot-clé yield (genère) var generateur = function* (value) { var uneVariable = 42; while(true) { uneVariable += value; yield uneVariable; } }; Contrairement à return, yield ne vas pas interrompre l’exécution de la fonction.
  37. 37. Générateurs ECMAScript 6 On a donc : var generateur = function* (value) { var uneVariable = 42; while(true) { uneVariable += value; yield uneVariable; } }; for (var valeur of generateur(10)) { console.log(valeur); // 52 … 62 … 72 … 82 … (vers l’infini et au delà) }
  38. 38. Générateurs ECMAScript 6 Comme vous pouvez le lire, l’itération va tourner en infini. Ce n’est probablement pas ce que l’on veut. var generateur = function* (value) { var uneVariable = 42; while(value >= 0) { uneVariable += value; yield uneVariable; value--; } }; for (var valeur of generateur(5)) { console.log(valeur); // 47 … 51 … 54 … 56 … 57 } Faire mumuse sur JSFiddle
  39. 39. Générateurs ECMAScript 6 Maintenant que nous avons fait notre premier générateur (qui n’a rien de mieux qu’une itération) ; allons un peu plus loin dans l’inspection de l’objet Generator ...
  40. 40. Générateurs ECMAScript 6 Inspectons le retour de generateur() dans l’ inspecteur d’une console JS : var iterateur = generateur(10); console.log(iterator); >> Generator { } // Nous avons donc bien objet de type Generator, // ouvrons le avec l’inspecteur en utilisant les // variables de substitution de console.log() : console.log('%o', iterator);
  41. 41. Générateurs ECMAScript 6 Nous savons donc que Generator est une classe qui offre deux méthodes publiques : next() et throw()
  42. 42. Générateurs ECMAScript 6 Comme son nom l’indique, next() est un appel à dépiler la prochaine valeur générées par notre générateur. Si elle est disponible, elle est retournée, sinon, on considère la boucle finie. Analysons le retour de Generator.next() ...
  43. 43. Générateurs ECMAScript 6 var generateurVide = function* () {}; var iterateurVide = generateurVide(); console.log(iterateurVide.next()); // >> Object { value: undefined, done: true } var generateur = function* () { yield 'AC/DC'; yield 'Iron Maiden'; yield 'Justin Bieber'; yield 'Metallica'; }; var iterateur = generateur(); console.log(iterateur.next()); console.log(iterateur.next()); console.log(iterateur.next()); console.log(iterateur.next()); console.log(iterateur.next()); // >> Object { value: "AC/DC", done: false } // >> Object { value: "Iron Maiden", done: false } // >> Object { value: "Justin Bieber", done: false } // >> Object { value: "Metallica", done: false } // >> Object { value: undefined, done: true } Faire mumuse sur JSFiddle
  44. 44. Générateurs ECMAScript 6 Le fonctionnement des Generator dans une boucle for (... of … ) est donc maintenant simple à comprendre : Chaque itération fait un appel à .next(), si l’objet de retour a la propriété done à false, elles assignent la valeur de la propriété value pour cette itération. Si l’objet de retour a la propriété done à true, elles s’arrêtent immédiatement.
  45. 45. Générateurs ECMAScript 6 Afin maintenant de comprendre comment la synchronisation est faite, passons à un autre test. Un test champêtre.
  46. 46. Générateurs ECMAScript 6 var generateurPoete = function* () { console.log('Un Kalachnikov dans le boule en introduction'); yield 'b'; console.log('Ouest Side, Ouest Side, 92 injection'); yield '2o'; console.log('Certains croivent qu'ils rivalisent, faudra qu'on les hospitalise'); yield 'b'; console.log('Tu tiens pas la route, pé-ra avec un "A" collé dans le dos'); yield 'a'; }; var neuf2_izi = generateurPoete(); console.log(neuf2_izi.next()); console.log(neuf2_izi.next()); console.log(neuf2_izi.next()); console.log(neuf2_izi.next()); console.log(neuf2_izi.next()); Faire mumuse sur JSFiddle Donc le code suivant :
  47. 47. Générateurs ECMAScript 6 "Un Kalachnikov dans le boule en introduction" Object { value: "b", done: false } "Ouest Side, Ouest Side, 92 injection" Object { value: "2o", done: false } "Certains croient qu'ils rivalisent, faudra qu'on les hospitalise" Object { value: "b", done: false } "Tu tiens pas la route, pé-ra avec un "A" collé dans le dos" Object { value: "a", done: false } Object { value: undefined, done: true } Affichera dans la console :
  48. 48. Générateurs ECMAScript 6 Comme vient de nous le prouver Booba, l’instruction yield empile une valeur puis mets en pause l’exécution du générateur jusqu'à ce que .next() soit appelé pour la dépiler.
  49. 49. Générateurs ECMAScript 6 Cette technique présente deux avantages : - La mémoire n’est pas allouée pour des tonnes de valeurs mais uniquement pour la valeur en cours d’itération ; - Les valeurs ne sont pas générées dans une boucle qui utilise toute les ressources le temps de la génération mais uniquement “à la demande”.
  50. 50. Générateurs ECMAScript 6 Un autre avantage des générateurs est que la “communication” entre le Generator et son consommateur (l'appelant de .next()) n’est pas unidirectionnelle, c’est un dialogue !
  51. 51. Générateurs ECMAScript 6 La méthode .next() accèpte un argument, c’est la valeur qui sera retournée par yield. Donc si : monGenerator.next('coucou'); alors dans le générateur : var message = yield 'valeur générée'; // message est égale à ‘coucou’
  52. 52. Générateurs ECMAScript 6 Appliquons ça sur un exemple dramatique :
  53. 53. Générateurs ECMAScript 6 var patronat = function* () { var tours = 3; while(tours) { var demande = yield 'rien'; window.console.log( 'Patron : Parce que la dernière fois vous avez déjà demandé %s', demande ); tours--; } }; var dialogueSocial = patronat(); var syndicat = function(demande) { var reponse = dialogueSocial.next(demande); if (reponse && !reponse.done && reponse.value) { window.console.log('Syndicat : On a demandé %s et on a eu => %s', demande, reponse. value); } else { window.console.log('Syndicat : On démarre une grève'); } }; syndicat('une augmentation'); syndicat('des congés'); syndicat('des tickets resto'); syndicat('des RTT'); Faire mumuse sur JSFiddle
  54. 54. Générateurs ECMAScript 6 "Syndicat : On a demandé une augmentation et on a eu => rien" "Patron : Parce que la dernière fois vous avez déjà demandé des congés" "Syndicat : On a demandé des congés et on a eu => rien" "Patron : Parce que la dernière fois vous avez déjà demandé des tickets resto" "Syndicat : On a demandé des tickets resto et on a eu => rien" "Patron : Parce que la dernière fois vous avez déjà demandé des RTT" "Syndicat : On démarre une grève" ECMAScript 6, c’est un coup monté du patronat. Je vois que ça.
  55. 55. Générateurs ECMAScript 6 Bon, ce dialogue a bien dérapé. Les bugs ça arrive. Tiens d’ailleurs on a pas parlé de l’autre méthode de Generator, .throw(). Magnifique transition.
  56. 56. Générateurs ECMAScript 6 La méthode .throw() permet, toujours dans cet esprit de dialogue constructif, au consommateur du générateur (celui qui appèle .next(), par exemple) de lever une exception dans le corps de la fonction du Generator, au niveau du yield. Disons le sans détour, c’est un moyen de torpiller de l’ intérieur ! Heureusement, un gilet pare-balles nommé try/catch est là pour gérer ça.
  57. 57. Générateurs ECMAScript 6 .throw() prend donc un argument, typiquement une chaîne de caractère (message d’erreur), ou un objet Error. Cette valeur sera lancée comme Exception lors du prochain appel de yield.
  58. 58. Générateurs ECMAScript 6 function* monGenerateurQuonEmbete() { var loops = 5; while(loops) { try { yield loops; } catch(err) { console.log('Aie, je viens de recevoir %o', err); // 1 -> return; } loops--; } }; var victime = monGenerateurQuonEmbete(); console.log(victime.next()); // 2 -> console.log(victime.next()); // 3 -> console.log(victime.throw(new Error('Et pan dans les dents'))); // 4 -> console.log(victime.next()); // 5 -> >> Object { value: 5, done: false } // <- 2 >> Object { value: 4, done: false } // <- 3 >> "Aie, je viens de recevoir Error: Et pan dans les dents [...]" // <- 1 >> Object { value: undefined, done: true } // <- 4 >> Object { value: undefined, done: true } // <- 5 Faire mumuse sur JSFiddle
  59. 59. Générateurs ECMAScript 6 Fin du premier chapitre. Dans le prochain, on explorera comment coupler les générateurs et les promesses. Tout un programme. Mais ça vaut vraiment le coup. Promis. Pour me contacter :

×