As of this writing, I've been working on the RxJS project for almost 6 years, I think. When I started out, I really had no idea what I was getting ...
For further actions, you may consider blocking this person and/or reporting abuse
we all have regrets but not enough of us are brave enough to publicly admit them and listen to our detractors. this is one of the best personal reflections I have ever read. the rxjs community is so lucky to have you as it's leader!
3 is the inspiration for a talk and workshop I give on ReactiveX. I had a similar experience when I first found RxJS - I knew something was super cool there, but I couldn't stick a use case on it.
For what it's worth I think this is true of the entire ReactiveX ecosystem. The ReactiveX homepage starts with a marble diagram of debounce. i just completed a nine month Kotlin project that used RxKotlin extensively and we used debounce...once...I think, yet were very effective using Rx tech all over the place. I think it's easy to fall into the trap, after a while, of saying "here's the cool features!" instead of saying what those features are good for.
This is a really nice article, and RxJS is great.
Thank you for taking the time to reflect on RxJS. The truth is, I probably wouldn't be creating a library right now if RxJS didn't exist because RxJS set such a huge precedent for the functional (reactive) programming paradigm. Sometimes you just gotta try new stuff to see what works. I respect that mentality.
This is my biggest gripe with RxJS. If in a separate universe this were not the case, I'm sure I would be an active RxJS advocate right now. I started rubico because I saw the value of the functional programming paradigm but could not find any existing solutions I was happy with. The primary reason I was not satisfied with RxJS was in fact the Observable; if RxJS had been built around Promises instead of Observables, I would have reached for it in a heartbeat. That is really the only difference between our two libraries; RxJS is to Observables as rubico is to Promises.
Why do I oppose Observables so much?
It's because they, by themselves, make life hard. You can't just
assert.deepEqual
the value of an Observable. Instead you have to switch your mind to virtual time and use mock Observables. Promises have way better interop with the regular types; it's a one-to-one mapping. Promise of string? Cool. Promise of Array? Also cool. Observables are inherently many-to-one. This is why it's pretty common in practice to use RxJS operatorsmergeMap
orconcatMap
; you have to deal with Observables of Observables. flatMap is a powerful concept, but in the case of Observable, it's a necessity.The Observable is the dual of the Iterable, but that by itself should not give it any more of a case to make it into the spec. Shouldn't we as library authors create things that make developers' lives easy? All I see is Observables making life hard, and I can't get behind that.
You can't just
assert.deepEqual
onObservable
not because it is many-to-one, but because it is lazy, whilePromise
is strict. When you compare twoPromise
s you are comparing their state and result cache, whereas when you compare twoObservable
s you are comparing all their callbacks, which makes less sense.Laziness is what gives the cancellation control. Without it you need the cancellation token workaround. C# uses the same workaround too. Between the guarantees and strictness, we each pick our trade-off.
The dual of an existing tool is almost always a equally powerful tool, as borne out by mathematics many times over. I agree that Observable needs more polish to improve developer experience. But I am more disappointed by a spec which chooses to break duality without informed ground. It was a chance to future-proof the Promise API and the spec threw it.
FlatMap (monadic bind) is a necessity for programming at all. It is a mathematical model for sequential context. Historically programs focus on one such context: the single-thread procedure. The source code line sequence represents that. In Haskell, any such context can use the do notation and avoid calling flatMap directly. In JS, async/await does that for Promise (of which
then
is flatMap anyway), and there is no reason it can't do so for Observable apart from that Promise is in the spec but Observable is not. Arguing that flatMap is necessary for Observable but not for Promise is misunderstanding Promise. Arguing Promise is more worthy to be in the spec for that reason is circular logic.Of course you wouldn't just
assert.deepEqual
two Observables or two Promises, it's the step between getting somethingassert.deepEqual
-able from Observables that is so much harder than from Promises. With Observables, you have to mock them in virtual time or fully consume them to some arbitrary type (losing precision) to test their behavior. With Promises, there is no time parameter; it's a one-to-one mapping from the Promise to its associated value.The Observable's implicit higher order as a type is what gives both laziness and cancellation control. Observables live at a level of abstraction that makes cancellation convenient. I would know because my library lives at a similar level of abstraction. It even implements Promise cancellation on async iterable transformations. The proposal for promise cancellation was withdrawn because
Maybe they were talking about cancel tokens here? Either way, proponents for Observables rejoiced when this proposal was withdrawn, yet happily continue to cancel Observables. Cancellation in the observable proposal is pretty up in the air, by the way, and is far from standardized. I don't understand why you guys keep waving it around.
Are you talking about Observables here? And implying it should thusly make it into the spec? Does that mean all things dual by mathematics should be standardized? Also, could you clarify what you mean when you say you are "more disappointed by a spec which chooses to break duality without informed ground?" Right now I'm interpreting it as you think Observables should make it into the spec by mathematical duality.
It's really not. Have you tried working with built-in types? I understand if you're working with effectful types all the time that you might see the world this way, but in practice I hardly need flatMap at all.
All you're telling me here is you haven't grokked Promises.
.then
has its own semantics and is absolutely not a flatMap for Promises. This example with a.flatMap
polyfill on Promise illustrates their difference. The following two expressions are equivalent.Arguing flatMap is necessary for Promise is misunderstanding Promise.
Promise is already in the spec.
I am no proponent of Observable, nor have I interacted with them. I feel the same as you about its verbosity, and I appreciate your rubico library. In the end everyone mostly have moved on to async functions and async iterators, which are at where I wished the level of abstraction Promise spec was. As references, Rust Future is conceptually
Future { poll :: () -> Pending | val }
, and Haskell Async is conceptuallyAsync { _asyncWait :: () -> SomeException | a }
, both of which have their own async/await. Promise is essentially thePending | val
part..then
has its own semantics including aflatMap
for Promises. The minor mismatches are just a fewjoin
s or apure
away. The important part is the concept of sequence.Who are not? I guess we differ about what is effectful. State and error handling are central to programming and are effectful in my vernacular. You just don't need to explicitly write out the flatMap because the language takes care of it.
Actually I see your point here. I'm becoming more aware that basically everything is an effect, especially when it comes to designing languages. I see how the language can take care of stuff like this; in fact rubico provides a language that takes care of Promises in this way.
This thought crossed my mind when I was writing out that example. Basically Promise
.then
does a whole bunch of flatMaps arbitrarily, if required. I'll chalk it up to a difference of what is effectful.As I said via Twitter, owning your mistakes and making them public takes high-levels of both courage and maturity. Mad props to you for doing that and thank you for elaborating on your Tweet in this write-up ππΎ .
The best way to follow through on the first sentence of "The Road Forward" is to write an open-source book. Instead of targeting mid-level and senior developers, assume your readers are juniors that know nothing about
Observable
.Before reading this article, I heard of
Observable
, but I had no clue what it was. Since RxJS is so popular, I would pay $30 for a book that teaches it from scratch. I'm imagining YDKJS, but for RxJS andObservable
. That said, if you want to spread the word to as many people as possible, an open-source book is a better option.Thanks again for the write-up. I wish you and the RxJS team the best going forward.
Not sure about the rules of self-promotion here, but I wrote a book on learning Observables/RxJS with practical demos: pragprog.com/titles/rkrxjs/ Happy to answer any questions you might have (though Ben's the true expert)
Thanks, Randall!
Really a great read! I've been a user of RxJS for a long time now and consider myself pretty comfortable with it. However, even though that's the case, it really is helpful to hear again how important it is to mentally separate the Observable primitive type from the operators.
I find it interesting, as you suggest, to separate the primitive from the operators. However, I don't see Rxjs size, at least in its latest versions, as being a problem. As operators can be imported separately, there is minimal impact bundle-wise. The impact seems to be more of a cognitive nature, with some folks maybe assuming that because there are 100 operators, it must be complicated, probably twice as complicated as if there were 50? This is like saying that npm is unusable because there are million of packages there.
I think at core there is both a teaching and learning problem. Beating up yourself, and reducing the number of operators to make the library artificially more palatable will not automatically solve those problems.
Teaching problem:
Learning problem:
Last thing, the readability issues that you mention comes from the fact that behaviors and events are not well introduced, yet they are a key part of understanding how to use Rxjs. That is more important and very linked to the hot/cold dichotomy (another confusing metaphor IMO). If you had separate types. you could document some operators as accepting only behaviors, like withLatestFrom (is is sample/audit now?), and save some programmers the debugging.
Alright there are so many more things (structure readable code and so on), but this is getting too long. In any case, while I appreciate the humility and capacity of self-reflection that you show , I would not want you to make the wrong diagnostic here. Observables are an abstraction. Some programmers will always balk at abstraction that they cannot get in 5mn. For the rest of them, I mostly see documentation as an issue. Rxjs design is quite good. But if you could make a better tracing and debugging story, you may not even need to do too much documentation-wise. You could just let people play with it and build their own intuition.
In summary, keep up the good work and don't get demoralized by the critics.
Thanks for that article!
I found observables intriguing, and Cycle.js got me to look into libs like most and xstream.
I wish JS had observables instead of promises. But in the end, I guess promises were easier to grasp π
thanks for your work on rxjs. I have just begun working with rxjs through angular. I think is such a powerful primitive, but it has links into functional programming which is another thing people (including me) seem to strugle with. That doesn't say that functional programming is bad or Observables are bad. In fact, I would love to master both. I believe I would be able to build much better software if I would master these concepts.
Again, thanks for your awesome work!
I'm quiet new to RxJS, but I feel this is my biggest issue with it.
I especially think pushing RxJS into Angular was a big mistake, as it ends up everywhere and it seems to me that people don't really try to understand it, and blindly apply some operators until it works.
Now that isn't really RxJSs' fault, but it would help if the library would provide a little less foodguns, and a bit more guidance / best practices.
Takes a lot of humility and level headedness to write something of this caliber! Iβve been a fan of the project since hearing the talk on how you implemented it at Netflix years ago, but I will also be the first to admit it is a project that I misunderstood at first. Donβt let those Tweets get to you! Twitter is no place for constructive criticism.
For me, RxJS had the biggest learning curve.
I didn't understand observables, I didn't know how RxJS tied in, and the code examples I did find, looking back they're terrible code.
But RxJS and observables have also had the biggest payoff once I understood how they work, their use, their seperation, and the use cases that make performance and handling of data so easy. I don't want to go back to not using observables and I hope they get their own native implementation soon!
Thanks for sharing. It took me a while to wrap my head around the concept of RxJS as a whole as there was a bit of a learning curve. But after using it consistently, I was able to understand what it was actually doing and how to better implement it. The community appreciates your work.
Really good read. I've used rxjs and Observables before, but brushing up on Observables tonight. This post has me intrigued about Observables!
Maybe adding a quick example (in the post) of Observables usage could help folks better understand how it's used vs what they'd normally do.
I think this is gonna be a really good direction for RxJS. It's such a powerful library and I was really looking forward to use it when migrated an angularjs app to Angular8. With no internal knowledge of RxJS in the team, at some point we got stuck, so I think more documentation and a smaller API will definitely help. And reactivex.io/learnrx/ is probably still the best functional tutorial out there (not sure who maintains it)
I love this post (1 year later). RxJS is amazingly powerful and terrible. The complexity that can get introduced with pipes, the callback hell (nested switchMaps that need catchError) that that community spent so long getting rid of all of a sudden became ok again in RxJS. Nobody can write good RxJS code unless they've been doing it for a year. The amount of time seniors have spent walking juniors through an RxJS pipe (and then the amount of time juniors have spent trying to make a change in that pipe and understand what they've done) is massive.
But, as you said, Observable itself is golden.
Go for it - split out the Observable into a separate package and make RxJS 7 depend on it!
Thank you for writing this.
It must be hard to admit things could have been better. But this is the right way to move forward. I wish other community leaders would follow you, and talk openly about their community problems and how they plan to fix them.
Thanks. This sums up a lot of my problems when working on Angular app.
I often felt that what I am writing is not how it should be, but finding examples of what are good practises was really hard. When things clicked I usually was really happy with how powerful and expressive RxJS is, although as you mentioned, size of the API was daunting and knowing what and when to use was a herculean task.
Knockoutjs is a great library that extracts the observable without the operators. I find it also lacks the ceremony of Angular which makes it easier to understand and Steve Sanderson has created a Visual Studio template that uses typescript and has a hot reload.
I still find that RXJS (the combination of observances and operators) can do things that as of yet the detractors can not provide alternative solutions to. This means Iβve had a project that I started without RXJS fail but then solve it by using RXJS.
Precisely how I feel as a user. I've said RxJS is a baby in a tub of dirty bathwater. I just don't know where the baby ends and the bathwater begins. Well, thanks for clearing that up.
Thanks for the clarification about Observables and RxJS, Ben. As someone who is interested in learning RxJS for quite some time, this has been a huge help.
Is it worth mentioning
zen-observable
here? It's pretty bare-bones and Apollo uses it, so it's the observable API I became familiar with and started using in my own projects.Honestly the purpose of Rxjs is less clear to me than ever, but still a very interesting read. Thanks
Thanks for your insights!
I've noticed the libs that have better documentation & real world examples people can copy/paste do the best.
Good and honest article ... I think the two things that you mention (large API surface and opaque use case) are exactly what kept me from ever getting "into" RxJS.
what Shawn said!!!