On Github fmauquie / nodejsparis-es2015-iterateurs-generateurs
Node.js Paris ∾ 19/01/2016
Fabien Mauquié
for (let [i, elem] of anArray.entries()) { console.log(`index: ${i}, value: ${elem}`); }
let aMap = new Map().set('a', 1).set('b', 2); aMap.entries(); // MapIterator { [ 'a', 1 ], [ 'b', 2 ] } let plop = ['p', ...aMap.entries(), 'lop']; plop; // [ 'p', [ 'a', 1 ], [ 'b', 2 ], 'lop' ]
let aSet = new Set().add('a').add('b'); let [a, b] = aSet; a; // 'a' b; // 'b' aSet.add('c'); let [a2, ...bc] = aSet; bc; // [ "b", "c" ]Pas encore implémenté dans V8
let obj = {prop1: true, prop2: false}; for (let prop of obj) { // TypeError: obj[Symbol.iterator] is not a function }
let iterable = { [Symbol.iterator]() { let step = 0; return { next() { if (step <= 2) { step++; } switch (step) { case 1: return { value: 'Une valeur', done: false }; case 2: return { value: 'deux valeurs', done: false }; default: return { value: undefined, done: true }; } } }; } };
Les protocoles d'itération
let iterable = { [Symbol.iterator]() { return { value: 0, next() { return {value: ++value}; } }; } };
let iterable = { [Symbol.iterator]() { return this; }, currentValue: 0, next() { return {value: ++currentValue}; } };
C'est comme ça dans les collections ES2015
let arr = []; let iterator = arr[Symbol.iterator](); iterator[Symbol.iterator]() === iterator; // true
let peopleCSV = [['Nom', 'Prénom'], ['Fabien', 'Mauquié']]; let itPeople = peopleCSV[Symbol.iterator](); // Remove header line itPeople.next(); for (let [name, surname] of itPeople) { console.log(`Name: ${name}, Surname: ${surname}`); }
let arr = ['a', 'b']; let iterator = arr[Symbol.iterator](); iterator.next(); // {value: 'a', done: false} iterator.next(); // {value: 'b', done: false} iterator.next(); // {value: undefined, done: true}
function* gen() { console.log('A'); yield 'A'; console.log('B'); return 'B'; } let iterator = gen(); iterator.next(); // console: 'A'; {value: 'A', done: false} iterator.next(); // console: 'B'; {value: 'B', done: true} iterator.next(); // {value: undefined, done: true}
// Déclaration function* gen() { // ... } // Assignation let gen2 = function* () { /* ... */ }; // Dans un objet let obj = { * gen() { // gen: function* gen() { // ··· } }; // Classe. Aucun navigateur ne supporte class aujourd'hui // FF et Edge s'y mettent... class UneClass { * gen() { // ··· } }
function* gen() { yield 'A'; return 'B'; } let iterator = gen(); iterator.next(); // {value: 'A', done: false} iterator.next(); // {value: 'B', done: true}
for (let valeur of gen()) { console.log(valeur); } [...gen()]; // ['A']
La plupart des structures d'itération ne prennent pas en compte la valeur de retour
function* gen2() { yield 'C'; let b = yield* gen(); yield b; } [...gen2()]; // [ 'C', 'A', 'B' ]
function* gen3() { yield* ['un', 'autre', 'iterable']; } [...gen3()]; // [ 'un', 'autre', 'iterable' ]
function* iterateOnTree(head) { if (!head) return; yield head.value; yield* iterateOnTree(head.left); yield* iterateOnTree(head.right); } [...iterateOnTree({ value: 'A', left: { value: 'B', left: {value: 'C'} }, right: { value: 'D' } })]; // [ 'A', 'B', 'C', 'D' ]
function* aie() { setTimeout(() => (yield)); } // SyntaxError: arrow function may not contain yield
function* consommateur() { console.log('Démarré'); console.log(`1. ${yield}`); console.log(`2. ${yield}`); return 'termine'; } let iterator = consommateur(); // "pomper" pour démarrer iterator.next(); // Démarré ; {value: undefined, done: false} // Envoyer des valeurs iterator.next('un !'); // 1. un ! ; {value: undefined, done: false} iterator.next('deux !'); // 2. deux ! ; {value: 'termine', done: true}
Force un 'return' à l'endroit où le générateur est arrêté
function* avecReturn() { try { console.log('Démarré'); yield; console.log('On ne devrait pas passer là'); } finally { console.log('on est parti'); } } let iterator = avecReturn(); iterator.next(); // Démarré ; {value: undefined, done: false} // Forcer un retour iterator.return('stop'); // on est parti ; {value: 'stop', done: true}
function* empecherReturn() { try { console.log('Démarré'); yield; console.log('On ne devrait pas passer là'); } finally { yield 'ahah!'; } } let iterator = empecherReturn(); iterator.next(); // Démarré ; {value: undefined, done: false} iterator.return('stop'); // {value: 'ahah!', done: false} iterator.next(); // {value: 'stop', done: true}Documentez !
Lance une erreur à l'endroit où le générateur est arrêté
function* avecThrow() { console.log('Démarré'); yield; console.log('On ne devrait pas passer là'); } let iterator = avecThrow(); // Forcer une erreur iterator.throw(new Error('aaaaargh !')); // Error: aaaargh !
Il est possible de catcher l'erreur avec try { ... } catch(e) { ... }
function* newborn() { yield; } let iterator = newborn(); iterator.next('une valeur'); // Erreur ! on n'est pas sur un yield iterator.return('plop'); // OK iterator.throw(new Error('aaaaargh !')); // OK
function* sum(sink) { var accumulator = 0; try { while(true) { accumulator += yield; } } finally { sink(accumulator); } } function* filterNumbers(sink) { sink.next(); // Amorçage try { while (true) { let word = yield; if (/[\d+]/.test(word)) { sink.next(parseInt(word)); } } } finally { sink.return(); } } let iterator = filterNumbers(sum(console.log.bind(console))); iterator.next(); // Amorçage iterator.next('J\'ai'); iterator.next('20'); iterator.next('minutes'); iterator.next('pour'); iterator.next('120'); iterator.next('slides'); iterator.return();
fetch('json/entry.json') .then(function(response) { return response.json(); }) .then(function(json) { return json.id; }) .then(function(id) { return fetch('/json/' + id); }) .then(function(response) { return response.json(); }) .then(function(specifics) { return Promise.all(specifics.map(obj => fetch('/json/' + obj.id)); }) .then(function(fetchedObjects) { return Promise.all(fetchedObjects.map(obj => obj.json())); }) .then(console.log.bind(console, 'objets:')) .catch(console.error.bind(console, 'aaargh')) ;
function* fetchObjectList() { let response = yield fetch('json/entry.json'); let id = (yield response.json()).id; let specifics = yield fetch(`json/${id}`); var fetchedObjects = yield Promise.all( (yield specifics.json()).map(obj => fetch(`json/${obj.id}`)) ); return yield Promise.all(fetchedObjects.map(obj => obj.json())); } co(function* () { try { let objects = yield* fetchObjectList(); console.log('objets:', ...objects); return objects; } catch(e) { console.error('aaargh', e); } });
Si on sait qu'on ne yield que des promises…
function co(generator) { let iterator = generator(); let runNext = function(promise) { promise.then(function(res) { let next = iterator.next(res); if (!next.done) { runNext(next.value); } }) .catch(function(err) { iterator.throw(err); }); } runNext(iterator.next().value); }
function* returnYield() { return yield 'A'; } let iterator = returnYield(); iterator.next(); // {value: 'A', done: false} iterator.next('coucou!); // {value: 'coucou!', done: true}