Publié le : 06/05/2021

Programmation réactive avec RxJS dans Javascript

Programmation réactive avec RxJS

Faire de la programmation réactive est actuellement incontournable que ce soit pour le web ou pour les mobiles. Et cette programmation réactive est actuellement possible dans Javascript avec RxJS.

C’est quoi cette programmation réactive.

La programmation réactive est un point de vue dans la programmation sur la manière d’écrire des codes et qui est principalement apparues avec les traitements de flux de données asynchrones. C’est une autre manière de développer des applications qui « réagiront » aux changements qui se produisent au lieu de la manière « traditionnelle » d’écrire un logiciel où on écrit explicitement le code pour gérer les changements (on appelle aussi la programmation « impérative »).

Les flux

Dans le monde de la programmation, les flux sont une séquence d’événements qui se déroule chronologiquement. Ces flux peuvent être des saisies effectuées par les utilisateurs ou le clic sur un bouton. Ces flux peuvent être écouté et on peut agir sur ces flux en fonction de ce que l’on veut faire. Pour ce faire, on utilise les fonctions pour traiter ces flux.

Les flux émet trois choses durant sa durée de vie, qui sont :

  • une valeur,
  • un erreur,
  • un signal de bon déroulement.

Nous capturons ces événements asynchrone et nous exécutons des fonctions adéquat en conséquence.

Les promise et les observable ont été créés pour résoudre ce problème qui sévit dans le domaine de l’asynchrone.

Les différents types d’opérations asynchrone dans les applications web moderne

Nous pouvons cité les types d’opérations asynchrone suivant que l’on rencontre dans les applications web moderne :

RxJS dans Javascript : la programmation réactive

Voyons maintenant comment fonctionne RxJS. Pour ce faire, nous allons voir la notion Observable et tout ce qui en découle dans la programmation en Javascript.

C’est quoi Observable ?

Obsrvable est une fonction avec des caractéristiques spécial. Il prend en paramètre un Observer (c’est un objet avec les méthodes next, error et complete) et retourne un logique d’annulation. De plus, Observable fournit un support pour échanger des messages entre le publishers et le subscribers dans votre application. Il offre aussi des bénefices significatives dans la capture des événements, la programmation asynchrone et la manipulation de plusieurs valeurs. Mais aussi, Observable ne produit les données que lorsqu’on y souscrit : subscribe, c’est ce qu’on appelle le traitement asynchrone lazy. Enfin, les observable avec RxJS fournit plusieurs fonctions que l’on peut utiliser pour créer de nouvelles observables. Et ces fonctions simplifie le processus de création des Observables pour les événements, les timers, les promises, etc…

Nous pouvons donner un exemple (ci-dessous) de code dans Javascript de création d’un observable avec RxJS.

    const button = document.querySelector("button");
    const observer = {
      next: function(value) {
        console.log(value);
      },
      error: function(err) {
        console.error(err);
      },
      complete: function() {
        console.log("Completed");
      }
    };
    const observable = Rx.Observable.fromEvent(button, "click");
    observable.subscribe(observer);

Inscription (Subscribe)

Après avoir vu les Observable, nous allons voir les Subscribe.

Comme mentionné auparavant les Obsevable sont de type lazy, c’est à dire qu’ils ne traitent les valeurs que lorsqu’il y a quelqu’un s’y inscrits (subscribe). On s’inscrit on appelant la fonction subscribe() de l’instance de l’objet en passant comme paramètre un objet observer pour recevoir les notifications.

Mais en plus de la fonction subscribe, nous avons un autre fonction importante qui est unsubscribe(). Il ne prend aucun argument mais possède les ressources tenu par subscribe. On peut voir cette fonctionnalité à travers l’exemple de code un peu plus haut.

 const button = document.querySelector("button");
    const observable = Rx.Observable.fromEvent(button, "click");
    const subscription = observable.subscribe(event => console.log(event));
    


    subscription.unsubscribe();

Observer

Un observer est un objet avec trois fonctions qui sont next, error et complete. L’observer, comme vu dans l’exemple un peu plus haut, est le paramètre passé à la fonction subscribe. Après que l’observable produit une valeur, il informe l’observer en appelant la fonction next, et s’il y a une erreur, il appelle la fonction error. De plus, lorsqu’on souscrit à un observable, il va continuer à passer des valeurs à un observer jusqu’au signal complete.

Opérateurs

Les opérateurs sont des fonctions qui ont été construit sur la base des Observables afin de permettre de faire des manipulations sophistiqué sur les collections. Un opérateur est essentiellement une fonction qui prend en paramètre un Observable et retourne un Observable. Il existe plusieurs opérateurs pour plusieurs cas que l’on peut catégoriser par :

  • Création,
  • Transformation,
  • Filtre,
  • Combinaison,
  • Multicasting,
  • Gestion des erreurs,
  • Utilitaire,
  • etc…

Les opérateurs passent chaque valeur à un opérateur au suivant avant de traiter la valeur suivante dans l’ensemble. C’est différent des opérateurs tableaux (map et filter) qui traite le tableau en entier à chaque étape.

Nous allons voir un exemple sur l’utilisation d’opérateur ci-dessous.

    const observable = Rx.Observable.of(1, 2, 3).map(value => value * value);

    observable.subscribe(x => console.log(x));
    // Sortie :
    // 1
    // 4
    // 9

RxJS fournis plusieurs opérateurs, mais seule une poignée est utilisée fréquement. La documentation de l’API RxJS donne une liste détaillée des opérateurs et des cas d’utilisations. Le tableau suivant nous donne une liste des opéarteurs les plus utilisé.

CatégorieOpérateurs
Créationfrom, fromPromise, fromEvent, of
CombinaisoncombineLatest, concat, merge, startWith, withLatestFrom, zip
FiltredebounceTime, distinctUntilChanged, filter, take, takeUntil
TransformationbufferTime, concatMap, map, mergeMap, scan, switchMap
Utilitairetap
Multicastingshare

Subject

Dans RxJS, Subject est un type spécial d’Observable qui permet aux valeurs d’être envoyé vers de nombreux observer. Alors que les observables simples sont en monodiffusion. Un sujet dans RxJS est un hybride car il peut agir comme un Observable ou comme un Observer en même temps. Dans le code Javascript qui utilise RxJS ci-dessous, nous avons deux Observer qui sont lié à un sujet et nous allons envoyer des valeurs au Subject.

    const subject = new Rx.Subject();

    subject.subscribe({
      next: v => console.log("observerA: " + v)
    });
    subject.subscribe({
      next: v => console.log("observerB: " + v)
    });

    subject.next(1);
    subject.next(2);

    // Sortie : 
    // observerA: 1
    // observerB: 1
    // observerA: 2
    // observerB: 2

Observable vs Promise

Pour une meilleure compréhension, nous allons faire une étude comparatif de l’API Promise de l’ES6 au librairie Observable RxJS. Nous verrons la similitude et la différence entre le Promise et l’Observable. Et pourquoi on préfère utiliser les Observables au lieu des Promise dans certains conditions.

Une seule valeur vs Plusieurs valeurs

Si nous allons effectuer une requête au Promise et attendre une réponse, nous aurons une seule et unique réponse à cette requête. De plus, le promise est toujours résolu avec la première valeur passé au fonction resolve et ignore les autres appels. Et contrairement au Promise, les Observables permettent de traiter plusieurs valeurs jusqu’à ce qu’on appelle observer.complete().

Ci-dessous un exemple pour illustrer celà :

// Création d'un Promise en utilisant l'API Promise d'ES6
const demoPromise = new Promise((resolve, reject) => {
  asyncOperation((err, value) => {
    if (err) {
      reject(err); // s'il y a une erreur, on capture cet erreur dans la chaine .catch()
    } else {
      resolve(value); // réception d'une valeur, nous récupérons cette valeur à l'intéreur de la chaine .then()
    }
  });
});

// Création d'un Observable en utilisant l'API Rxjs.Observable
const demoObservable = Rx.Observable.create(observer => {
  asyncOperation((err, value) => {
    if (err) {
      observer.error(err); // au lieu de reject(err)
    } else {
      observer.next(value); // au lieu de resolve(value)
      observer.complete(); // optionnel. une fois que la tâche asynchrone soit terminé on appelle 
 observer.complete()
    }
  });
});

Eager vs Lay

Les Promises sont eager ce qui veut dire que les promises feront les tâches que vous lui direz de faire dès que le constructeur du Promise est invoqué. De l’autre côté, les Observables sont lazy. Les constructeur des observables ne sont appelés que lorsque quelqu’un subscribes à un Observables. En d’autres termes, il ne se passe rien jusqu’à ce qu’on subscribe.

Pour illustrer celà, voici un exemple

// Promise a commencé à émettre des valeurs alors que nous n'avons pas appelé la méthode .then() sur le Promise
    const demoPromise = new Promise((resolve, reject) => {
      setTimeout(() => {
        console.log('emmit value');
        resolve(100);
      }, 3000);
    });

    // Observable n'a pas encore émit de valeur jusqu'à ce qu'on subscribe
    const demoObservable = new Observable(observer => {
      setInterval(() => {
        if (err) {
          observer.error('DemoError throw'); // instead of reject(err)
        } else {
          observer.next('value'); // à la place de resolve(value)
          observer.complete(); // optionnel. une fois que la tâche asynchrone ne se termine
        }
      });
    });

Not cancellable vs Cancellable

L’une des premières choses sur lesquelles les utilisateurs des Promises sont confrontés est de savoir comment annuler un Promise. Les Promises ES6 ne peuvent pas être annulé. Mais en utilisant les librairies comme bluebird ou axios, on peut annuler un Promise.

De l’autre côté, les Observables supporte l’annulation des tâches asynchrones en appelant la méthode unsubscribe().

Ci-dessous un exemple d’illustration.

    const observable = Rx.Observable.from([10, 20, 30]);
    const subscription = observable.subscribe(x => console.log(x));
    
    subscription.unsubscribe(); // arrête les tâches en cours

Exemples pratiques de l’utilisation de RxJS dans Javascript

Pour que vous ayez une plus grande compréhension de ce que l’on viens de parler, voici quelques exemples qui sont utilisé fréquemment quand on programme de manière réactive avec RxJS dans Javascript.

Création d’observable à partir des valeurs

const observable = Rx.Observable.of("foo", 
    98, 
    false, 
    ["john", "doe"], 
    {
        age: 19,
        gender: "male"
  });

  observable.subscribe(val => console.log(val));

Création d’observable à partir des flux de données

  const observable = Rx.Observable.create( observer => {
    observer.next('Bonjour');
    observer.next('C'est le temps de se réveiller !!');
  });

  observable.subscribe(value => console.log(value));
  // sortie :
  // Bonjour
  // C'est le temps de se réveiller

Observable à partir des événements DOM

    const button = document.querySelector('button');
    const observable = Rx.Observable.fromEvent(button, 'click');
    observable.subscribe(event => console.log(event));

Création d’observable à partir de promise

    const promise = new Promise((resolve, reject) => {
    asyncOperation((err, value) => {
      if (err) {
        reject(err);
      } else {
        resolve(value);
      }
    });
  });

  const Observable = Rx.Observable.fromPromise(promise);

  Observable.subscribe(value => console.log(value));

Utilisation de l’observable à partir des méthodes Timer

  const timer = Rx.Observable.timer(3000);

  timer.subscribe(() => console.log("timeout!!"));

Création d’un observable à partir de l’interval

   const interval = Rx.Observable.interval(3000);

  interval.subscribe(tick => console.log(`${tick} tick`));

Utilisation des opérateurs map

   const observable = Rx.Observable.from(2, 4, 6, 8);

  observable.map(value => value * value).subscribe(result => console.log(result));

Utilisation des opérateurs Do

    const dogs = Rx.Observable.of("Buddy", "Charlie", "Cooper", "Rocky");

    // Utilisation de l'opérateur do pour débugger
    dogs
      .do(dog => console.log(dog))
      .filter(dog => dog === "Cooper")
      .do(dog => console.log(dog))
      .subscribe(dog => console.log(dog));

Debounce and Throttle

Le Debounce est utilisé pour attendre X fois et envoi la dernière valeur.

De l’autre côté, Throttle donne la première valeur et attend X fois.

    const input = document.querySelector("input");
    const observable = Rx.Observable.fromEvent(input, "keyup");

    observable.debounceTime(3000).subscribe(event => console.log(event));

    observable.throttleTime(1000).subscribe(event => console.log(event));

Enfin, bufferTime collecte les données du passé sous forme de tableau et émet ces tableaux périodiquement dans le temps.

      const clicks = Rx.Observable.fromEvent(document, "click");
      const buffered = clicks.bufferTime(1000);
      buffered.subscribe(x => console.log(x));

Conclusion sur l’utilisation du RxJS dans Javascript

Les Promises sont le meilleur choix pour les opérations AJAX alors que les Observables sont extrêmement puissant pour le traitement de tâche asynchrone. De plus, nous avons vus que les Observables possèdent un pléthore d’opérateur pour créer, transformer, filtrer, etc…

Le contenu ci-dessus a été écrit en se basant sur :

Les mots clés rattachés à cet article : Javascript  -  Observable  -  Promise  -  RxJS

Nos clients

Une vingtaine de clients nationaux et internationaux