Node.js Paris ∾ 19/01/2016
Fabien Mauquié
node
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*
yield
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() {
// ···
}
}
yield
et return
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
yield*
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' ]
yield
ne fonctionne que dans un générateur
function* aie() {
setTimeout(() => (yield));
}
// SyntaxError: arrow function may not contain yield
next(value)
return(value)
throw(error)
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}
yield
est asymétriqueyield
renvoie la valeur à sa droiteyield
yield
avec la valeur passée à next()
return()
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}
return()
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 !
throw()
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
yield*
?throw()
se comporte comme une exception, elle se propage du yield
où on est en pause vers l'appelantreturn()
se comporte aussi comme une exception (par rapport aux finally
)
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();
yield
er des promisesnext()
avec le résultatthrow()
avec l'erreur
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);
}
return yield
?
function* returnYield() {
return yield 'A';
}
let iterator = returnYield();
iterator.next(); // {value: 'A', done: false}
iterator.next('coucou!); // {value: 'coucou!', done: true}