JS Warmup

ça va bien se passer

Ça va bien se passer

Exercice 1 - On commence doucement

Codez une fonction sum prenant un nombre indéfini de paramètres et retournant la somme des nombres.

Correction :

// utilisation des rest paramters
function sum(...number) {
  // les rest parameters s'utilisent comme des tableaux
  // on peut donc utiliser la méthode "reduce" du prototype Array
  // pour faire la somme
  return numbers.reduce((acc, curr) => acc + curr, 0);
}

Note : pour la méthode reduce

const tableau = [4, 5, 6];
const reducerCallback = (accumulateur, valeurCourante) => { return accumulateur + valeurCourante }

tableau.reduce(reducerCallback, 0); // ici, je spécifie la valeur de départ
  • 1er tour de boucle : accumulateur vaut 0 (valeur de départ) et valeurCourante vaut 4 (première case du tableau) et reducerCallback retourne 4
  • 2ème tour de boucle : accumulateur vaut 0 + 4 = 4 et valeurCourante vaut 5 (deuxième case du tableau) et reducerCallback retourne 9
  • 3ème tour de boucle : accumulateur vaut 4 + 5 = 9 et valeurCourante vaut 6 (troisième case du tableau) et reducerCallback retourne 15
  • Après le troisième tour, c’est terminé, le tableau est parcouru. Et le résultat final est donc 15

Exercice 2 - Escalade Véloce 🚀

2A

Ecrivez une fonction isFunction qui prend un paramètre et qui vérifie qu’il s’agisse bien d’une fonction. Aidez vous de l’opérateur typeof.

isFunction devra fonctionner comme suit :

const f1 = () => {};
function f2() {}
const nombre = 12;

isFunction(f1); // retourne true
isFunction(f2); // retourne true
isFunction(nombre); // retourne false

Correction :

function isFunction(f) {
  // un simple "typeof" suffit
  return typeof f === 'function';
}

2B

Ecrivez une fonction filterNonFunctions qui prend en paramètre un nombre indéfini d’arguments sous la forme d’un rest parameter et qui retourne un tableau ne contenant que les arguments étant des fonctions. Pour cela, aidez-vous de la méthode filter du prototype Array.

Elle devra fonctionner comme suit :

const f1 = () => {};
function f2() {}
const nombre = 12;

const functions = filterNonFunctions(f1, nombre, f2); // [f1, f2]

Correction :

function filterNonFunctions(...functions) {
  // il suffit d'utiliser la méthode "filter"
  // du prototype Array et la méthode isFunction précédemment écrite
  return functions.filter(isFunction);
}

2C

Ecrivez désormais une fonction pipe qui fonctionne comme suit :

  • nombre indéfini de fonctions en entrée ;
  • filtre les arguments qui ne sont pas des fonctions ;
  • stocke le tableau résultant du filtre ;
  • retourne une nouvelle fonction
  • prenant un nombre indéfini d’arguments ;
  • exécute chaque fonction n en lui passant en argument le résultat de l’appel n - 1 ;
  • retourne le résultat final.

Exemple :

// On a des fonctions de base
const multiply = (x, y) => x * y;
const square = (x) => x * x;
const double = (y) => y * 2;
const addTen = (z) => z + 10;

// pipe prend un nombre indéfini de fonctions en argument
// Ici, on crée trois nouvelles fonctions
const piped1 = pipe(multiply, square, double, addTen);
const piped2 = pipe(square, double);
const piped3 = pipe();

// piped1(2, 3) va exécuter à la suite :
// multiply(2, 3), square(6), double(36), addTen(72)
const result1 = piped1(2, 3);

// piped2(4) va exécuter à la suite :
// square(4), double(16)
const result2 = piped2(4);

// piped3 ne va rien exécuter
const result3 = piped3();

console.log(result1); // affiche 86
console.log(result2); // affiche 32
console.log(result3); // undefined

Correction :

function pipe(...functions) {
  // d'abord on filtre les fonctions
  const fns = filterNonFunctions(...functions);
  // on retourne une nouvelle fonction
  return (...args) => {
    // si nous avons au moins une fonction
    if (fns.length > 0) {
      // on va faire un "reduce" un peu particulier
      return fns // reduce uniquement sur le tableau moins son premier élément
              .splice(1)
              // "val" est l'accumulateur, et "f" la valeur courante
              // fns[0](...args) est la valeur initiale
              .reduce((val, f) => f(val), fns[0](...args));    
    }
  }
};

Exercice 3 - Same, but different

Ecrivez une fonction compose qui fonctionne comme suit :

const multiply = (x, y) => x * y;
const square = (x) => x * x;
const double = (y) => y * 2;
const addTen = (z) => z + 10;

// compose prend un nombre indéfini de fonctions en argument
// et retourne une nouvelle fonction
const composed1 = compose((x) => multiply(x, 3), square, double, addTen);
const composed2 = compose(square, double);
// la nouvelle fonction prend un nombre indéfini d'arguments
// et exécute les différentes fonctions comme une composition mathématique
const result1 = composed1(2);
const result2 = composed2(4);

console.log(result1); // affiche 1728
console.log(result2); // affiche 64

Indice : compose est le pendant de pipe : au lieu d’exécuter les fonctions dans l’ordre passé, elle les exécute dans l’ordre inverse (type f(g(x)))

Correction :

function compose(...functions) {
  const fns = filterNonFunctions(...functions);
  return (...args) => {
    if (fns.length > 0) {
      // Pareil que pour "pipe", sauf qu'on "reverse" le tableau
      return fns.reverse().splice(1).reduce((val, f) => f(val), fns[fns.length -1](...args));
    }
  }
}

