New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Do Observables make sense for http? #5876
Comments
I have little experience with Angular 2.0, but that was the same concern I had when trying the new angular. For most of my Http calls a promise would be the best way to go. |
If you guys make me choose between a car that can transtorm into Optimus Prime and a regular car just to go from A to B, I would totally go with the first one :D |
Observables are way more powerful. We can easily create code that "retries" http calls, in case the first one failed an similar things. You can't do this with promises at all. In addition, another thing you can't do with promises is cancelling calls. Just think of the use case where you build a type ahead component and with each keystroke you have to throttle 300 ms before you send the actual Xhr. This is very easy with observables, but not only that, with observables we can make sure that we always execute the return call in the right order + we can cancel calls. Promises are just not suited for that.
You can turn any observable into a promise by calling Does that make sense @thelgevold ? |
@PascalPrecht I see the advantages in the cases you describe but I guess time will tell if the benefits will warrant the new direction. I could be wrong about this, but I am not 100% convinced that the use cases you describe have been huge pain points for developers in the past. Generally I find most http needs to be super simple and typically amounts to fetching some simple json without the need to either cancel or retry. Not trying to fight the change, just not 100% convinced yet that it's necessary as the core implementation :-). Especially now that the api is a bit cumbersome with piecemeal includes. I don't agree with the statement that promises don't allow you to guarantee correct order of execution though. |
That's right, you can emulate the actual order by chaining them. My actual point was that e.g. you could say you fire 10 XHR calls, and you're only interested in the very last one, while the other 9 are still pending, which is a very common case when you build search inputs etc. With Observables all other requests can be cancelled and don't eat any more resources. And again, you can always fallback to promises if you want. No one forces you to use observables. |
I agree, using promises is definitely an alternative. I will let it go for now :-), but still not convinced that Observables have enough real use cases in the context of http to warrant the switch. The ability to cancel and cherry pick requests is nice, but I also question the underlying architecture of a system that constantly gets into a state where requests have to be cancelled, ignored or retried. The search box might be an exception, but request control and predictability is also part of a good design. Performance is still impacted if you fire 9 out of 10 requests needlessly. They still hit the server even though you have the ability to ignore the results. |
IMO, learning the basic syntax of making HTTP requests with Observable is pretty easy. Even if you're not interested in cancellable requests or retries, using the same approach to processing data (regardless if they come from HTTP, WebSocket, or an event) is beneficial. |
@yfain Yeah, the syntax is pretty straight forward. I've played around with it and found a solution to most of my needs (plain call, chaining etc) in my Angular 2.0 sample project. I think though that $http worked well in Angular 1.x and was one of the concepts that could have been allowed to migrate over as a default implementation. After all it's pretty much based around standard promises. There has also been a lot of back and forth about how to implement the Rx integration in Angular 2.0, which I assume contributes to delays in getting Angular 2.0 to market. I suspect things are still in flux when it comes to this. |
I should preface with this that 8 months ago I was literally making the same argument @thelgevold is currently, and now I'm probably the most vocal advocate for RxJS and Observables on the team :) First - the simple promise based API you want already exists, native to the (modern) browser - it's the Fetch API, and it works just fine with Angular2. It's not without its own set of issues - that epic thread is mostly due to the fact that Promises, as they exist today, don't have a semantic way of describing cancellation. Is a cancelled Promise a success or an error? Neither? Something else? To be decided... One of our big focuses in angular2 is mobile - not being able to cancel requests we no longer care about (as @PascalPrecht mentions) is a dealbreaker. Rather than trying to shoehorn that into Promises, Observables provide this functionality out of the box. Beyond cancellation - Promises are not re-usable. If a promise fails (again, think spotty network connection on a mobile device) you have to completely re-initialize the whole request. Observables: http.get(url)
.retry(3)
.subscribe(...); or retry 3 times with incremental backoff... http.get(url)
.retryWhen(attempts => {
return Observable
.range(1, 3).zip(attempts, (i) => i)
.flatMap(i => Observable.timer(i * 1000));
}); That only touches the surface of the sort of stuff that's trivial to do with Observables. I'd agree that if you look at http in an isolated context, promises might seem to make more sense (excluding the above) - but I'd argue that in the context of a reactive application, you're not dealing with a single request - you're dealing with a stream of actions that get transformed into requests... this.results = someFormInput.valueChanges //observable of input changes
.debounceTime(150) //debounce for 150ms
.switchMap(query => http.get(`http://api.com?q=${query}`)) //make request
.map(res => res.json()); //map to json Again, only touching the surface of what we can do here. For what it's worth, RxJS beat Angular2 to beta (by one day!) and @Blesh and co have been great partners for us - they are certainly not contributing to any delays to angular2. I'm a huge fan of Promises, and it's easy to turn an Observable into a Promise - a Promise, after all, is effectively a Observable of a single value. The opposite is very difficult to do. My 2c. Http isn't yet complete, and as its a standalone module, the option for what you use is completely up to you, with no penalty for opt'ing out. That said - stay tuned. Lots of interesting stuff to come on top of this. See angular's tactical prototype, which again is built on top of Observables : https://github.com/angular/tactical Closing this as this decision is pretty much made, but absolutely feel free to continue discussion or ask questions :) |
In a single page application, promises are a sub-optimal choice for HTTP, because promises cannot be cancelled.
Promises have a few problems:
All-in-all when I see heavy promise use in code, I suspect there could be bugs or unwanted behavior. |
@robwormald @Blesh Cancellation: Side Effects: Errors:
That said. I am very impressed with the RxJS implementation in general, so please don't see this as criticism of your library. It's just my view that it's not as clear that it's the best choice as the default http implementation. However, I see great benefits for people with real streaming needs though. P.S. |
You're welcome to use whatever you like. There is nothing stopping anyone from using any number of promise-based AJAX implementations. As long as you're aware of the inadequacies, and you're willing to work around them, you'll be fine. However, it's my advice, if you're going to allocate an object for async abstraction, to use an observable for the benefits that have been mentioned here and many times elsewhere. Promises are "okay"... You just need to know where they might get you. |
Ok thanks for all the points that have been made regarding this. I will definitely keep exploring Observables and dive into some of the more advanced use cases. Currently I think I have all the "normal" Observable use cases covered in my blog: http://www.syntaxsuccess.com/viewarticle/angular-2.0-and-http Anyway, I will take a deep dive and report back later to let you know if I have become a believer :-) |
I just posted about this over on #2684, but having read this thread I'll try to answer my own question... Am I right in saying the reason Angular doesn't simply return Promises and let people turn them into Observables if they feel like using RxJS is that cancelling the resulting Observable wouldn't cancel the underlying HTTP request? But by returning Observables natively, cancellation does cancel the underlying HTTP request? |
That's correct. |
Related reading, fwiw https://groups.google.com/forum/#!topic/angular-data-dev/AdULPprCkbI%5B1-25%5D |
@benjamingr oh dear, I'd hoped we'd seen the last of that thread... |
We have, it's an historical reference at this point demonstrating the way decisions are bring made in that project :) Clearly the users' best interests come first and not how one can push what spec to the TC. I use Aurelia, and I'm not really an Angular 2 user (at least yet) - so I'm all for pushing observables (which I use) to the spec at the expense of Angular users now. I just don't want to see the same happen to Aurelia :) |
I think as more people move to Angular 2 this question is going to keep re-occuring. The benefits of using observables for single HTTP requests is just not immediately obvious to most people when they first encounter it. Even the RxJS README.md says "Promises are good for solving asynchronous operations such as querying a service with an XMLHttpRequest, where the expected behavior is one value and then completion". So that doesn't help explain things! It's probably worth distilling all the reasoning in these various threads into a helpful piece of documentation that can be referred to in future when people ask the question. Showing how to implement a more advanced retry function based on the failure reason would also be quite enlightening, and something most people will need. Seeing so many examples using |
Retry and cancellation are examples that can be translated to promises trivially. Observables shine in more complicated examples like autocomplete, infinite scrolling, etc. If you want to show where they shine you need to show use cases promises are not capable of. |
@benjamingr please do show me how to trivially cancel a promise :) |
I think that google groups thread contains a lot about cancelling promises, no need to start that again here! :) |
@fxck here http://bluebirdjs.com/docs/api/cancellation.html#what-about-promises-that-have-multiple-consumers although the Internet is full of examples and Angular 1 had (quirky, but still) cancellation so there's that. |
I think the Google thread was an interesting read. I am still planning on using both Promises and Observables, but I think better documentation is key here. Ideally we should have "neutral" documentation describing the pros and cons to both approaches. Currently a search for toPromise() does not yield a single result in the API preview :-) Perhaps the documentation should describe when you actually might have a clear benefit from Observables, but right now it's very biased towards just Observables. I also wouldn't mind a more explicit "opt-in" approach to either one based on application needs and developer experience. My key points has always been that I don't necessarily agree that the ability to cancel the request necessarily outweighs the burden of re-educating developers who might not even utilize the cancel feature. Instead of defaulting to Observables we could make it more explicit, so that people can educate themselves on when one makes sense over the other.
Better documentation describing the pros and cons of both might make it easier to pick the one that is best suitable for your own specific needs/team skills. |
@thelgevold no no, we should Observables as much as possible so they have adoption. We get a smoother sail for the proposal through the TC stages and into the language. |
@benjamingr: To be fair, cancellable promises aren't really the "Promises" anyone is talking about in this thread. This is about standard promises vs the observable type that's been in languages for years. Cancellation completely breaks the one good thing about promises by making them mutable. But that's a debate for another day. |
@Blesh cancellation doesn't make promises very mutable - I agree that it's not as pretty. Cancellation for promises signals "disinterest" and not "abort" so all a consumer can do is signal they're not interested in the return value (and not change it, handlers will not fire - with the exception of This is less about promises IMO and more about unicast vs multicast semantics. Being always multicast means promises have to refcount in order to have sound cancellation meaning abort semantics are not an option. Promises also do not "swallow errors" - not even native ones. Promises will fire an event when an error happened but was not handled (window.addEventHandler("unhandledrejection" and process.on("unhandledRejection" in node). I'd just like to emphasize I'm for Observables in Angular's http now because it'll help push them in the spec and battle test them by many users. While frameworks have a 5 year lifespan Observables will stay in the language for good and I get to work with a good default functional abstraction for handling events (and not DOM event emitters :S). What I think we need for ng2 is to sell Observables to the users with motivating examples in the spec - these examples should not be "retry" or "cancel" but things promises can't do elegantly like autocomplete, working with websockets, progression and anything with "many" semantics which is painful before observables and trivial with them. |
@robwormald I think what really bothers me abut this is that you end up with a global "click" subscription, so you have to store some of the state in the DOM. I find that this solution is too closed off for input since it's a global registration.
I can't picture a perfect API here, but I would love a way that hides the creation of the initial Observable without any hard coded DOM references. Would it be an idea to introduce an Observable group? You can then decide what type of events you want to associate with the group.
|
@thelgevold if you look in #4062 there's some ideas on what the implementation might look like. Let's keep discussion in there as that's an open issue. @benjamingr your input would be most welcome there as well - i'm using a similar redux-like-with-rx pattern in angular2 and it's the missing link for me. |
Quick side note: in both @benjamingr's fiddle and @thelgevold's code example I can see source.flatMap(x => doSomething(x)).map(r => selectResult(r)) and source.switchMap(x => doSomething(x)).map(r => selectResult(r)) ... (not condensed on purpose so you can see arguments)... It should be: source.flatMap(x => doSomething(x), (x, r) => selectResult(r)) and source.switchMap(x => doSomething(x), (x, r) => selectResult(r)) It's easily the most common perf gaff I see in Rx. (sometimes it's intentional, I suppose, but still) Just wanted to shout that out. I do it too, sometimes. Or at least I used to do this a lot. I'm sure it's still in some of my older code. |
Since this thread turned out to be general discussion of observables (don't get me wrong, it's a good thread and people in the future might refer to it): @Blesh would you mind elaborating a little on why it's a perf gaff? I assumed it'd be "about as fast" and never really considered it. By the way - In all honesty the vast majority of our RxJS code is:
I'm sure I have a lot less Rx experience than you netflix folk who use it at much larger scale and on more projects. We do more typical (and probably less interesting) things with Rx. I'd be interested to hear where you ran into performance problems. |
Then why do you need Angular 2? What does it actually give you on top of just using Rx with a virtual dom with components? |
What can I say? I like my templates...
|
Simply put, it incurs more subscriptions. Each operator requires a subscription to the source observable under the hood. That's another observable, another subscription, another observer, extra function calls down your chain, etc. Overhead. However, if you use the resultSelector argument in Generally, it doesn't make much difference for the average app. But when perf matters (say at scale, or in a busy, real-time UI), you want to limit subscription/operator depth if possible. |
I think I might have a(nother) mental gap in my understanding of RxJS then. Why do you actually have to create all that when I'd expect Rx to optimize this under the hood and not really create a new subscription/observable/observer until it has to (that is, I'd expect it to "forward" the original subscription and then copy on write when a second subscriber is attached). The most common case at least for things like http calls (but definitely not the only one) is to have a single subscriber. I guess I'd expect RxJS to optimize for that (like bluebird does). |
I think one of the more compelling things about Angular 2 will be build-time tooling around statically analyzing templates. You could, in theory, analyze all of your templates for your components and do things like generate graph queries that those components needed to use, and/or compile other metadata to make things like Falcor more efficient. There was a talk on some of these features I think at AngularConnect, or perhaps it came up while chatting with @IgorMinar, I'm not sure. That wouldn't be as possible in a VDOM scenario, where the view could be built in anyway JavaScript can manipulate an object. (It's possible, but clunkier, and unlikely to work in every scenario). Personally, I love using VDOM, but it does violate the Rule of Least Power a bit. That said, VDOM fits 99% of my needs for projects I currently work on at Netflix. |
I agree. We're working on that. But currently no version of RxJS will do this. Likewise, there could be similar optimizations for operators that are completely synchronous like |
@alxhub covered this in his talk at AngularConnect - https://youtu.be/bVI5gGTEQ_U?t=905 - Template Transforms. A falcor example: <div #user="graph.users[123]">
{{user.firstName}} {{user.lastName}}
</div> that desugars into the longhand falcor syntax <div #user="graph.deref(['users', 123]) | async">
{{user.get(['firstName']) | async}}
{{user.get(['lastName']) | async}}
</div> |
I moved the optimization discussion to the Rx repo at ReactiveX/rxjs#1121 (I hope that's ok). |
JSX templates in React are just JavaScript, it's trivial to statically analyze and transform (it's just a regular babel transform, no additional parsing). Much easier than any tooling for a real dedicated template format - errors are detected by all existing JS tooling that understands the transform. The type system (flow) isn't too shabby either. It's not like Angular templates aren't Turing complete after all - but they can't leverage all the existing static analysis tools JS has. Since you I'll watch the AngularConnect talk. Maybe it'll make be a believer :) |
All the examples that are defending the use of ¿Why don't you show an example that represents the common use case using For example if you do a standard request: this.http.get('./country-info/' + country + '.json')
.map((res: Response) => res.json())
.subscribe(res => this.capitol = res.capitol); When I read this code I understand the following: for each result that the How can you defend this type of code: ['{ "capitol": "Washington DC"}']
.map((body) => JSON.parse(body))
.forEach((result) => this.capitol = result.capitol); Instead of: const body = '{ "capitol": "Washington DC"}';
const result = JSON.parse(body)
this.capitol = result.capitol I can't get my head around it. |
That's not how it works. Doing subscribe is in this case literally the same as using promises with then. You won't ever have to do that foreach yourself, nor does it work like that inside, it's not an array, it's a stream. |
You are forcing the use of a stream of events which can handle unlimited events when there is only one event to handle. |
Yes, and why do you, as the consumer, care? Performance wise, it's the same, apart from using |
this.http.get('./country-info/' + country + '.json')
.map((res: Response) => res.json())
.subscribe(res => this.capitol = res.capitol); vs this.http.get('./country-info/' + country + '.json')
.then((res: Response) => res.json())
.then(res => this.capitol = res.capitol); ... only the second one can't be cancelled and will trap your errors. In a single page app, cancellation is important because when you change routes you don't need to finish loading data from the previous route. It's wasted computational cycles, garbage collection, memory allocation, etc, etc. Promises just aren't the right type for HTTP in a single page app. and then when you want any sort of resiliency against your network going out, or you want to retry every second on failure you can just add one line with RxJS... Total control. Not so with promises. this.http.get('./country-info/' + country + '.json')
.retryWhen(error$ => error$.switchMap(err => navigator.onLine ? timer(1000) : fromEvent(document, 'online')) // ONE LINE ADDED
.map((res: Response) => res.json())
.subscribe(res => this.capitol = res.capitol); In any case @fxck is 100% correct that you can use Promises and not Angular's Much ado about nothing, really. I've had this discussion so many times I can't count them, so I don't want to engage much more, but I just wanted to point out a few basic points on this. The only argument in favor of promises I think actually has some merit is that they're multicast by default... which is something you have to opt-into for observables. |
I am using observables almost exclusively now for Angular 2 http, but in most cases, promises would have worked equally well. Question to the group: With I understand that async/await does nothing to bridge the functionality gap between promises and observables, but maybe the new syntax will be appealing to some. In my view async/await aims to make async code flow feel more synchronous, which might be easier for junior developers to grasp. Again, the mentioned arguments in favor of observables will obviously still stand even with async/await. |
Async await currently lacks cancellation. When it does finally support cancellation via tokens, observables will also support tokens seemlessly. Other things: promises are always asynchronous. Observables can be synchronous, this makes them easier to deal with and more performant with regards to SSR. Additionally WRT SSR, observables don't trap errors completely, which means that node services can opt into the "panic" behavior that is desirable for better debugging of node processes (via core dump analysis) This is a really long thought out decision. There have been volumes of blogs and talks about the benefits of observables over promises in common web app scenarios Also... Again... There's nothing stopping you from using async await or promises wherever you want. |
@Blesh you've got to stop saying that, there are plenty of real reasons to use observables in Angular 2 - promise cancellation is as-specced as observables and promises don't trap errors. Retrying is again, trivial. Async await cancellation is also, as far ahead as observables in the spec. I disagree with the SSR point - but meh. The argument for observables is having a unified API and to push them in the spec, that should be enough. |
No I don't. ¯_(ツ)_/¯ |
I totally agree with @thelgevold, I've spent more than 5 hours trying to convince myself with Observables instead of Promises, and found that it's adding too much complexity for api responses. What developers really need is the flexibility to manage the returned items inside the response and not the response itself. for instance imagine an api call that returns list of cars Even more, I think using Lodash or something similar (on the data level and not stream level) instead of RxJs for such situations will be more useful.
|
It's true you really don't need any arithmetic operators other than + and that if you resort to using + most of the time you might feel like those other operators are just complicating matters and forcing you to learn new approaches. And so it is with this discussion about promises. |
One big difference between Promises and Observables, is that Observables are functions! I understand the power of Observables and the lacks of Promises. Using Promises, I could do : Using Observables, I have to chain a subscription absolutely. Because Observables are just functions. If I do not, the http call is never executed : |
vs
I totally don't see the point of |
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
I don't want this to sound too critical :-).
I just have a few questions/concerns about Observables in the context of Angular 2.0 http.
Since I started using http in Angular 2.0 I have always wondered why a decision was made to go with Observables over promises.
Don't get me wrong, I think observables (with or without RxJs) is cool and I've enjoyed learning about it, but when it comes to plain http calls, I am struggling to see a clear advantage over regular promises.
One of the arguments I've seen is that promises are only good for one time calls, but a stream can be left open indefinitely. My view though is that "one time" http calls probably make up the 90% use case for http. I can definitely see an application of the open ended streams, but in my experience I see it more as a one off case in most business applications. Basically now, I find myself using Observables in a way that just mimics promises.
I see other applications of Observables though. Some examples I've seen include streams of events (e.g. click event patterns etc). I think it definitely has merit, but it seems to me that we are trying to "shoehorn" the 90% use case into the 10% use case with this.
I am not against incorporating alternative technologies, but I question if this pivot to Observables is worth the reeducation of developers when it comes to the http calls. I wonder if it would make sense to offer a lean promise based http implementation to the masses and encourage people with real streaming needs to use RxJs directly instead? I think that will allow us to benefit from the best of both worlds and better enable us to pick the best tool for the job.
Just to be clear: My questions/concerns about this predates the new approach of having to bring in RxJS dependencies piecemeal :-).
-Tor
The text was updated successfully, but these errors were encountered: