How does Observables (Rx.js) compare to ES2015 generators?
Asked Answered
W

3

48

As far as I understand, following are the techniques to solve asynchronous programming workflows:

  1. Callbacks (CSP)
  2. Promises

Newer approaches:

  1. Rx.js Observables (or mostjs, bacon.js, xstream etc)
  2. ES6 generators
  3. Async/Await

We are now moving away from callbacks & promises to these newer approaches. What I understand currently is - Async/Await is more like a cleaner abstraction on top of ES2015 generators.

What I am not able to understand is the conceptual difference between Observables and Generators. I have used both extensively and have no trouble in using them.

What confuses me is the use case for Observables and Generators. I have come to conclusion that, in the end, they are addressing the same problem - asynchronicity. Only potential difference I see is generators inherently provide imperative semantics to code while Observables using Rxjs seem to provide reactive paradigm. But is that it?

Should that be the criteria to choose between Observable and Generator? What are the pros and cons.

Am I missing the big picture?

Also with Observable eventually making into future Ecmascript, are Promises (with cancelable-token)/Observable/Generators going to compete with each other?

Wordsmith answered 27/12, 2016 at 16:52 Comment(5)
A couple notes: Async/Await builds on Promises, not Generators. Also, the cancelable Promise proposal was recently withdrawn.Marcel
Like apples to oranges. They are different patterns. Generators have nothing to do with asynchronicity. If you've used them, you probably should know that. Generators offer a pattern that is convenient for async control flow (co or TS async/await implementation for ES6 target), but that's all.Overly
@estus, Yep. Generator is not really async solution. Generator is more like a factory of iterators. However, as you mentioned, eventually, it is solving async problem with the addition of Q or Co control flow.Wordsmith
Hi Harshal, see my updated answer for some very relevant info to your question. André Staltz just wrote about how closely related Observables and Generators are.Fester
Async/Await is just a syntactic sugar for PromiseIntrepid
F
35

Observables push changes, and hence the observable, not the function reacting to it, is in control. Generators on the other hand require you to pull values out of them. So the function that will react to the new value determines when it is ready for a new value.

I had trouble with backpressure using observables, but with generators, you can let values out as slowly as you want.

Edit: the last question. Promises are just observables that only emit once, so I don't think they will compete with each other. I think the real battle will be async/await vs observables, and async/await has a head start, and is already in C# (and now Node.js). But observables have that sweet FRP feel, and functional programing is super cool, so I think they will both end up with a good chunk of mindshare.

Edit2: André Staltz, author of Cycle.js and xstream, and contributor to Rx.js, wrote a great article on how generators and Observables relate (on 2018-01-31). In particular, he shows how they both inherit from a common base class.

And now a consumer can be a Listener (“observer”) or a Puller, it’s up to the consumer whether it will pull the producer or not. And the producer can be a Listenable (“observable”) or a Pullable (“iterable”), it’s up to the producer whether it sends data proactively or only on demand. As you can see, both consumer and producer are simple functions of the same type:

(num, payload) => void

So any operator that we build will work for both reactive programming or iterable programming, simply because the line between those two modes gets blurred and it’s not anymore about observables versus iterables, it’s just about transformations of data between producer and consumer.

I recommend reading it [link]. The article introduces "Callbags", a spec for callbacks used for reactive and iterable programming. He implements that spec to make a tiny library for both iterable and reactive programming. To entice you to read the article and check out the library, here are some examples from the 7kb lib based on the spec he introduces:

Reactive programming example

Pick the first 5 odd numbers from a clock that ticks every second, then start observing them:

const {forEach, interval, map, filter, take, pipe} = require('callbag-basics');

pipe(
  interval(1000),
  map(x => x + 1),
  filter(x => x % 2),
  take(5),
  forEach(x => console.log(x))
);

// 1
// 3
// 5
// 7
// 9

Iterable programming example

From a range of numbers, pick 5 of them and divide them by 4, then start pulling those one by one:

const {forEach, fromIter, take, map, pipe} = require('callbag-basics');

function* range(from, to) {
  let i = from;
  while (i <= to) {
    yield i;
    i++;
  }
}

pipe(
  fromIter(range(40, 99)), // 40, 41, 42, 43, 44, 45, 46, ...
  take(5), // 40, 41, 42, 43, 44
  map(x => x / 4), // 10, 10.25, 10.5, 10.75, 11
  forEach(x => console.log(x))
);

// 10
// 10.25
// 10.5
// 10.75
// 11
Fester answered 25/7, 2017 at 4:41 Comment(1)
You can also use generators as observersPlugboard
M
5

I understand the question was posted in 2016 where generator were still used synchronously.

However, I think a more interesting (suitable) question today is the difference between async generator and reactive observable or reactive programming in general. Please allow me to digress.

In the following, I'll use:

  • A to represent async/await, promise etc.
  • B to represent async generator
  • C to represent reactive observable.

First of all, all of them are designed to deal to async data. It's the detail that matters.

If we have a finite and small async data source, such a single HTTP request, a few KBs of data on disk. Then A wins, simple and clear.

When the data source became infinite (e.g. user clickings) or too big to hold in memory, then A won't work well because we will run into accidental complexity very quickly (dealing with backpressure to start with). B and C both shines in general, as they both provide backpressure facilities in one way or another.

Then, when the data source starts to requiring buffering, de-duping, debounce, merging/interleaving with other data source etc, C wins as many library will provide generic operators to help dealing with common stream issue. A example use case, is stop user from spamming comments in short window (e.g. 5s).

Does it mean C is better than B in general?

In my opinion, no. Despite C being more versatile, B is simpler, more flexible, and integrate with the language a bit better (e.g. for await ... of). And it does not require you to almost change your entire programing paradigm as per C would require.

Mazza answered 8/8, 2021 at 11:55 Comment(0)
T
4

You can consider the rxjs observables as asynchronous generators i.e. generators yielding promises. Just because the content is not guaranteed to be ready at the time we call .next (in opposition to regular generators)

  • The subscriber will permanently consume into an infinite loop the generator’s content (calling .next)
  • Wait on the returned promise to be resolved (or revoked) to do what you want
  • The observable (made with an async generator) will complete on the generators return.

More reading

asynchronous Iterators proposal

Toxicosis answered 4/12, 2017 at 21:59 Comment(1)
Adding that still async generators are pulled and observables push data.Your

© 2022 - 2024 — McMap. All rights reserved.