n00b pro

ES6

Let op! De technieken uit dit hoofdstuk mag je niet gebruiken zonder expliciete toestemming van de docent.

Dit hoofdstuk is onderdeel van de cursus Javascript. Andere cursussen in dezelfde reeks: HTML, CSS, C#, Ontwikkelomgeving.

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:

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

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

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

WeakSet en WeakMap

Zelfde als Map en Map, met enkele beperkingen:

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:

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:

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:

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:

C:\Users\rogie\Desktop>node script.js
resultaat in de command prompt
resultaat

Nuttige links