Récemment, j'ai commencé à regarder les bibliothèques RxJS et RxJava (de Netflix) qui travaillent sur le concept de programmation réactive.
Node.js fonctionne sur la base de boucles d'événements, qui vous fournit tout l'arsenal pour la programmation asynchrone et les bibliothèques de nœuds suivantes comme "cluster" vous aident à tirer le meilleur parti de votre machine multicœur. Et Node.js vous fournit également la fonctionnalité EventEmitter où vous pouvez vous abonner aux événements et agir en conséquence de manière asynchrone.
D'un autre côté, si je comprends bien, RxJS (et la programmation réactive en général) fonctionne sur le principe des flux d'événements, en s'abonnant aux flux d'événements, en transformant les données du flux d'événements de manière asynchrone.
Donc, la question est de savoir ce que signifie l'utilisation de packages Rx dans Node.js. Quelle est la différence entre la boucle d'événements du Node, l'émetteur d'événements et les abonnements aux flux et abonnements du Rx.
Les observables ne sont pas comme les EventEmitters. Ils peuvent agir comme EventEmitters dans certains cas, notamment lorsqu'ils sont multidiffusés à l'aide de sujets RxJS, mais ils n'agissent généralement pas comme EventEmitters.
En bref, un RxJS Subject est comme un EventEmitter, mais un RxJS Observable est une interface plus générique. Les observables sont plus similaires aux fonctions avec zéro argument.
Considérer ce qui suit:
function foo() {
console.log('Hello');
return 42;
}
var x = foo.call(); // same as foo()
console.log(x);
var y = foo.call(); // same as foo()
console.log(y);
Bien sûr, nous nous attendons tous à voir en sortie:
"Hello"
42
"Hello"
42
Vous pouvez écrire le même comportement ci-dessus, mais avec Observables:
var foo = Rx.Observable.create(function (observer) {
console.log('Hello');
observer.next(42);
});
foo.subscribe(function (x) {
console.log(x);
});
foo.subscribe(function (y) {
console.log(y);
});
Et la sortie est la même:
"Hello"
42
"Hello"
42
C'est parce que les fonctions et les observables sont des calculs paresseux. Si vous n'appelez pas la fonction, la console.log('Hello')
ne se produira pas. Toujours avec Observables, si vous n'appelez pas (subscribe
), la console.log('Hello')
ne se produira pas. De plus, "appeler" ou "s'abonner" est une opération indépendante: deux appels de fonction déclenchent deux effets secondaires distincts et deux abonnements observables déclenchent deux effets secondaires distincts. Contrairement aux EventEmitters qui partagent les effets secondaires et ont une exécution désirée indépendamment de l'existence d'abonnés, Observables n'ont aucune exécution partagée et sont paresseux.
Jusqu'à présent, aucune différence entre le comportement d'une fonction et un observable. Cette question sur StackOverflow aurait été mieux formulée comme "RxJS Observables vs fonctions?".
Certaines personnes affirment que les observables sont asynchrones. Ce n'est pas vrai. Si vous entourez un appel de fonction de journaux, comme ceci:
console.log('before');
console.log(foo.call());
console.log('after');
Vous verrez évidemment la sortie:
"before"
"Hello"
42
"after"
Et c'est le même comportement avec Observables:
console.log('before');
foo.subscribe(function (x) {
console.log(x);
});
console.log('after');
Et la sortie:
"before"
"Hello"
42
"after"
Ce qui prouve que l'abonnement de foo
était entièrement synchrone, tout comme une fonction.
Alors, quelle est vraiment la différence entre une observable et une fonction?
Les observables peuvent "retourner" plusieurs valeurs au fil du temps, quelque chose qui ne fonctionne pas. Vous ne pouvez pas faire ça:
function foo() {
console.log('Hello');
return 42;
return 100; // dead code. will never happen
}
Les fonctions ne peuvent renvoyer qu'une seule valeur. Les observables peuvent cependant le faire:
var foo = Rx.Observable.create(function (observer) {
console.log('Hello');
observer.next(42);
observer.next(100); // "return" another value
observer.next(200);
});
console.log('before');
foo.subscribe(function (x) {
console.log(x);
});
console.log('after');
Avec sortie synchrone:
"before"
"Hello"
42
100
200
"after"
Mais vous pouvez également "renvoyer" des valeurs de manière asynchrone:
var foo = Rx.Observable.create(function (observer) {
console.log('Hello');
observer.next(42);
observer.next(100);
observer.next(200);
setTimeout(function () {
observer.next(300);
}, 1000);
});
Avec sortie:
"before"
"Hello"
42
100
200
"after"
300
De conclure,
func.call()
signifie " donnez-moi une valeur immédiatement (de manière synchrone) "obsv.subscribe()
signifie " donnez-moi des valeurs. Peut-être que beaucoup d'entre elles, peut-être de manière synchrone, peut-être asynchrone "C'est ainsi que les observables sont une généralisation des fonctions (qui n'ont pas d'arguments).
Quand un auditeur est-il attaché à Emitter?
Avec les émetteurs d'événements, les auditeurs sont informés à chaque fois qu'un événement les intéresse. Lorsqu'un nouvel écouteur est ajouté après que l'événement s'est produit, il ne sera pas informé de l'événement passé. De plus, le nouvel auditeur ne connaîtra pas les événements qui se sont produits auparavant. Bien sûr, nous pourrions programmer manuellement notre émetteur et notre écouteur pour gérer cette logique personnalisée.
Avec les flux réactifs, l'abonné obtient le flux d'événements qui se sont produits depuis le début. Le moment auquel il souscrit n'est donc pas strict. Maintenant, il peut effectuer diverses opérations sur le flux pour obtenir le sous-flux d'événements qui l'intéresse.
L'avantage en ressort:
Flux d'ordre supérieur:
Un flux d'ordre supérieur est un "flux de flux": un flux dont les valeurs d'événement sont elles-mêmes des flux.
Avec les émetteurs d'événements, une façon de le faire est d'avoir le même écouteur attaché à plusieurs émetteurs d'événements. Cela devient complexe lorsque nous devons corréler l'événement survenu sur différents émetteurs.
Avec des flux réactifs, c'est un jeu d'enfant. Un exemple de mostjs (qui est une bibliothèque de programmation réactive, comme RxJS mais plus performante)
const firstClick = most.fromEvent('click', document).take(1);
const mousemovesAfterFirstClick = firstClick.map(() =>
most.fromEvent('mousemove', document)
.takeUntil(most.of().delay(5000)))
Dans l'exemple ci-dessus, nous corrélons les événements de clic avec les événements de déplacement de la souris. Les modèles de déduction entre les événements deviennent plus faciles à réaliser lorsque les événements sont disponibles sous forme de flux.
Cela dit, avec EventEmitter, nous pourrions accomplir tout cela en concevant nos émetteurs et nos auditeurs. Il a besoin de plus d'ingénierie car il n'est pas prévu en premier lieu pour de tels scénarios. Alors que les flux réactifs le font si couramment, car ils sont destinés à résoudre de tels problèmes.