Exercice 4 - Promises et Observables

4A

Allez lire la documentation sur les Promises avant de faire l’exercice.

Ecrivez une fonction delay qui fonctionne comme suit :

const multiply = (x, y) => x * y;

// delay prend en paramètre une fonction et un timeout (millisecondes)
// et retourne une nouvelle fonction qui :
// - retournera une Promise
// - exécutera la fonction (ici multiply) après le timeout
const delayed = delay(multiply, 2000);
delayed(3, 4).then((res) => console.log(res)); // affichera 12 après 2s

Correction :

function delay(fn, timeout) {
  return (...args) => {

    // Cette fonction sera passée au constructeur de la Promise
    // Elle prend deux paramètres, resolve et reject : 
    // https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Promise
    const promiseCallback = (resolve, reject) => {

      const timedOutFunction = () => {
        let res;
        // Le try...catch n'était pas demandé
        // Je l'ai rajouté
        try {
          res = fn(...args);
          resolve(res);
        } catch (exception) {
          reject(exception)
        }
      };

      // On enclenche le setTimeout
      setTimeout(timedOutFunction, timeout);
    };

    return new Promise(promiseCallback);
  }
}

4B

On a la classe suivante :

class Observable {
  // rest parameters
  constructor(...args) {
    // on ne fait pas "this.values"
    // afin que le champ reste privé
    let values = args;
    // idem avec subscribers, qui sera un tableau de fonctions
    const subscribers = [];
    // la méthode "next" prend des rest parameters en entrée
    this.next = (...vals) => {
      // elle met à jours "values"
      values = [...vals];
      // et déclenche tous les traitements avec ces nouvelles valeurs
      subscribers.forEach((f) => f(...values));
    };
    // "subscribe" enregistre simplement une nouvelle fonction
    // qui sera exécutée à la prochaine valeur émise par "next"
    this.subscribe = (f) => {
      subscribers.push(f);
    };
  }
}

En vous aidant de cette classe, écrivez une fonction debounce de sorte à ce que le code suivant fonctionne :

const square = (x) => x ** 2;

// "debounce" prend une fonction ainsi qu'un timeout en paramètre
// et retourne un Observable
const debounced = debounce(square, 1000);

const subscriber = (res) => console.log("res", res);

// (1) DEVRAIT exécuter square après 1s et afficher 9
debounced(3).subscribe(subscriber);
// (2) se déclenchera après 1.5s, exécutera square et affichera 4
setTimeout(() => debounced(2), 1500);
debounced(10); // (3) annulera l'appel (1) et relancera un nouveau timeout

Indices :

  • vous pouvez vous aider de cet article, écrit par votre humble intervenant. Par contre l’implémentation qui s’y trouve n’est pas totalement celle demandée ici 🙃.

Correction :

function debounce(fn, timeout) {
  let timer;
  let obs = new Observable();
  return (...args) => {
    clearTimeout(timer);
    // À la fin du timeout, on n'a plus qu'à donner la nouvelle valeur à l'Observable
    timer = setTimeout(() => obs.next(fn(...args)), timeout)
    // On retourne l'Observable
    return obs;
  }
}

4C

Reprenez la fonction delay et, au lieu de retourner une Promise, retournez un Observable.

Correction :

function delay(fn, timeout) {
  let obs = new Observable();
  return (...args) => {
    setTimeout(() => obs.next(fn(...args)), timeout);
    return obs;
  }
}

4D

Dans la classe Observable, implémentez une méthode pipe : contrairement à la première fonction codée dans les précédents exercices, celle-ci ne retournera pas une fonction mais exécutera directement les fonctions passées en paramètre et retournera un Observable contenant le résultat.

Le code suivant doit fonctionner :

class Observable {
  constructor(...args) {
    let values = args;
    const subscribers = [];
    this.next = (...vals) => {
      values = vals;
      subscribers.forEach((f) => f(...values));
    };
    this.subscribe = (f) => {
      subscribers.push(f);
      f(...values);
    };
    this.pipe = (...functions) => {
      // TODO: implémentez cette méthode
      // Pensez à filtrer les arguments qui ne sont pas des fonctions
      // Vous devez retourner un Observable comportant le résultat final
      // Pour le reste, c'est sensiblement pareil que la précédente
      // fonction pipe()
    };
  }
}

new Observable(4)
  .pipe(
    (x) => x ** 2,
    (x) => ("" + x).split(""),
    (numbers) => numbers.map((x) => +x),
    (numbers) => numbers.reduce((acc, curr) => acc + curr, 0)
  )
  .subscribe((res) => console.log("resultat", res)); // 7

Correction :

class Observable {
  constructor(...args) {
    let values = args;

    const subscribers = [];

    this.next = (...vals) => {
      values = vals;
      subscribers.forEach((f) => f(...values));
    };

    this.subscribe = (f) => {
      subscribers.push(f);
      f(...values);
    };

    this.pipe = (...functions) => {
      // on filtre les fonctions
      const fns = filterNonFunctions(...functions);
      const obs = new Observable();
      // s'il en reste, alors on les exécute les unes à la suite des autres
      if (fns.length > 0) {
        const res = fns.slice(1).reduce((val, fn) => fn(val), fns[0](...values));
        obs.next(res);
      }
      // on retourne l'Observable
      return obs;
    };

    const filterNonFunctions = (...fns) => {
      return fns.filter((f) => typeof f === 'function');
    }
  }
}