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
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. 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. 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. 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. 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. 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. 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. 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.
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. 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. 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. 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. 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. 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).
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. 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.
23. Le générateur est un concept assez répandu
dans beaucoup d’autres langage.
Générateurs
Mainstream
label !
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
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. 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. Générateurs
Pour comprendre le “en parallèle”, voir
“Event Loop” dans :
http://slideshare.net/jucrouzet/promises-javascript
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. 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.
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. 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 …) !
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. 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. 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. 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. 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. 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. Générateurs ECMAScript 6
Nous savons donc que Generator est une classe qui offre
deux méthodes publiques : next() et throw()
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() ...
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. Générateurs ECMAScript 6
Afin maintenant de comprendre comment la synchronisation
est faite, passons à un autre test.
Un test champêtre.
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. 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. 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. 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. 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. 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’
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. 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. 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. 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. 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. 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. 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 :