Top Banner
Javascript Les générateurs
59
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Javascript   les générateurs (generators)

Javascript

Les générateurs

Page 2: Javascript   les générateurs (generators)

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

Page 3: Javascript   les générateurs (generators)

Itérateurs ?

Page 4: Javascript   les générateurs (generators)

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 ...}

Page 5: Javascript   les générateurs (generators)

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 ?

Page 6: Javascript   les générateurs (generators)

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);});

Page 7: Javascript   les générateurs (generators)

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] ); }}

Page 8: Javascript   les générateurs (generators)

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 ?

Page 9: Javascript   les générateurs (generators)

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

Page 10: Javascript   les générateurs (generators)

\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 ?

Page 11: Javascript   les générateurs (generators)

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.

Page 12: Javascript   les générateurs (generators)

Pourquoi c’est nul ?

Page 13: Javascript   les générateurs (generators)

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 !

Page 14: Javascript   les générateurs (generators)

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.

Page 15: Javascript   les générateurs (generators)

Dénigrons !

// Création de ma liste.var liste = [];

// Remplissagefor (var i = 0; i < 1000000; i++) { liste.push(Math.random());}

// Affichagefor (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().

Page 16: Javascript   les générateurs (generators)

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.

Page 17: Javascript   les générateurs (generators)

Dénigrons !

// Création de ma liste.var liste = [];

// Remplissageconsole.time('Remplissage du tableau');for (var i = 0; i < 1000000; i++) { liste.push(Math.random());}console.timeEnd('Remplissage du tableau');

// Affichageconsole.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

Page 18: Javascript   les générateurs (generators)

Dénigrons !

// Remplissageconsole.time('Remplissage du tableau');for (var i = 0; i < 1000000; i++) { liste.push(Math.random());}console.timeEnd('Remplissage du tableau');

// Affichageconsole.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).

Page 19: Javascript   les générateurs (generators)

Dénigrons !

A tester vous-même :

http://jsfiddle.net/jucrouzet/fhgkaLgt/

Page 20: Javascript   les générateurs (generators)

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”.

Page 21: Javascript   les générateurs (generators)

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.

Page 22: Javascript   les générateurs (generators)

Et donc, les générateurs !

Page 23: Javascript   les générateurs (generators)

Le générateur est un concept assez répandu dans beaucoup d’autres langage.

Générateurs

Mainstreamlabel !

Page 24: Javascript   les générateurs (generators)

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

Page 25: Javascript   les générateurs (generators)

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

Page 26: Javascript   les générateurs (generators)

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

Page 27: Javascript   les générateurs (generators)

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

Page 28: Javascript   les générateurs (generators)

Générateurs

Pour comprendre le “en parallèle”, voir“Event Loop” dans :

http://slideshare.net/jucrouzet/promises-javascript

Page 29: Javascript   les générateurs (generators)

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.

Page 30: Javascript   les générateurs (generators)

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.

Page 31: Javascript   les générateurs (generators)

Alors c’est parti !

Page 32: Javascript   les générateurs (generators)

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.};

Page 33: Javascript   les générateurs (generators)

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 …) !

Page 34: Javascript   les générateurs (generators)

Générateurs ECMAScript 6

Occupons-nous maintenant du corps de ce générateur afin qu’il génère effectivement quelque chose...

Page 35: Javascript   les générateurs (generators)

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};

Page 36: Javascript   les générateurs (generators)

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.

Page 37: Javascript   les générateurs (generators)

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à)}

Page 38: Javascript   les générateurs (generators)

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

Page 39: Javascript   les générateurs (generators)

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 ...

Page 40: Javascript   les générateurs (generators)

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);

Page 41: Javascript   les générateurs (generators)

Générateurs ECMAScript 6

Nous savons donc que Generator est une classe qui offre deux méthodes publiques : next() et throw()

Page 42: Javascript   les générateurs (generators)

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() ...

Page 43: Javascript   les générateurs (generators)

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

Page 44: Javascript   les générateurs (generators)

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.

Page 45: Javascript   les générateurs (generators)

Générateurs ECMAScript 6

Afin maintenant de comprendre comment la synchronisation est faite, passons à un autre test.Un test champêtre.

Page 46: Javascript   les générateurs (generators)

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 :

Page 47: Javascript   les générateurs (generators)

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 :

Page 48: Javascript   les générateurs (generators)

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.

Page 49: Javascript   les générateurs (generators)

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”.

Page 50: Javascript   les générateurs (generators)

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 !

Page 51: Javascript   les générateurs (generators)

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’

Page 52: Javascript   les générateurs (generators)

Générateurs ECMAScript 6

Appliquons ça sur un exemple dramatique :

Page 53: Javascript   les générateurs (generators)

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

Page 54: Javascript   les générateurs (generators)

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.

Page 55: Javascript   les générateurs (generators)

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.

Page 56: Javascript   les générateurs (generators)

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.

Page 57: Javascript   les générateurs (generators)

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.

Page 58: Javascript   les générateurs (generators)

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

Page 59: Javascript   les générateurs (generators)

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 :