Over ES6
ES6 is een gevoelige uitbreiding en modernisering van Javascript als taal. Dit gebeurt in verschillende stappen, genoemd naar het jaartal waarin ze doorgevoerd zijn. Enkele voorbeelden:
- ECMAScript 2015:
let
enconst
, default parameters voor methodes,Array.find()
... - ECMAScript 2016: exponentiële operator
**
,Array.includes()
... - ECMAScript 2017: async methodes,
Object.values()
... - ECMAScript 2018: rest / spread oproperties, aanvullingen voor reguliere expressies...
- ECMAScript 2019:
String.trimStart()
enString.trimEnd()
,Array.flat()
... - ECMAScript 2020: nullish coalescing operator
??
- ECMAScript 2021:
String.replaceAll()
, private methodes... - ECMAScript 2022: top level await, static class fields en methodes...
- ECMAScript 2023:
Array.findLast()
,Array.toSorted()
...
Kort door de bocht is ES5 oude javascript, en ES6 nieuwe. Elk jaar komen er features bij. In dit hoofdstuk geven we er een paar van de meest interessante.
Operators
** machtsverheffing
Beter laat dan nooit, de **
machtsverheffing:
// ES5
console.log(Math.pow(2, 3)); // 8
// ES6
console.log(2 ** 3); // 8
Variabelen
object shorthand property
// ES5
var name = 'Bobby', age = 42;
var person = {name: name, age: age};
// ES6
const name = 'Bobby', age = 42;
const person = {name, age}; // shorthand
method shorthand property
// ES5
var obj = {
sum: function(a, b) {
return a + b;
}
};
console.log(obj.sum(6, 8)); // 14
// ES6
const obj = {
sum(a, b) { // shorthand
return a + b;
}
};
console.log(obj.sum(6, 8)); // 14
...rest parameter
Aggregeer overblijvende parameters in één enkele ...rest parameter:
// ES6
const test = function(x, y, ...a) {
return (x + y) * a.length;
}
console.log(test(1, 2, "hello", true, 7)); // 9
...spread operator
Zet zowat alles om naar een vlakke array. Spreid b.v. een array of string in individuele elementen:
// ES6
const str = "hello";
const chars = [ ...str ]; // ["h", "e", "l", "l", "o"]
const options = ["green", true, 7];
const extendedOptions = [1, 2, ...options]; // [1, 2, "green", true, 7]
Eenvoudige concatenatie van arrays:
// ES6
const arr = [1, 2, ...[3, 4, 5], 6, 7];
console.log(arr); // [1, 2, 3, 4, 5, 6, 7]
Kan ook gebruikt worden om querySelectorAll()
te converteren naar een array:
<ul>
<li><a href="">link 1</a></li>
<li><a href="">link 2</a></li>
<li><a href="">link 3</a></li>
</ul>
<p id="msg"></p>
const links = document.querySelectorAll('ul a');
const arrLinks = [...links];
document.querySelector('#msg').innerHTML = `
<code>document.querySelectorAll('ul a')</code>: ${Array.isArray(links) ? 'wel' : 'geen'} array
<br><code>[...document.querySelectorAll('ul a')]</code>: ${Array.isArray(arrLinks) ? 'wel' : 'geen'} array`;
destructureren
Destructureren verdeelt een array over variabelen.
voorbeeld 1: waarden verdelen over een array
// ES5
var list = [77, true, 'hello'];
var a = list[0];
var b = list[1];
var c = list[2];
// ES6
const list = [77, true, 'hello'];
const [a, b, c] = list; // array destructuring
console.log(a); // 77
console.log(b); // true
console.log(c); // 'hello'
Je kan eenvoudig waarden skippen:
// ES6
const list = [77, true, 'hello'];
const [a, , c] = list;
console.log(a); // 77
console.log(c); // 'hello'
voorbeeld 2: standaardwaarden voor arrays
// ES6
const list = [7, 42];
const [a = 1, b = 2, c = 3, d] = list;
console.log(b); // 42
console.log(c); // 3
console.log(d); // undefined
voorbeeld 3: waarden wisselen
// ES6
const [a, b] = [b, a]; // waarden worden verwisseld
voorbeeld 4: meerdere waarden retourneren uit een methode
// ES6
const func = function() {
return [1, true, 'hello', {}]; // return multiple values
}
const [a, , b, ] = func();
console.log(a); // 1
console.log(b); // 'hello'
voorbeeld 5: een object destructureren in variabelen
// ES6
const test = {
name: 'Rogier',
address: {
street: 'Kerkstraat',
number: 33
},
age: 42
};
const { name, address } = test; // object destructuring
console.log(name); // Rogier
console.log(address); // {street: "Kerkstraat", number: 33}
console.log(address.number); // 33
Voorbeeld met expliciete namen van variabelen en deep matching:
// ES6
const test = {
name: 'Rogier',
address: {
street: 'Kerkstraat',
number: 33
},
age: 42,
isMale: true
};
const { name: n, address: {street: s}, age: a } = test; // object destructuring
console.log(n); // Rogier
console.log(s); // 'Kerkstraat'
console.log(a); // 42
Functies
arrow functies
Een arrow functie is een verkorte notatie voor functies (zoals lambda expressies in C#):
// ES6
// zonder parameters
const sayHello = () => console.log('hello');
sayHello(); // hello
// één parameter
const double = a => a * 2;
console.log(double(4)); // 8
// meerdere parameters
const sum = (a, b) => a + b;
console.log(sum(2,4)); // 6
currying functies
Een currying functie is een functie die een functie teruggeeft
// ES5
var highpass = function highpass(cutoff) {
return function (n) {
return n >= cutoff;
};
};
const gt4 = highpass(4);
console.log(gt4(6)); // true
console.log(gt4(3)); // false
// ES6
const highpass = cutoff => n => n >= cutoff;
const gt4 = highpass(4);
console.log(gt4(6)); // true
console.log(gt4(3)); // false
default parameters
// ES5
function f (x, y, z) {
if (y === undefined) y = 7;
if (z === undefined) z = 42;
return x + y + z;
};
// ES6
function f (x, y = 7, z = 42) {
return x + y + z;
}
generator functies
Een generator is een functie die elke keer een verschillende waarde teruggeeft. Telkens next()
opgeroepen wordt, loopt het tot de volgende yield
.
generator basisvoorbeeld
// ES6
// define a generator
const someGenerator = function*() {
yield 'hello';
yield 9;
yield true;
}
// calling the generator returns an iterator object
const gen = someGenerator();
// call the next iterator value
console.log(gen.next().value); // 'hello'
console.log(gen.next().value); // 9
console.log(gen.next().value); // true
console.log(gen.next().value); // undefined
In feite geeft next()
een object terug van de vorm { value: ..., done: ... }
// ES6
const someGenerator = function*() {
yield 'an';
yield 'bob';
}
const gen = someGenerator();
console.log(gen.next()); // {value: "an", done: false}
console.log(gen.next()); // {value: "bob", done: false}
console.log(gen.next()); // {value: undefined, done: true}
generator met parameters
Voorbeeld met parameters:
// ES6
const counterGenerator = function*(start, step) {
const number = start;
while(true) {
yield number;
number += step;
}
}
const gen = counterGenerator(100, 3);
console.log(gen.next().value); // 100
console.log(gen.next().value); // 103
console.log(gen.next().value); // 106
Fibonacci met generator
// ES6
function* fibonacci(n) {
const infinite = !n && n !== 0;
let current = 0;
let next = 1;
while (infinite || n--) {
yield current;
[current, next] = [next, current + next];
}
}
// oldskool:
const fibGenerator = fibonacci();
console.log(fibGenerator.next().value); // 0
console.log(fibGenerator.next().value); // 1
console.log(fibGenerator.next().value); // 1
// generators are iterables, so for...of work, as do spreads:
const [...first10] = fibonacci(10);
console.log(first10); // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
generator delegatie
Je kan vanuit één generator delegeren naar een andere:
// ES6
function* g1() {
yield 2;
yield 3;
yield 4;
}
function* g2() {
yield 1;
yield* g1();
yield 5;
}
const iterator = g2();
const next = iterator.next();
while(!next.done) {
console.log(next.value); // 1 2 3 4 5
next = iterator.next();
}
- voor een meer diepgaande uitleg zie ponyfoo.com
Collecties
Set
Een Set is als een array, maar zonder dubbele waarden:
// ES6
const x = new Set([1, 2, 3, 4, 4, 4, 5]); // kopieer array, maar verwijder dubbels
x.add(6);
x.delete(2);
console.log(`The set contains ${x.size} elements.`); // 5
console.log(`The set has 1: ${x.has(1)}`); // true
console.log(`The set has 8: ${x.has(8)}`); // false
// output:
// 1
// 2
// 4
// 5
// 6
for (const value of x) {
console.log(value);
}
- andere methodes zijn
clear()
,forEach()
envalues()
Map
Een Map is als een object, maar alles kan key zijn:
// ES6
const x = new Map([
[(new Date()).toLocaleDateString('be-NL'), function today () {}],
[y => y * 2, { pony: 'foo' }],
['items', [1, 2]]
]);
x.delete('items');
x.set('author', 'Rogier');
// output:
// 3/20/2024: function today () {}
// y => y * 2: [object Object]
// author: Rogier
for (const [key, value] of x) {
console.log(`${key}: ${value}`);
}
- andere methodes zijn
clear()
,forEach()
envalues()
WeakSet en WeakMap
Zelfde als Map
en Map
, met enkele beperkingen:
- je kan enkel objecten toevoegen (WeakSet) of als keys gebruiken (
WeakMap
) - je kan niet itereren, dus geen
size
,for...in
enz...
Belangrijkste voordeel: elke item wordt automatisch opgekuist door de garbage collector als er geen referenties meer zijn. Een vergelijking van Set
en WeakSet
:
// ES6
const set = new Set();
const weakset = new WeakSet();
(function() {
const a = {x: 12};
const b = {y: 12};
set.add(a);
weakset.add(b);
})(); // a and b only exist within function
console.log(set); // set bevat a tot je het manueel verwijdert
console.log(weakset); // weakset bevat b tot garbage collected
Gebruik weak collections in plaats van Map
en Set
waar mogelijk om geheugenleks te vermijden. Typische use cases:
- organiseer objecten in groepen (
WeakSet
) - hou bij welke objecten al zijn verwerkt (
WeakSet
) - breid objecten uit zonder de garbagecollection te verstoren (
WeakMap
)
Weak collections zijn alleen nuttig voor garbage collection van objecten; het heeft dus geen zin object literals als keys te gebruiken:
// ES6
const weakmap = new WeakMap();
weakmap.set(new Date(), 111); // heeft geen zin
weakmap.set({}, 222); // heeft geen zin
Iterators
Een iterator definieert hoe een object wordt herhaald met for...in...
Het is beschikbaar als de geëvalueerde property [Symbol.iterator]
Het retourneert een waarde {waarde: ..., klaar: ...}
Voorbeeld 1: basisvoorbeeld
// ES6
const foo = {};
foo[Symbol.iterator] = function() {
const items = ['alice', 'bob', 'clive', 'dave'];
return {
next: function () {
return {
done: items.length === 0,
value: items.shift()
}
}
}
};
for (const item of foo) {
console.log(item); // alice bob clive dave
}
Kortere syntax:
// ES6
const foo = {};
foo[Symbol.iterator]() {
return {
items: ['alice', 'bob', 'clive', 'dave'],
next: function () {
return {
done: this.items.length === 0,
value: this.items.shift()
}
}
}
};
for (const item of foo) {
console.log(item); // alice bob clive dave
}
Voorbeeld 2: Fibonacci
// ES6
const fibonacci = {
[Symbol.iterator]() {
let pre = 0, cur = 1;
return {
next () {
[pre, cur] = [cur, pre + cur];
return { done: false, value: cur };
}
};
}
};
for (const n of fibonacci) {
if (n > 1000) break;
console.log(n);
};
Voorbeeld 3: prioriteiten
// ES6
const custom_collection = {
elements: [
{val: 111, priority: 1},
{val: 222, priority: 2},
{val: 333, priority: 1},
{val: 444, priority: 3},
{val: 555, priority: 2}
],
[Symbol.iterator]: function() {
const e = this.elements;
const setDone = new WeakSet();
let numReturned = 0;
return {
next: function() {
if (numReturned == e.length) {
return { value: undefined, done: true };
}
let prior = -1;
let retval = undefined;
for (const elem of e) {
if (setDone.has(elem)) continue;
if (elem.priority > prior) {
prior = elem.priority;
retval = elem;
}
}
numReturned++;
setDone.add(retval);
return { value: retval.val, done: false };
},
};
}
}
for(const i of custom_collection) {
console.log(i); // 444 222 555 111 333
};
OO concepten
Classes in javascript
// ES5
var Shape = function (id, x, y) {
this.id = id;
this.move(x, y) = function (x, y) {
this.x = x;
this.y = y;
};
};
// ES6
class Shape {
constructor (id, x, y) {
this.id = id;
this.move(x, y);
}
move (x, y) {
this.x = x;
this.y = y;
}
};
const dot1 = new Shape('dot 1', 25, 50);
console.log(`shape ${dot1.id} has position (${dot1.x},${dot1.y})`);
Static class members
// ES6
class Rectangle {
...
static defaultRectangle () {
return new Rectangle("default", 0, 0, 100, 100)
}
}
const defRectangle = Rectangle.defaultRectangle();
Overerving
// ES5
var Rectangle = function (id, x, y, width, height) {
Shape.call(this, id, x, y);
this.width = width;
this.height = height;
};
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;
var Circle = function (id, x, y, radius) {
Shape.call(this, id, x, y);
this.radius = radius;
};
Circle.prototype = Object.create(Shape.prototype);
Circle.prototype.constructor = Circle;
// ES6
class Rectangle extends Shape {
constructor (id, x, y, width, height) {
super(id, x, y);
this.width = width;
this.height = height;
}
}
class Circle extends Shape {
constructor (id, x, y, radius) {
super(id, x, y);
this.radius = radius;
}
}
Modules
Modules nemen in Javascript de rol van namespaces op zich.
// ES5
// libs/SomeLibrary/SomeApi.js
SomeLibrary = SomeLibrary || {};
SomeLibrary.SomeApi = {};
SomeLibrary.SomeApi.function1 = function () { ... };
SomeLibrary.SomeApi.function2 = function () { ... };
SomeLibrary.SomeApi.var1 = ...;
SomeLibrary.SomeApi.var2 = ...;
// use in someApp.js
var api = SomeLibrary.SomeApi; // use shortcut if you like
console.log(api.function2(...));
// use in otherApp.js
var api = SomeLibrary.SomeApi; // use shortcut if you like
console.log(api.function1(...));
console.log(api.var2);
// ES6
// libs/SomeLibrary/SomeApi.js
export function1 = function () { ... };
export function2 = function () { ... };
export const var1 = ...;
export const var2 = ...;
// use in someApp.js
import * as api from "libs/SomeLibrary/SomeApi"
console.log(api.function2(...));
// use in otherApp.js
import {function1 as f1, var2 as v2} from "libs/SomeLibrary/SomeApi"
console.log(f1(...));
console.log(v2);
Proxies
Proxies laten toe code uit te voeren wanneer properties van een object gelezen of geschreven worden (vergelijkbaar met getter en setter methodes van properties in C#). In zijn eenvoudigste vorm is het niet meer dan een passthrough:
// ES6
const handler = {}
const target = {}
const proxy = new Proxy(target, handler)
proxy.a = 'Alice' // property set
proxy.b = 'Bob' // property set
console.log(proxy.a); // property get; identical to target.a
console.log(proxy.b); // property get; identical to target.b
Met getter en setter code:
// ES6
const handler = {
// proxy trap for 'get'
get (target, key) {
console.info(`Get on property ${key}`);
return target[key];
},
// proxy trap for 'set'
set (target, key, value) {
console.info(`Set on property ${key}`);
target[key] = value;
return true;
}
};
const target = {};
const proxy = new Proxy(target, handler)
proxy.a = 'Alice'; // property set
proxy.b = 'Bob'; // property set
console.log(proxy.a); // property get
console.log(proxy.b); // property get
→ zie ook developer.mozilla.org documentatie
Proxies laten toe de toegang tot properties te controleren, b.v. maak alle '_'-prefixed properties private:
// ES6
const handler = {
get (target, key) {
if (key[0] === '_') throw new Error(`Invalid attempt to get private "${key}" property`);
console.info(`Get on property ${key}`);
return target[key];
},
set (target, key, value) {
if (key[0] === '_') throw new Error(`Invalid attempt to set private "${key}" property`);
console.info(`Set on property ${key}`);
return true;
}
};
const target = {};
const proxy = new Proxy(target, handler);
proxy.a = 'Alice';
proxy.b = 'Bob';
proxy._c = 'Clive';
Validatie met een proxy:
// ES6
const handler = {
set (target, key, value) {
if (key == 'age' && value < 0) throw new Error(`age cannot be negative`);
return true;
},
};
const person = {};
const proxy = new Proxy(person, handler);
proxy.age = -1; // throws an error
Helemaal veilig is het nog niet. Merk op dat je de properties nog steeds rechtstreeks kan veranderen:
// ES6
const handler = {
set (target, key, value) {
if (key == 'age' && value < 0) throw new Error(`age cannot be negative`);
return true;
}
};
const person = {};
const proxy = new Proxy(person, handler);
person.age = -1; // no problem
Promises
Promises zijn een belangrijk begrip in Javascript, waar veel asynchroon verloopt, i.e. terwijl één deelcode aan de uitvoering bezig is (API call, inladen resource, complexe berekening...) loopt de rest van de code verder. Als de deelcode klaar is, wordt het resultaat (of een foutmelding) teruggegeven aan het hoofdprogramma via een callback methode.
Om de syntax van al die callbacks wat te stroomlijnen, werden in ES6 promises geïntroduceerd.
Een promise is een belofte van een stuk code om iets uit te voeren. Het heeft drie toestanden:
- pending: nog bezig
- resolved: uitgevoerd
- rejected: gefaald
Basis promise
[daughter] — "I promise to clean my room today"
[mom] — "Ok. If you do, I'll give you a candy. But if you don't, I'll tell dad about it."
// ES6
// daughter's promise
const cleanRoom = new Promise(function (resolve, reject) {
if (Math.random() > 0.5) resolve(); // 50% chance that promise is kept
else reject();
})
// mom's reactions
cleanRoom
.then(function() { console.log('well done — here is your candy') })
.catch(function() { console.log('boo — I\'ll have to tell dad') });
Ietsje netter geschreven met arrow notaties:
// ES6
// daughter's promise
const cleanRoom = new Promise(function (resolve, reject) {
if (Math.random() > 0.5) resolve(); // 50% chance that promise is kept
else reject();
})
// mom's reactions
cleanRoom
.then(() => console.log('well done — here is your candy'))
.catch(() => console.log('boo — I\'ll have to tell dad'));
Zelfde, maar dan met de .then(result => {...}, error => {...})
otatie:
// ES6
// daughter's promise
const cleanRoom = new Promise(function (resolve, reject) {
if (Math.random() > 0.5) resolve(); // 50% chance that promise is kept
else reject();
})
// mom's reactions
cleanRoom.then(
() => console.log('well done — here is your candy'),
() => console.log('boo — I\'ll have to tell dad')
);
Een promise kan maar één keer gesettled worden (d.w.z. veranderen van pending naar resolved of rejected)
// ES6
const promise = new Promise(function (resolve, reject) {
console.log('pending');
const delay = 6000; // change to 2000 and run again
setTimeout(resolve, delay);
setTimeout(reject, 3000);
});
promise
.then(() => console.log('settled (resolved)'))
.catch(() => console.log('settled (rejected)'));
Data meegeven
[daughter] — "I promise to clean my room today"
[mom] — "Ok. If you do, you can pick a candy. If not, I want to hear your excuse."
// ES6
// daughter's promise
const cleanRoom = new Promise(function (resolve, reject) {
// 50% chance that promise is kept
if (Math.random() > 0.5) resolve('chocolate bar');
else reject('fell asleep');
})
// mom's reactions
cleanRoom
.then(result => console.log(`well done — here is your ${result}`))
.catch(error => console.log(`boo — you ${error}`));
Multiple branches
[daughter] — "I promise to clean my room today"
[mom] — "Ok. If you do, I'll give you a candy. If you don't, I won't care."
[dad] — "If you don't, I'll spank you."
// ES6
// daughter's promise
const cleanRoom = new Promise(function (resolve, reject) {
if (Math.random() > 0.5) resolve(); // 50% chance that promise is kept
else reject();
})
// mom's reactions: she only awards
cleanRoom
.then(() => console.log('well done — here is your candy'))
.catch(() => {});
// dad's reactions: he only punishes
cleanRoom.catch(error => console.log('Here is your spanking'));
→ een branch moet altijd eindigen met catch
om fouten op te vangen
Promise chaining
[dad] — "I promise to fix the car today"
[mom] — "good, because I promised we would take the kids to the park."
[big brother] — "little brother, i will ask mom if we go to the park"
// ES6
// dad's promise
const fixCar = new Promise(function (resolve, reject) {
// 50% chance dad fixes the car
if (Math.random() > 0.5) resolve('it was the carburator');
else reject('dad didn\'t find the problem');
});
// mom's promise
const goToPark = new Promise(function (resolve, reject) {
// 25% chance mom's got a headache
if (Math.random() > 0.75) {
reject('mom has a headache');
fixCar.catch(() => {}); // it doesn't matter if the car gets fixed or not
}
// depends on whether dad gets the car fixed or not
else fixCar.then(res => resolve(res), err => reject(err));
});
// big brother's promise
const tellLittleBrother = new Promise(function (resolve, reject) {
goToPark.then(
res => {
resolve({msg: `ok, we can go, ${res}`, success: true});
},
err => {
resolve({msg: `sorry bro', we cannot go, ${err}`, success: false});
}
);
});
// little brother's reaction
tellLittleBrother.then(
res => {
console.log(`[big brother] ${res.msg}`);
if(res.success) {
console.log('[little brother] yippeee!');
} else {
console.log('[little brother] wèèèh!');
}
}
); // no need to catch here; big brother always keeps his promise
Met extra berichten en een paar delays om het realistischer te maken:
// ES6
const fixCar = new Promise(function (resolve, reject) {
console.log('---- dad starts fixing the car');
// 50% chance dad fixes the car
setTimeout(
Math.random() > 0.5 ?
() => {
console.log('---- dad found the problem');
resolve('it was the carburator')
} : () => {
console.log('---- dad gives up');
reject('dad didn\'t find the problem')
},
2000
);
});
// mom's promise
const goToPark = new Promise(function (resolve, reject) {
// 25% chance mom's got a headache
if (Math.random() > 0.75) {
console.log('---- mom starts developing a headache');
setTimeout(() => reject('mom has a headache'), 300);
fixCar.catch(() => {}); // it doesn't matter if the car gets fixed or not
}
// depends on whether dad gets the car fixed or not
else {
fixCar.then(
res => {
console.log('---- mom notifies big brother');
setTimeout(() => resolve(res), 1000)
},
err => {
console.log('---- mom notifies big brother');
setTimeout(() => reject(err), 1000)
}
);
}
});
// big brother's promise
const tellLittleBrother = new Promise(function (resolve, reject) {
goToPark.then(
res => {
console.log('---- big brother runs to little brother with a smile');
setTimeout(() => {
resolve({msg: `ok, we can go, ${res}`, success: true});
}, 1000)
},
err => {
console.log('---- big brother runs to little brother with a frown');
setTimeout(() => {
resolve({msg: `sorry bro', we cannot go, ${err}`, success: false});
}, 1000)
}
)
});
// little brother's reaction
tellLittleBrother.then(
res => {
console.log(`[big brother] ${res.msg}`);
if(res.success) {
console.log('[little brother] yippeee!');
} else {
console.log('[little brother] wèèèh!');
}
}
); // no need to catch here; big brother always keeps his promise
Promise.All
[kids] — "Can we go to the park today?"
[mom] — "Only if you both clean your rooms"
// ES6
// kid1 promise
const cleanRoom1 = new Promise(function (resolve, reject) {
if (Math.random() > 0.5) resolve(); // 50% chance that promise is kept
else reject('kid 1 failed');
})
// kid2 promise
const cleanRoom2 = new Promise(function (resolve, reject) {
if (Math.random() > 0.5) resolve(); // 50% chance that promise is kept
else reject('kid 2 failed');
})
// all promises must be resolved:
Promise.all([
cleanRoom1,
cleanRoom2
]).then(() => console.log('well done, we go')).catch(err => console.log(err));
Promise.race
[kids] — "Can we have a candy?"
[mom] — "The first who cleans his room gets a candy."
// ES6
// kid1 promise
const cleanRoom1 = new Promise(function (resolve, reject) {
setTimeout(
() => resolve('kid 1 cleaned his room'),
Math.random() * 2000
)
})
// kid2 promise
const cleanRoom2 = new Promise(function (resolve, reject) {
setTimeout(
() => resolve('kid 2 cleaned his room'),
Math.random() * 2000
)
})
// first promise wins
Promise.race([
cleanRoom1,
cleanRoom2
]).then(res => console.log(`${res} first and gets the candy`));
Case study
We gooien volgende technieken samen in Node:
- een generator om URL's te genereren
- promises om de content van URL's te fetchen (HTML)
- Node modules om de HTML naar markup te parsen en te processen
Nodige mode modules:
{
...
"dependencies": {
"hget": "^3.1.0",
"marked": "^0.3.6",
"marked-terminal": "^2.0.0",
"request": "^2.79.0"
}
}
Het volledige script:
// ES6
// require modules
const request = require('request');
const hget = require('hget');
const marked = require('marked');
const MarkedTerminal = require('marked-terminal');
// define and create a generator for URL's
const urlGenerator = function*(){
const urls = [
'https://ikdoeict.be/',
'https://ikdoeict.be/opleiding-ict/'
];
while (urls.length) yield urls.shift();
}
const urlGen = urlGenerator();
// returns a promise to return the content found at an URL
const getNextPage = function () {
const url = urlGen.next();
return new Promise((resolve, reject) => {
if (url.done) {
reject('Done! No more pages');
return;
}
console.log(`Fetching next page at ${url.value}...`);
request(url.value, (err, res, body) => {
if (err) {
reject(err);
return;
}
resolve(body);
});
});
};
// displays and parses the content
const displayNextPage = function() {
getNextPage()
.then(html => hget(html, {
markdown: true,
root: 'div.main-content',
ignore: '.at-subscribe,.mm-comments,.de-sidebar'
}))
.then(md => marked(md, {
renderer: new MarkedTerminal()
}))
.then(txt => {
console.log(txt);
displayNextPage();
})
.catch(reason => console.error(reason));
}
// start recursive page call
displayNextPage();
Voer vervolgens het script uit: