Comprendre RxJS

Reactive extensions for JavaScript (RxJS) est une librairie de flux reactifs qui vous permet de travailler avec des flux de données asynchrones. La programmation réactive est un paradime de programmation orienté flux de données et propagation des changements. RxJS peut être utilisé aussi bien dans un navigateur que côté serveur via Node.js.

Je ne vais pas vous parler spécifiquement d'Angular dans cet article mais plutôt de la librairie RxJS en tant que telle.

 

Un peu de sémantique …

Pour commencer, analysons le terme flux de données asynchrones :

Flux : Séquence de données disponibles au fils du temps. Le streaming vidéo est un bon exemple : vous n'avez pas à attendre la fin du téléchargement pour voir la video.

Données : Données brutes au format JavaScript tels que : Number, String, Object, …

Asynchrones : En Javascript, c'est le mécansime que vous permet d'appeler une fonction et d'enregistrer une callback qui sera exécutée à la fin du traitement. Cela permet de continuer l'exécution du script sans attendre cette réponse. Les cas les plus courants sont les appels XHR (Ajax), Les évènement du DOM, les promesses.

 

Avec RxJS, nous représentons les flux de données asynchrones à l'aide de séquences d'observables. Il est possible d'utiliser les observables dans les patterns push et pull :

  • Lorsque vous appliquez le pattern push, vous souscrivez (subscribe) à un flux source et vous réagissez aux données dès qu'elles sont disponibles.
  • Lorsque vous appliquez le pattern pull, vous souscrivez de manière synchrone. C'est ce qui se passe lorsque l'on utilise les fonctions des tableaux (map(), …) par exemple. 

La comparaison avec les promesses est essentielle mais également regrettable. Cela reste cependant un excellent point de départ pour comprendre les observables. La comparaison entre le then des promesses et le subscribe des observables montrent l'intérêt de l'asynchronisation de nos développements. Cependant la comparaison ne va guère plus loin. Les promesses sont toujours multicast, la résolution ou le rejet des promesses se fait toujours de manière asynchrone. De l'autre coté, les observables sont parfois multicast, ils sont généralement asynchones, … 

Tout cela pour vous faire comprendre que vous avez, avec RxJS, d'autres questions à vous poser lorsque vous manipulez des observables. Mais commencons par revenir à la source des observables.

 

Le Pattern Observer

Les observables tirent leur nom du pattern Observer. Il s'agit d'un vieux pattern de conception software dans lequel un objet, nommé subject, maintient une liste d'Observers et les notifient à chaque changement d'état en appelant une de leurs méthodes. Ce pattern peut causer des fuites mémoires dans le cas où l'observer n'arrive pas à se désabonner s'il n'a plus rien à écouter. Pour se prémunir de cela, il est conseillé de mettre en place une Weak reference.  

 

 

 

Dans la librairie RxJS, Subjects hérite de Observable. Donc un Subject a les même opérateurs et méthodes que Observable. La différence entre un Subject et un Observable est que le Subject a un etat et une liste d'Observers tandis que l'Observable est simplement une fonction. Les Subject permettent de créer des sources de données tandis que les Observable emettent les données. 

 

RxJS combine des Subjects,  des Observables, des Operateurs, et des Observers. Les Observables envoient des notifications, les Opérateurs les transforment, tandis que les Observeurs les reçoivent. 

 

Commençons par l'objet Observer qui se charge de recevoir les notifications de l'Observable. Il s'agit d'un simple objet qui possède trois méthodes : next(), error() et complete(). next() est appelé à l'arrivée d'un élément, error() est appelé lorsqu'une erreur survient, et complete() est appelé en fin de séquence. Les Observers doivent se lier à une source de données et retourner une méthode permettant de détruire ce lien. Ils ont pour mission de pousser les données via l'appel de la méthode next(). Une fois leurs missions terminées, ils appellent la méthode complète(). Les Observers proposent un certain nombre de garanties :

  • Ils empêchent l'appel à next() avec complete() ou error()
  • Ils n'autorisent plus d'appel avec la désinscription
  • Les appels a complete() ou error()  font appel à la désinscription
  • Si l'une des trois méthodes lance une exception, le mécansime de désincription d'execute.

 

Voici à quoi ressemble la création d'un Observeur avec le framework RxJS : 

 

 

Notre Observer attend donc trois callbacks permettant de réagir aux appels des trois méthodes next(), complete() et error() :

  • onNext(), appelé pour chaque élément de la séquence d'observable
  • onError(), appelé seulement en cas d'erreur
  • onCompleted(), appelé seulement à la fin de la séquence.

 

De son côté un Observable RxJS est simplement une fonction qui prend en paramètre votre Observer RxJS et qui retourne une fonction d'annulation. Voici une implémentation basique d'un observable : 

 

 

L'Observer qui sera passé en paramètre de l'Observable est celui que pour avez définit en paramètre de la méthode subscribe().

 

Pour arrêter l'écoute des changements, vous devez appeler la méthode unsubscribe() sur la référence de la souscription :

 

 

Il est très important de bien veiller à la fin de vie de vos Observables. Il y a toujours un risque de fuite mémoire lorsque l'on manipule les Observable. Dans le cas de l'utilisation de la méthode unsubscribe(), la phase OnDestroy() est un bon endroit pour les réaliser.

Il n'est pas nécessaire de toujours faire appel à la méthode unsubcribe(). L'Observable peut, dans de nombreux cas, s'arreter naturellement à l'aide des opérateurs take(), takeWhile(), until() ou first(). Vous pouvez consulter cet article de Ben Lesh pour en savoir plus.  

 

La suite de cet article est disponible dans le livre Learn-Angular – Maîtriser les concepts du framework Angular pour développer des applications robustes.

 

Le livre est disponible sur 

 

Quelques sujets abordés dans l'article complet :

  • Comprendre les Observables chauds ou froids
  • Les principaux opérateurs
  • Exemple d'implémentation d'un drap & drop

 

William Koza

William Koza

Consultant Indépendant chez  
Passionné par la conception et le développement logiciel, j’ai rapidement pris le rôle de Technical Leader lors de mes premiers projets. Ces expériences ont ainsi pu me faire accéder à des rôles d’architecte dans des projets d’envergure. Aujourd'hui, j'exerce mon métier en tant qu'indépendant, et toujours avec la même passion.
William Koza

Les derniers articles par William Koza (tout voir)