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

Closed
thelgevold opened this Issue Dec 14, 2015 · 70 comments

Comments

Projects
None yet
@thelgevold
Contributor

thelgevold commented Dec 14, 2015

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

@adaojunior

This comment has been minimized.

Show comment
Hide comment
@adaojunior

adaojunior Dec 14, 2015

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.
But I'm curious to know why the Angular Team made the decision to use Observables.

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.
But I'm curious to know why the Angular Team made the decision to use Observables.

@ericmartinezr

This comment has been minimized.

Show comment
Hide comment
@ericmartinezr

ericmartinezr Dec 14, 2015

Contributor

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

Contributor

ericmartinezr commented Dec 14, 2015

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

@PascalPrecht

This comment has been minimized.

Show comment
Hide comment
@PascalPrecht

PascalPrecht Dec 14, 2015

Contributor

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.

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?

You can turn any observable into a promise by calling .toPromise() on it. It's a stream with a subscription that ends as soon as the first call comes back.

Does that make sense @thelgevold ?

Contributor

PascalPrecht commented Dec 14, 2015

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.

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?

You can turn any observable into a promise by calling .toPromise() on it. It's a stream with a subscription that ends as soon as the first call comes back.

Does that make sense @thelgevold ?

@thelgevold

This comment has been minimized.

Show comment
Hide comment
@thelgevold

thelgevold Dec 14, 2015

Contributor

@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.

Contributor

thelgevold commented Dec 14, 2015

@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.

@PascalPrecht

This comment has been minimized.

Show comment
Hide comment
@PascalPrecht

PascalPrecht Dec 14, 2015

Contributor

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.

Contributor

PascalPrecht commented Dec 14, 2015

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.

@thelgevold

This comment has been minimized.

Show comment
Hide comment
@thelgevold

thelgevold Dec 14, 2015

Contributor

I agree, using promises is definitely an alternative.
I will likely experiment with both - at least for my ongoing blog/POC work.

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.

Contributor

thelgevold commented Dec 14, 2015

I agree, using promises is definitely an alternative.
I will likely experiment with both - at least for my ongoing blog/POC work.

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.

@yfain

This comment has been minimized.

Show comment
Hide comment
@yfain

yfain Dec 15, 2015

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 commented Dec 15, 2015

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.

@thelgevold

This comment has been minimized.

Show comment
Hide comment
@thelgevold

thelgevold Dec 15, 2015

Contributor

@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.

Contributor

thelgevold commented Dec 15, 2015

@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.

@robwormald

This comment has been minimized.

Show comment
Hide comment
@robwormald

robwormald Dec 16, 2015

Member

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 :)

Member

robwormald commented Dec 16, 2015

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 :)

@robwormald robwormald closed this Dec 16, 2015

@benlesh

This comment has been minimized.

Show comment
Hide comment
@benlesh

benlesh Dec 19, 2015

Contributor

In a single page application, promises are a sub-optimal choice for HTTP, because promises cannot be cancelled.

  1. User clicks a link and starts loading a view
  2. Routing/view logic starts loading some data via promise-wrapped AJAX.
  3. User changes their mind and leaves the view.
  4. It would be nice to cancel the AJAX request and resolution logic from step 2, but you can't because Promises don't support that.
    5 Go to 1

Promises have a few problems:

  1. They can't be cancelled, so you're going to execute code you didn't have to on occasion because of Promises. This gets worse on resource-constrained devices.
  2. They "trap" errors. If you throw inside of a then block, you must have another then block after or the error is lost. And best you might get a helper message in console, but there's no where to globally catch it like other unhandled errors (window.onerror)
  3. They can only handle one value.
  4. There's no filtering, etc.
  5. then is used for both mapping functionality and side-effects. Makes it a little harder to determine where side-effects occur.

All-in-all when I see heavy promise use in code, I suspect there could be bugs or unwanted behavior.

Contributor

benlesh commented Dec 19, 2015

In a single page application, promises are a sub-optimal choice for HTTP, because promises cannot be cancelled.

  1. User clicks a link and starts loading a view
  2. Routing/view logic starts loading some data via promise-wrapped AJAX.
  3. User changes their mind and leaves the view.
  4. It would be nice to cancel the AJAX request and resolution logic from step 2, but you can't because Promises don't support that.
    5 Go to 1

Promises have a few problems:

  1. They can't be cancelled, so you're going to execute code you didn't have to on occasion because of Promises. This gets worse on resource-constrained devices.
  2. They "trap" errors. If you throw inside of a then block, you must have another then block after or the error is lost. And best you might get a helper message in console, but there's no where to globally catch it like other unhandled errors (window.onerror)
  3. They can only handle one value.
  4. There's no filtering, etc.
  5. then is used for both mapping functionality and side-effects. Makes it a little harder to determine where side-effects occur.

All-in-all when I see heavy promise use in code, I suspect there could be bugs or unwanted behavior.

@thelgevold

This comment has been minimized.

Show comment
Hide comment
@thelgevold

thelgevold Dec 19, 2015

Contributor

@robwormald @blesh
I am open to Observables in general, but I am still not convinced that the advantages are as clear in the case of run of the mill http requests.

Cancellation:
Granted, it's nice to be able to cancel requests, but I don't consider that a very frequent operation. Besides on devices, cancellation of navigation may often be of just theoretical value. By the time you get back to your small "hamburger" menu to navigate somewhere else, your initial call has likely already finished :-) I also think we could get the cancellation benefits by extending the promise standard to include a cancellation call since that seems to be the most cited benefit. I see this as an added feature of Observables - not something that is necessarily tied to just Observables. I remember plain xhr requests in jquery contained a request cancellation feature. In any event, my view is that the benefits of cancellation does not weigh up for the re-education of devs and shift in approach. It has also presented a challenge when it comes to the Angular 2 http api since it's too heavy to bring in the entire library.

Side Effects:
I agree that junior developers often set global variables/side effects in promise handlers, but Observables are also callback oriented, so the same devs are likely to do the same thing using Observables. I always make sure to return results up the chain, which in my opinion makes for very clean promise handling - without side effects. The limitation of only one result is not a real issue since you can return any object from anywhere in the chain. I can also reuse the same promise over and over again as an easy caching mechanism.

Errors:
It's true that you need a subsequent 'then', to handle error, but the 'then().catch()' pattern is a great convention here. I never use 'thens' with two callbacks since it won't handle issues in the success handler itself. Instead I use 'catch' since it makes for more readable code. (BTW I understand that catch is just then in disguise)

someService()
.then(singleCallBack)
.catch(singleErrorCallback)

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.
Your assumption that promise based code is buggy in general is far too broad..... :-)

Contributor

thelgevold commented Dec 19, 2015

@robwormald @blesh
I am open to Observables in general, but I am still not convinced that the advantages are as clear in the case of run of the mill http requests.

Cancellation:
Granted, it's nice to be able to cancel requests, but I don't consider that a very frequent operation. Besides on devices, cancellation of navigation may often be of just theoretical value. By the time you get back to your small "hamburger" menu to navigate somewhere else, your initial call has likely already finished :-) I also think we could get the cancellation benefits by extending the promise standard to include a cancellation call since that seems to be the most cited benefit. I see this as an added feature of Observables - not something that is necessarily tied to just Observables. I remember plain xhr requests in jquery contained a request cancellation feature. In any event, my view is that the benefits of cancellation does not weigh up for the re-education of devs and shift in approach. It has also presented a challenge when it comes to the Angular 2 http api since it's too heavy to bring in the entire library.

Side Effects:
I agree that junior developers often set global variables/side effects in promise handlers, but Observables are also callback oriented, so the same devs are likely to do the same thing using Observables. I always make sure to return results up the chain, which in my opinion makes for very clean promise handling - without side effects. The limitation of only one result is not a real issue since you can return any object from anywhere in the chain. I can also reuse the same promise over and over again as an easy caching mechanism.

Errors:
It's true that you need a subsequent 'then', to handle error, but the 'then().catch()' pattern is a great convention here. I never use 'thens' with two callbacks since it won't handle issues in the success handler itself. Instead I use 'catch' since it makes for more readable code. (BTW I understand that catch is just then in disguise)

someService()
.then(singleCallBack)
.catch(singleErrorCallback)

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.
Your assumption that promise based code is buggy in general is far too broad..... :-)

@benlesh

This comment has been minimized.

Show comment
Hide comment
@benlesh

benlesh Dec 20, 2015

Contributor

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.

Contributor

benlesh commented Dec 20, 2015

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.

@thelgevold

This comment has been minimized.

Show comment
Hide comment
@thelgevold

thelgevold Dec 21, 2015

Contributor

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 :-)

Contributor

thelgevold commented Dec 21, 2015

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 :-)

@jamesthurley

This comment has been minimized.

Show comment
Hide comment
@jamesthurley

jamesthurley Dec 23, 2015

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?

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?

@benlesh

This comment has been minimized.

Show comment
Hide comment
@benlesh

benlesh Dec 23, 2015

Contributor

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.

Contributor

benlesh commented Dec 23, 2015

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.

@robwormald

This comment has been minimized.

Show comment
Hide comment
@robwormald

robwormald Dec 28, 2015

Member

@benjamingr oh dear, I'd hoped we'd seen the last of that thread...

Member

robwormald commented Dec 28, 2015

@benjamingr oh dear, I'd hoped we'd seen the last of that thread...

@benjamingr

This comment has been minimized.

Show comment
Hide comment
@benjamingr

benjamingr Dec 28, 2015

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 :)

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 :)

@jamesthurley

This comment has been minimized.

Show comment
Hide comment
@jamesthurley

jamesthurley Dec 28, 2015

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(3) or .retryWhen(..) is frustrating, as you wouldn't actually use them in real life HTTP requests.

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(3) or .retryWhen(..) is frustrating, as you wouldn't actually use them in real life HTTP requests.

@benjamingr

This comment has been minimized.

Show comment
Hide comment
@benjamingr

benjamingr Dec 28, 2015

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.

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.

@fxck

This comment has been minimized.

Show comment
Hide comment
@fxck

fxck Dec 28, 2015

@benjamingr please do show me how to trivially cancel a promise :)

fxck commented Dec 28, 2015

@benjamingr please do show me how to trivially cancel a promise :)

@jamesthurley

This comment has been minimized.

Show comment
Hide comment
@jamesthurley

jamesthurley Dec 28, 2015

I think that google groups thread contains a lot about cancelling promises, no need to start that again here! :)

I think that google groups thread contains a lot about cancelling promises, no need to start that again here! :)

@benjamingr

This comment has been minimized.

Show comment
Hide comment
@benjamingr

benjamingr Dec 28, 2015

@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.

@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.

@thelgevold

This comment has been minimized.

Show comment
Hide comment
@thelgevold

thelgevold Dec 28, 2015

Contributor

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.

http.asObservable().get('/myUrl').subscribe(...);

vs

http.asPromise().get('myUrl').then(...)

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.

Contributor

thelgevold commented Dec 28, 2015

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.

http.asObservable().get('/myUrl').subscribe(...);

vs

http.asPromise().get('myUrl').then(...)

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.

@benjamingr

This comment has been minimized.

Show comment
Hide comment
@benjamingr

benjamingr Dec 28, 2015

@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.

@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.

@benlesh

This comment has been minimized.

Show comment
Hide comment
@benlesh

benlesh Dec 29, 2015

Contributor

@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.

Contributor

benlesh commented Dec 29, 2015

@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.

@benjamingr

This comment has been minimized.

Show comment
Hide comment
@benjamingr

benjamingr Dec 29, 2015

@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 finally). Analogous to return on a generator.

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.

@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 finally). Analogous to return on a generator.

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.

@jamesthurley

This comment has been minimized.

Show comment
Hide comment
@jamesthurley

jamesthurley Dec 29, 2015

@benjamingr I agree conditional retry can be done in promises trivially, but to help people like myself adapt to the "Angular 2 way" it would still be nice to see the recommended approach with observables.

I guess implementing something like the Operator Retry with predicate from RxJava would be a good approach? I couldn't find an equivalent in RxJS already from my googling...

Just to be clear, I'm talking about retrying on communications failures, but not on non-transient failures like authorization, not found, etc.

I don't mean to drag this off-topic, I guess my main point was just that showing these "trivial" but realistic scenarios with observables will help get people on board. If people can't easily work out the trivial stuff, then they won't even get to reading about the advanced scenarios.

@benjamingr I agree conditional retry can be done in promises trivially, but to help people like myself adapt to the "Angular 2 way" it would still be nice to see the recommended approach with observables.

I guess implementing something like the Operator Retry with predicate from RxJava would be a good approach? I couldn't find an equivalent in RxJS already from my googling...

Just to be clear, I'm talking about retrying on communications failures, but not on non-transient failures like authorization, not found, etc.

I don't mean to drag this off-topic, I guess my main point was just that showing these "trivial" but realistic scenarios with observables will help get people on board. If people can't easily work out the trivial stuff, then they won't even get to reading about the advanced scenarios.

@robwormald

This comment has been minimized.

Show comment
Hide comment
@robwormald

robwormald Dec 29, 2015

Member

@jamesthurley the simplest example uses the .catch() operator and looks like this: https://jsfiddle.net/8xba6s1k/1/

obs.catch((err, sourceObs) => {
  //check error for whatever
  if(err === 'foo'){
    //retry by simply returning the original
    return sourceObs;
  }
  //otherwise fail out
  throw new Error('fail');
})

...and I got a bit carried away tinkering with this, so here's a configurable exponential backoff retrier thing adapted for Rx5: https://jsfiddle.net/wcuujjpn/1/

Certainly we'll add some docs on this sort of thing as well.

Member

robwormald commented Dec 29, 2015

@jamesthurley the simplest example uses the .catch() operator and looks like this: https://jsfiddle.net/8xba6s1k/1/

obs.catch((err, sourceObs) => {
  //check error for whatever
  if(err === 'foo'){
    //retry by simply returning the original
    return sourceObs;
  }
  //otherwise fail out
  throw new Error('fail');
})

...and I got a bit carried away tinkering with this, so here's a configurable exponential backoff retrier thing adapted for Rx5: https://jsfiddle.net/wcuujjpn/1/

Certainly we'll add some docs on this sort of thing as well.

@benjamingr

This comment has been minimized.

Show comment
Hide comment
@benjamingr

benjamingr Dec 29, 2015

@benjamingr I agree conditional retry can be done in promises trivially, but to help people like myself adapt to the "Angular 2 way" it would still be nice to see the recommended approach with observables.

Yes, but we need to do two distinct things - sell people observables aggressively and show people how to use them. The docs need to do both.

@benjamingr I agree conditional retry can be done in promises trivially, but to help people like myself adapt to the "Angular 2 way" it would still be nice to see the recommended approach with observables.

Yes, but we need to do two distinct things - sell people observables aggressively and show people how to use them. The docs need to do both.

@robwormald

This comment has been minimized.

Show comment
Hide comment
@robwormald

robwormald Dec 29, 2015

Member

further tinkering, now with a predicate to determine if we can recover or not https://jsfiddle.net/cefnjqfh/ for posterity.

Member

robwormald commented Dec 29, 2015

further tinkering, now with a predicate to determine if we can recover or not https://jsfiddle.net/cefnjqfh/ for posterity.

@jamesthurley

This comment has been minimized.

Show comment
Hide comment
@jamesthurley

jamesthurley Dec 29, 2015

Thanks @robwormald, it's really invaluable to see how you would approach this.

I must admit my brain started melting reading your more complicated examples, but I just need to dedicate some time to picking over it and learning more about Rx. This gives me good motivation to do so.

Thanks @robwormald, it's really invaluable to see how you would approach this.

I must admit my brain started melting reading your more complicated examples, but I just need to dedicate some time to picking over it and learning more about Rx. This gives me good motivation to do so.

@benjamingr

This comment has been minimized.

Show comment
Hide comment
@benjamingr

benjamingr Dec 29, 2015

@jamesthurley that's because @robwormald's backoff example is a complicated way to just do:

function exponentialRetry(obs, retries = 5) {
  // we need an exponential backoff, so let's do that
  const backoff = range(0, retries).concatMap(x => of(x).delay(Math.pow(2, x) * 1e3));
  // we need to have no initial delay, and to throw when we're out of retries
  const backoffStrategy = concat(of(0), backoff, Observable.throw(Error("No more retries")));
  // now we can filter with a marker so that we progress retries when we need to. 
  const Sentinal = Symbol(), marker = Observable.of(Sentinal);
  // and we join them all together
  return backoffStrategy.flatMap(x => obs.catch(y => marker)).filter(x => x !== Sentinal);
}

Or without the comments:

function exponentialRetry(obs, retries = 5) {
  const backoff = range(0, retries).concatMap(x => of(x).delay(Math.pow(2, x) * 1e3));
  const backoffStrategy = concat(of(0), backoff, Observable.throw(Error("No more retries")));
  const Sentinal = Symbol(), marker = Observable.of(Sentinal);
  return backoffStrategy.flatMap(x => obs.catch(y => marker)).filter(x => x !== Sentinal);
}

This can be "golfed" to a one liner, but I'm yet to see complicated examples with observables - they really do make complex async flows easier.

Here is promises btw, for comparison:

let exp = (fn, retries, delay) => fn().delay(delay).catch(e => !retries ? reject(e) : exp(fn, retries - 1, delay * 2));

And verbosely:

function expRetry(fn, retries = 5, delay = 1){ 
  return fn().delay(delay).catch(e => 
    if(retries > 0) return expRetry(fn, retries - 1, delay * 2));
    throw e;
  });
}

Both look a lot simpler if you're not used to Rx, it takes a while to get used to the rx coding style - sometimes it pays off - sometimes it's just complicated.

@jamesthurley that's because @robwormald's backoff example is a complicated way to just do:

function exponentialRetry(obs, retries = 5) {
  // we need an exponential backoff, so let's do that
  const backoff = range(0, retries).concatMap(x => of(x).delay(Math.pow(2, x) * 1e3));
  // we need to have no initial delay, and to throw when we're out of retries
  const backoffStrategy = concat(of(0), backoff, Observable.throw(Error("No more retries")));
  // now we can filter with a marker so that we progress retries when we need to. 
  const Sentinal = Symbol(), marker = Observable.of(Sentinal);
  // and we join them all together
  return backoffStrategy.flatMap(x => obs.catch(y => marker)).filter(x => x !== Sentinal);
}

Or without the comments:

function exponentialRetry(obs, retries = 5) {
  const backoff = range(0, retries).concatMap(x => of(x).delay(Math.pow(2, x) * 1e3));
  const backoffStrategy = concat(of(0), backoff, Observable.throw(Error("No more retries")));
  const Sentinal = Symbol(), marker = Observable.of(Sentinal);
  return backoffStrategy.flatMap(x => obs.catch(y => marker)).filter(x => x !== Sentinal);
}

This can be "golfed" to a one liner, but I'm yet to see complicated examples with observables - they really do make complex async flows easier.

Here is promises btw, for comparison:

let exp = (fn, retries, delay) => fn().delay(delay).catch(e => !retries ? reject(e) : exp(fn, retries - 1, delay * 2));

And verbosely:

function expRetry(fn, retries = 5, delay = 1){ 
  return fn().delay(delay).catch(e => 
    if(retries > 0) return expRetry(fn, retries - 1, delay * 2));
    throw e;
  });
}

Both look a lot simpler if you're not used to Rx, it takes a while to get used to the rx coding style - sometimes it pays off - sometimes it's just complicated.

@thelgevold

This comment has been minimized.

Show comment
Hide comment
@thelgevold

thelgevold Dec 29, 2015

Contributor

I personally have to start with the basics and work up from there. I have a decent understanding of how to use Observables to mimic promises when it comes to normal requests (single, chained requests and parallel requests), but I am still not familiar enough with the syntax to go this advanced with Observables (yet...). I will get there, but it will take a lot of effort to become as familiar with this as I am with promises.
The first step will be to understand all the new functions and their meaning. It's a relatively steep learning curve where the framework is a moving target - still being developed.

I will be honest and admit that the Observable retry sample above is far too advanced for me at the moment. I will be able to "get" it eventually, but it's a bit discouraging that we can't find clear examples where Observables truly shine compared to promises... Any mid/senior level programmer will take a two second look at the promise based equivalent sample and understand it right away... The same is not true for the Observable based solution.

I appreciate that Observables are designed for complex workflows, but I find that the world of coding is often not that complex.. And if it is, it's often our job to make it less complex :-)

Contributor

thelgevold commented Dec 29, 2015

I personally have to start with the basics and work up from there. I have a decent understanding of how to use Observables to mimic promises when it comes to normal requests (single, chained requests and parallel requests), but I am still not familiar enough with the syntax to go this advanced with Observables (yet...). I will get there, but it will take a lot of effort to become as familiar with this as I am with promises.
The first step will be to understand all the new functions and their meaning. It's a relatively steep learning curve where the framework is a moving target - still being developed.

I will be honest and admit that the Observable retry sample above is far too advanced for me at the moment. I will be able to "get" it eventually, but it's a bit discouraging that we can't find clear examples where Observables truly shine compared to promises... Any mid/senior level programmer will take a two second look at the promise based equivalent sample and understand it right away... The same is not true for the Observable based solution.

I appreciate that Observables are designed for complex workflows, but I find that the world of coding is often not that complex.. And if it is, it's often our job to make it less complex :-)

@benlesh

This comment has been minimized.

Show comment
Hide comment
@benlesh

benlesh Dec 30, 2015

Contributor

For exponential backoff, I usually just use something simple like retryWhen and a little mutable counter:

let backoff = 0;

myObservable.retryWhen(errors =>
  errors.switchMap(() => {
    if (navigator.online) {
      // we have internets! So take next step back delay (increment backoff)
      return Observable.timer(Math.pow(2, backoff++));
    } else {
      // no internet! just wait for the window to come back online.
      return Observable.fromEvent(window, 'online').take(1);
    }
  }))
  // we have a successful message, reset the backoff because we're working again
  .do(() => backoff = 0);

There's only two downsides to this:

  1. Some people don't like mutable state like backoff. Personally, as long as you're keeping it under control, I think it's legit.
  2. backoff is going to be set to 0 on every successful message. That's pretty low-cost though and shouldn't impact anything terribly.

EDIT: if you want to stop retrying, you can return an Observable.throw() or Observable.empty() in the retryWhen

Contributor

benlesh commented Dec 30, 2015

For exponential backoff, I usually just use something simple like retryWhen and a little mutable counter:

let backoff = 0;

myObservable.retryWhen(errors =>
  errors.switchMap(() => {
    if (navigator.online) {
      // we have internets! So take next step back delay (increment backoff)
      return Observable.timer(Math.pow(2, backoff++));
    } else {
      // no internet! just wait for the window to come back online.
      return Observable.fromEvent(window, 'online').take(1);
    }
  }))
  // we have a successful message, reset the backoff because we're working again
  .do(() => backoff = 0);

There's only two downsides to this:

  1. Some people don't like mutable state like backoff. Personally, as long as you're keeping it under control, I think it's legit.
  2. backoff is going to be set to 0 on every successful message. That's pretty low-cost though and shouldn't impact anything terribly.

EDIT: if you want to stop retrying, you can return an Observable.throw() or Observable.empty() in the retryWhen

@thelgevold

This comment has been minimized.

Show comment
Hide comment
@thelgevold

thelgevold Dec 30, 2015

Contributor

I have thought of a simpler case where Observables actually greatly simplify things. In the past I've implemented countless master detail grids where a user clicks a row to load (ajax) more details about a particular item. Along with this we need a way to prevent multiple calls in flight from the user clicking rapidly on multiple rows before the server responds. Without a way to cancel the current request we will have to either temporarily detach event handlers or otherwise disable the row for clicking.
With the unsubscribe feature of Observables this seems trivial since I can just unsubscribe before each call.

Not sure if this is the most elegant implementation, but it seems to work:
The idea here is that I hope the unsubscribe will prevent the results from being processed out of order and match the current row with the results belonging to a different row. Assuming the results of prior calls will simply be ignored and only the results of the most recent request will be processed on the client.

I would appreciate any ideas for improvements on this. To test it out I added a simple country/capitol grid to my Angular-2-Sample project.
Demo here: http://www.syntaxsuccess.com/angular-2-samples/#/demo/http

getCapitol(country){

        //Can this be done differently?
        if(this.pendingRequest){
            this.pendingRequest.unsubscribe();
            console.log('cancelled observable');
        }

        this.activeCountry = country;

        this.pendingRequest = this.http.get('./country-info/' + country + '.json')
                              .map((res: Response) => res.json())
                              .subscribe(res => this.capitol = res.capitol);
    }

I guess this is a variation on the type to search filter, but I think it might be a more common feature since master detail grids are so common. I guess you could argue that we could add cancellation to promises, but at least with Observables it's supported out of the box

Contributor

thelgevold commented Dec 30, 2015

I have thought of a simpler case where Observables actually greatly simplify things. In the past I've implemented countless master detail grids where a user clicks a row to load (ajax) more details about a particular item. Along with this we need a way to prevent multiple calls in flight from the user clicking rapidly on multiple rows before the server responds. Without a way to cancel the current request we will have to either temporarily detach event handlers or otherwise disable the row for clicking.
With the unsubscribe feature of Observables this seems trivial since I can just unsubscribe before each call.

Not sure if this is the most elegant implementation, but it seems to work:
The idea here is that I hope the unsubscribe will prevent the results from being processed out of order and match the current row with the results belonging to a different row. Assuming the results of prior calls will simply be ignored and only the results of the most recent request will be processed on the client.

I would appreciate any ideas for improvements on this. To test it out I added a simple country/capitol grid to my Angular-2-Sample project.
Demo here: http://www.syntaxsuccess.com/angular-2-samples/#/demo/http

getCapitol(country){

        //Can this be done differently?
        if(this.pendingRequest){
            this.pendingRequest.unsubscribe();
            console.log('cancelled observable');
        }

        this.activeCountry = country;

        this.pendingRequest = this.http.get('./country-info/' + country + '.json')
                              .map((res: Response) => res.json())
                              .subscribe(res => this.capitol = res.capitol);
    }

I guess this is a variation on the type to search filter, but I think it might be a more common feature since master detail grids are so common. I guess you could argue that we could add cancellation to promises, but at least with Observables it's supported out of the box

@robwormald

This comment has been minimized.

Show comment
Hide comment
@robwormald

robwormald Dec 30, 2015

Member

@thelgevold this is built into Rx - the .switchMap() operator does this - it's the same as .mergeMap() (aka .flatMap() ) except it will cancel any pending observables if a new one comes down the pipe.

Ideally, all events/action/whatevers would start with an Observable, which would make the system fully reactive - as we don't have that ability yet (see #4062 for a proposal to fix this), we can easily use a subject to accomplish it:

<button (click)="getCapitol.next(country)"> get capitol </button>
<div>{{ (capitol | async)?.name }}</div>
class Comp {
  getCapitol = new Subject();
  constructor(){
    this.capitol = this.getCapitol
      .switchMap(country => this.http.get(`./country-info/${country}.json`))
      .map(res => res.json());
  }
}

Slightly expanded demo of the above: http://plnkr.co/edit/kVm1m3vVwgGupCzRTTHV?p=preview (note if you spam the buttons quickly, you'll see the XHRs getting cancelled in the network log)

This, to me, illustrates why one shouldn't think about http Observables in isolation - when plugged into a reactive way of working, the benefits stand out more.

Member

robwormald commented Dec 30, 2015

@thelgevold this is built into Rx - the .switchMap() operator does this - it's the same as .mergeMap() (aka .flatMap() ) except it will cancel any pending observables if a new one comes down the pipe.

Ideally, all events/action/whatevers would start with an Observable, which would make the system fully reactive - as we don't have that ability yet (see #4062 for a proposal to fix this), we can easily use a subject to accomplish it:

<button (click)="getCapitol.next(country)"> get capitol </button>
<div>{{ (capitol | async)?.name }}</div>
class Comp {
  getCapitol = new Subject();
  constructor(){
    this.capitol = this.getCapitol
      .switchMap(country => this.http.get(`./country-info/${country}.json`))
      .map(res => res.json());
  }
}

Slightly expanded demo of the above: http://plnkr.co/edit/kVm1m3vVwgGupCzRTTHV?p=preview (note if you spam the buttons quickly, you'll see the XHRs getting cancelled in the network log)

This, to me, illustrates why one shouldn't think about http Observables in isolation - when plugged into a reactive way of working, the benefits stand out more.

@thelgevold

This comment has been minimized.

Show comment
Hide comment
@thelgevold

thelgevold Dec 30, 2015

Contributor

That's awesome. Thanks for taking the time to explain this!
I will definitely play around with this a bit more to better understand all the details. The Subject idea seems pretty powerful in combination with the switchMap though.

Contributor

thelgevold commented Dec 30, 2015

That's awesome. Thanks for taking the time to explain this!
I will definitely play around with this a bit more to better understand all the details. The Subject idea seems pretty powerful in combination with the switchMap though.

@benjamingr

This comment has been minimized.

Show comment
Hide comment
@benjamingr

benjamingr Dec 30, 2015

@thelgevold

I have thought of a simpler case where Observables actually greatly simplify things.

This is trivial to build out of no combinators with promises (based on your observable example):

function last(fn) {
    let prev = {cancel: function() {}};
    return function() { 
      if(prev && prev.cancel) prev.cancel();
      prev = fn.apply(this, arguments);
      return prev; 
    };
}

This will only execute the last request. Of course this can be trimmed to:

function last(fn, prev) {
    return function() { 
      if(prev && prev.cancel) prev.cancel();
      return prev = fn.apply(this, arguments);
    };
}

Even without cancellation, you can signal disinterest in the last request, there have been libraries with promises that do this for several years.

Subjects are like promise deferreds, and should pretty much only be used to convert APIs to promises and when writing new combinators, never in other scenarios as Rx elegantly lets you combine things automatically yourself.

Here is an (old) Rx (not 5) demo I used in my talk about observables in Angular 2 back in May. https://jsfiddle.net/8s2s247e/ (Thanks again to @blesh for reviewing it and finding embarrassing errors :D). It demonstrates autocomplete elegantly IMO - implementing this with promises would have very considerable overhead. I can dig a few more demos if you'd like.

@blesh

or exponential backoff, I usually just use something simple like retryWhen

Yes, that is probably how I'd implement it in my code too - it's simpler and requires much less understanding of the model, it's probably a bit faster too. I didn't want to demonstrate mutable state in an observable chain. I recall you being one of the strongest advocates against it from es-observable - but I guess we can both agree that if a pure function mutates some data in order to produce an immutable value - that's just fine :)

@thelgevold

I have thought of a simpler case where Observables actually greatly simplify things.

This is trivial to build out of no combinators with promises (based on your observable example):

function last(fn) {
    let prev = {cancel: function() {}};
    return function() { 
      if(prev && prev.cancel) prev.cancel();
      prev = fn.apply(this, arguments);
      return prev; 
    };
}

This will only execute the last request. Of course this can be trimmed to:

function last(fn, prev) {
    return function() { 
      if(prev && prev.cancel) prev.cancel();
      return prev = fn.apply(this, arguments);
    };
}

Even without cancellation, you can signal disinterest in the last request, there have been libraries with promises that do this for several years.

Subjects are like promise deferreds, and should pretty much only be used to convert APIs to promises and when writing new combinators, never in other scenarios as Rx elegantly lets you combine things automatically yourself.

Here is an (old) Rx (not 5) demo I used in my talk about observables in Angular 2 back in May. https://jsfiddle.net/8s2s247e/ (Thanks again to @blesh for reviewing it and finding embarrassing errors :D). It demonstrates autocomplete elegantly IMO - implementing this with promises would have very considerable overhead. I can dig a few more demos if you'd like.

@blesh

or exponential backoff, I usually just use something simple like retryWhen

Yes, that is probably how I'd implement it in my code too - it's simpler and requires much less understanding of the model, it's probably a bit faster too. I didn't want to demonstrate mutable state in an observable chain. I recall you being one of the strongest advocates against it from es-observable - but I guess we can both agree that if a pure function mutates some data in order to produce an immutable value - that's just fine :)

@robwormald

This comment has been minimized.

Show comment
Hide comment
@robwormald

robwormald Dec 30, 2015

Member

This is trivial to build out of no combinators with promises (based on your observable example):

Correct me if i'm wrong, but this still isn't going to cancel the underlying XHR, right?

Subjects are like promise deferreds

Agreed, but there's not a nice way to do it in angular2 currently, other than manually grabbing the DOM element and doing Observable.fromEvent - which is more verboten than using subjects in ng2, because it'll prevent usage in web-workers and the like. Ideally we'll implement an Observable decorator:

class FooComponent {
  @ObserveChild('myBtn' , 'click') buttonClicks: Observable<Event>
  @ObserveChild(SomeChildComponent, 'someCustomEvent') childClicks: Observable<Event>

  ngAfterViewInit(){
    this.buttonClicks.switchMap(...).subscribe(...);
  }
}
Member

robwormald commented Dec 30, 2015

This is trivial to build out of no combinators with promises (based on your observable example):

Correct me if i'm wrong, but this still isn't going to cancel the underlying XHR, right?

Subjects are like promise deferreds

Agreed, but there's not a nice way to do it in angular2 currently, other than manually grabbing the DOM element and doing Observable.fromEvent - which is more verboten than using subjects in ng2, because it'll prevent usage in web-workers and the like. Ideally we'll implement an Observable decorator:

class FooComponent {
  @ObserveChild('myBtn' , 'click') buttonClicks: Observable<Event>
  @ObserveChild(SomeChildComponent, 'someCustomEvent') childClicks: Observable<Event>

  ngAfterViewInit(){
    this.buttonClicks.switchMap(...).subscribe(...);
  }
}
@robwormald

This comment has been minimized.

Show comment
Hide comment
@robwormald

robwormald Dec 30, 2015

Member

Also - cool demo 👍

On Dec 29, 2015, at 11:38 PM, Benjamin Gruenbaum notifications@github.com wrote:

@thelgevold https://github.com/thelgevold
I have thought of a simpler case where Observables actually greatly simplify things.

This is trivial to build out of no combinators with promises (based on your observable example):

function last(fn) {
let prev = {cancel: function() {}};
return function() {
if(prev && prev.cancel) prev.cancel();
prev = fn.apply(this, arguments);
return prev;
};
}
This will only execute the last request. Of course this can be trimmed to:

function last(fn, prev) {
return function() {
if(prev && prev.cancel) prev.cancel();
return prev = fn.apply(this, arguments);
};
}
Even without cancellation, you can signal disinterest in the last request, there have been libraries with promises that do this for several years https://github.com/domenic/last.

Subjects are like promise deferreds, and should pretty much only be used to convert APIs to promises and when writing new combinators http://stackoverflow.com/questions/23803743/what-is-the-explicit-promise-construction-antipattern-and-how-do-i-avoid-it, never in other scenarios as Rx elegantly lets you combine things automatically yourself.

Here is an (old) Rx (not 5) demo I used in my talk about observables in Angular 2 back in May. https://jsfiddle.net/8s2s247e/ https://jsfiddle.net/8s2s247e/ (Thanks again to @blesh https://github.com/blesh for reviewing it and finding embarrassing errors :D). It demonstrates autocomplete elegantly IMO - implementing this with promises would have very considerable overhead. I can dig a few more demos if you'd like.

@blesh https://github.com/blesh
or exponential backoff, I usually just use something simple like retryWhen

Yes, that is probably how I'd implement it in my code too - it's simpler and requires much less understanding of the model, it's probably a bit faster too. I didn't want to demonstrate mutable state in an observable chain. I recall you being one of the strongest advocates against it from es-observable - but I guess we can both agree that if a pure function mutates some data in order to produce an immutable value - that's just fine http://clojure.org/transients :)


Reply to this email directly or view it on GitHub #5876 (comment).

Member

robwormald commented Dec 30, 2015

Also - cool demo 👍

On Dec 29, 2015, at 11:38 PM, Benjamin Gruenbaum notifications@github.com wrote:

@thelgevold https://github.com/thelgevold
I have thought of a simpler case where Observables actually greatly simplify things.

This is trivial to build out of no combinators with promises (based on your observable example):

function last(fn) {
let prev = {cancel: function() {}};
return function() {
if(prev && prev.cancel) prev.cancel();
prev = fn.apply(this, arguments);
return prev;
};
}
This will only execute the last request. Of course this can be trimmed to:

function last(fn, prev) {
return function() {
if(prev && prev.cancel) prev.cancel();
return prev = fn.apply(this, arguments);
};
}
Even without cancellation, you can signal disinterest in the last request, there have been libraries with promises that do this for several years https://github.com/domenic/last.

Subjects are like promise deferreds, and should pretty much only be used to convert APIs to promises and when writing new combinators http://stackoverflow.com/questions/23803743/what-is-the-explicit-promise-construction-antipattern-and-how-do-i-avoid-it, never in other scenarios as Rx elegantly lets you combine things automatically yourself.

Here is an (old) Rx (not 5) demo I used in my talk about observables in Angular 2 back in May. https://jsfiddle.net/8s2s247e/ https://jsfiddle.net/8s2s247e/ (Thanks again to @blesh https://github.com/blesh for reviewing it and finding embarrassing errors :D). It demonstrates autocomplete elegantly IMO - implementing this with promises would have very considerable overhead. I can dig a few more demos if you'd like.

@blesh https://github.com/blesh
or exponential backoff, I usually just use something simple like retryWhen

Yes, that is probably how I'd implement it in my code too - it's simpler and requires much less understanding of the model, it's probably a bit faster too. I didn't want to demonstrate mutable state in an observable chain. I recall you being one of the strongest advocates against it from es-observable - but I guess we can both agree that if a pure function mutates some data in order to produce an immutable value - that's just fine http://clojure.org/transients :)


Reply to this email directly or view it on GitHub #5876 (comment).

@benjamingr

This comment has been minimized.

Show comment
Hide comment
@benjamingr

benjamingr Dec 30, 2015

Correct me if i'm wrong, but this still isn't going to cancel the underlying XHR, right?

It does, I wouldn't have posted it as a counterpart otherwise. Most userland promise implementations support cancellation. For contrast, native observables currently (in the proposal) only have a .subscribe, forEach unsubscribe and new Observable. None of the fancy switchMap map filter and interval stuff. Here, open the network tab and see for yourself (click the button rapidly) - https://jsfiddle.net/gmcda1mc/

BTW, to be clear - just because Promises have sound cancellation and retry, that does not make them a good alternative to observables. When working with websockets - promises are simply not sufficient as they represent singular values. Again, I'm now 100% for observables in Angular everywhere - the most important thing here is to help the spec progress - when it's in it's likely it'll get a lot of the cool combinators in a future proposals - and users who want can use promises anyway.

Agreed, but there's not a nice way to do it in angular2 currently

Modeling events with the command pattern a-la MV* frameworks and not with observable subscriptions is a choice I disagree with. Personally, I'd go with something like:

    <button (click)="buttonClicked">Click Me</button>

And then something like:

  class ExampleComponent {
     // ...
    @Observe
    buttonClicked(o) {
        o.subscribe(ev => alert("Hello Click Event")); 
    }
  }

This would let us do throttling very easily. I would definitely not do it with selectors, and I would definitely not make users write subjects as they'd get them very wrong (at least this is the case with promises when users promisify APIs).

But I think users will find observables as an end to end default very confusing without prior background.

Correct me if i'm wrong, but this still isn't going to cancel the underlying XHR, right?

It does, I wouldn't have posted it as a counterpart otherwise. Most userland promise implementations support cancellation. For contrast, native observables currently (in the proposal) only have a .subscribe, forEach unsubscribe and new Observable. None of the fancy switchMap map filter and interval stuff. Here, open the network tab and see for yourself (click the button rapidly) - https://jsfiddle.net/gmcda1mc/

BTW, to be clear - just because Promises have sound cancellation and retry, that does not make them a good alternative to observables. When working with websockets - promises are simply not sufficient as they represent singular values. Again, I'm now 100% for observables in Angular everywhere - the most important thing here is to help the spec progress - when it's in it's likely it'll get a lot of the cool combinators in a future proposals - and users who want can use promises anyway.

Agreed, but there's not a nice way to do it in angular2 currently

Modeling events with the command pattern a-la MV* frameworks and not with observable subscriptions is a choice I disagree with. Personally, I'd go with something like:

    <button (click)="buttonClicked">Click Me</button>

And then something like:

  class ExampleComponent {
     // ...
    @Observe
    buttonClicked(o) {
        o.subscribe(ev => alert("Hello Click Event")); 
    }
  }

This would let us do throttling very easily. I would definitely not do it with selectors, and I would definitely not make users write subjects as they'd get them very wrong (at least this is the case with promises when users promisify APIs).

But I think users will find observables as an end to end default very confusing without prior background.

@thelgevold

This comment has been minimized.

Show comment
Hide comment
@thelgevold

thelgevold Dec 30, 2015

Contributor

@benjamingr @robwormald @blesh
Ok, so given that both Subjects and interacting with DOM elements are both discouraged. How would I write my sample the "clean" way today?

I think my code is pretty verbose and not that elegant, but is it at least conceptually correct?

I was able to see http request get cancelled in the network tab with this, so the behavior seems to be correct. I want to learn more about the advanced "map" functions, but is it generally not recommended to call unsubscribe manually for this use case?

        //Can this be done differently?
        if(this.pendingRequest){
            this.pendingRequest.unsubscribe();
            console.log('cancelled observable');
        }

        this.activeCountry = country;

        this.pendingRequest = this.http.get('./country-info/' + country + '.json')
                              .map((res: Response) => res.json())
                              .subscribe(res => this.capitol = res.capitol);
Contributor

thelgevold commented Dec 30, 2015

@benjamingr @robwormald @blesh
Ok, so given that both Subjects and interacting with DOM elements are both discouraged. How would I write my sample the "clean" way today?

I think my code is pretty verbose and not that elegant, but is it at least conceptually correct?

I was able to see http request get cancelled in the network tab with this, so the behavior seems to be correct. I want to learn more about the advanced "map" functions, but is it generally not recommended to call unsubscribe manually for this use case?

        //Can this be done differently?
        if(this.pendingRequest){
            this.pendingRequest.unsubscribe();
            console.log('cancelled observable');
        }

        this.activeCountry = country;

        this.pendingRequest = this.http.get('./country-info/' + country + '.json')
                              .map((res: Response) => res.json())
                              .subscribe(res => this.capitol = res.capitol);
@benjamingr

This comment has been minimized.

Show comment
Hide comment
@benjamingr

benjamingr Dec 30, 2015

@thelgevold I don't know, I'm not an Angular 2 user - apart from being invited to participate and then being talked down to - I was never really a part of the discussion of how to shape these APIs. Luckily, I didn't get my way back then or the API would have looked differently and observables would probably not have as strong of a case for inclusion in the language core.

Warning: what's coming next is very opinionated

I personally think Knockout had the right idea of binding to observables but failed to use actual reactive observables. If you could bind to observables and get events directly as observables - you could easily work end-to-end with observables and get a very declarative and clean data flow.

When I write FE code, I code a lot of React with Rx (no flux) and I don't really miss the notion of a framework. Things just flow through a centralized bus observable that's an Rx scan (the reducer), and then break up to smaller observables until they reach components and bind to their data. Data flows up through observables (I subclass React.Component with a subclass that lets me get onClick etc as observables) - state flows up to the bus where processing happens. Reducers stack up like in redux, all the app state is an immutable snap except for transient state.

This isn't flux, but it's super simple and it works like a charm. I could do the same with Angular I guess. You can create a Component class and give it those abilities, and then have your components subclass that (or with a decorator, if that's your thing).

I don't use this style of coding all the time - mainly when building things myself. Mainly since other developers who are not as accustomed to Rx and would find it confusing.

@thelgevold I don't know, I'm not an Angular 2 user - apart from being invited to participate and then being talked down to - I was never really a part of the discussion of how to shape these APIs. Luckily, I didn't get my way back then or the API would have looked differently and observables would probably not have as strong of a case for inclusion in the language core.

Warning: what's coming next is very opinionated

I personally think Knockout had the right idea of binding to observables but failed to use actual reactive observables. If you could bind to observables and get events directly as observables - you could easily work end-to-end with observables and get a very declarative and clean data flow.

When I write FE code, I code a lot of React with Rx (no flux) and I don't really miss the notion of a framework. Things just flow through a centralized bus observable that's an Rx scan (the reducer), and then break up to smaller observables until they reach components and bind to their data. Data flows up through observables (I subclass React.Component with a subclass that lets me get onClick etc as observables) - state flows up to the bus where processing happens. Reducers stack up like in redux, all the app state is an immutable snap except for transient state.

This isn't flux, but it's super simple and it works like a charm. I could do the same with Angular I guess. You can create a Component class and give it those abilities, and then have your components subclass that (or with a decorator, if that's your thing).

I don't use this style of coding all the time - mainly when building things myself. Mainly since other developers who are not as accustomed to Rx and would find it confusing.

@thelgevold

This comment has been minimized.

Show comment
Hide comment
@thelgevold

thelgevold Dec 31, 2015

Contributor

@robwormald
I know you cautioned against the use of fromEvent. I tried it and I totally agree - it's not a good idea.

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.

<table id="tableId" class="table">
    <tr [ngClass]="{active: isActive('usa')}"><td id="usa">USA</td></tr>
    <tr [ngClass]="{active: isActive('denmark')}"><td id="denmark">Denmark</td></tr>
    <tr [ngClass]="{active: isActive('germany')}"><td id="germany">Germany</td></tr>
    <tr [ngClass]="{active: isActive('argentina')}"><td id="argentina">Argentina</td></tr>
</table>

ngOnInit(){

    this.pendingRequest = Observable.fromEvent(document.getElementsByTagName('td'),'click')
        .switchMap((r:any) => {
                               this.activeCountry = r.target.id;
                               return this.http.get('./country-info/' + this.activeCountry + '.json')
                             })
       .map((res: Response) => res.json())
       .subscribe(r => this.capitol = r.capitol);
}

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.

<div ObservableGroup=['group1',{country:'usa'}]>USA</div>
<div ObservableGroup=['group1',{country:'germany'}]>Germany</div>

Observable.fromGroupAndEvent('group1','click')
.switchMap((r:any) => {
                                       this.activeCountry = r.country;
                                       return this.http.get('./country-info/' + this.activeCountry + '.json')
                                  })
            .map((res: Response) => res.json())
            .subscribe(r => this.capitol = r.capitol);

Contributor

thelgevold commented Dec 31, 2015

@robwormald
I know you cautioned against the use of fromEvent. I tried it and I totally agree - it's not a good idea.

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.

<table id="tableId" class="table">
    <tr [ngClass]="{active: isActive('usa')}"><td id="usa">USA</td></tr>
    <tr [ngClass]="{active: isActive('denmark')}"><td id="denmark">Denmark</td></tr>
    <tr [ngClass]="{active: isActive('germany')}"><td id="germany">Germany</td></tr>
    <tr [ngClass]="{active: isActive('argentina')}"><td id="argentina">Argentina</td></tr>
</table>

ngOnInit(){

    this.pendingRequest = Observable.fromEvent(document.getElementsByTagName('td'),'click')
        .switchMap((r:any) => {
                               this.activeCountry = r.target.id;
                               return this.http.get('./country-info/' + this.activeCountry + '.json')
                             })
       .map((res: Response) => res.json())
       .subscribe(r => this.capitol = r.capitol);
}

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.

<div ObservableGroup=['group1',{country:'usa'}]>USA</div>
<div ObservableGroup=['group1',{country:'germany'}]>Germany</div>

Observable.fromGroupAndEvent('group1','click')
.switchMap((r:any) => {
                                       this.activeCountry = r.country;
                                       return this.http.get('./country-info/' + this.activeCountry + '.json')
                                  })
            .map((res: Response) => res.json())
            .subscribe(r => this.capitol = r.capitol);

@robwormald

This comment has been minimized.

Show comment
Hide comment
@robwormald

robwormald Dec 31, 2015

Member

@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.

Member

robwormald commented Dec 31, 2015

@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.

@benlesh

This comment has been minimized.

Show comment
Hide comment
@benlesh

benlesh Dec 31, 2015

Contributor

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.

Contributor

benlesh commented Dec 31, 2015

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.

@benjamingr

This comment has been minimized.

Show comment
Hide comment
@benjamingr

benjamingr Dec 31, 2015

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:

  • In RxJS 2 (some is in 4, but no code is in 5 yet, I didn't think/know it was production ready yet).
  • Not very performance sensitive, the parts on the server that use RxJS create a few observables when the app starts and then reuses them with promises assimilated for singular things. The parts on the client never had any performance issues because of RxJS itself.

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.

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:

  • In RxJS 2 (some is in 4, but no code is in 5 yet, I didn't think/know it was production ready yet).
  • Not very performance sensitive, the parts on the server that use RxJS create a few observables when the app starts and then reuses them with promises assimilated for singular things. The parts on the client never had any performance issues because of RxJS itself.

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.

@benjamingr

This comment has been minimized.

Show comment
Hide comment
@benjamingr

benjamingr Dec 31, 2015

@robwormald

@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.

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?

@robwormald

@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.

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?

@robwormald

This comment has been minimized.

Show comment
Hide comment
@robwormald

robwormald Dec 31, 2015

Member

What can I say? I like my templates...

On Dec 30, 2015, at 11:43 PM, Benjamin Gruenbaum notifications@github.com wrote:

@robwormald https://github.com/robwormald
@benjamingr https://github.com/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.

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?


Reply to this email directly or view it on GitHub #5876 (comment).

Member

robwormald commented Dec 31, 2015

What can I say? I like my templates...

On Dec 30, 2015, at 11:43 PM, Benjamin Gruenbaum notifications@github.com wrote:

@robwormald https://github.com/robwormald
@benjamingr https://github.com/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.

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?


Reply to this email directly or view it on GitHub #5876 (comment).

@benlesh

This comment has been minimized.

Show comment
Hide comment
@benlesh

benlesh Dec 31, 2015

Contributor

@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.

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 mergeMap/flatMap or others (switchMap, concatMap, fromEvent, et al), that overhead is reduced to just calling the function on the result directly.

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.

Contributor

benlesh commented Dec 31, 2015

@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.

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 mergeMap/flatMap or others (switchMap, concatMap, fromEvent, et al), that overhead is reduced to just calling the function on the result directly.

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.

@benjamingr

This comment has been minimized.

Show comment
Hide comment
@benjamingr

benjamingr Dec 31, 2015

That's another observable, another subscription, another observer, extra function calls down your chain, etc. Overhead.

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 .map is attached?

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).

That's another observable, another subscription, another observer, extra function calls down your chain, etc. Overhead.

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 .map is attached?

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).

@benlesh

This comment has been minimized.

Show comment
Hide comment
@benlesh

benlesh Dec 31, 2015

Contributor

What does it actually give you on top of just using Rx with a virtual dom with components?

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.

Contributor

benlesh commented Dec 31, 2015

What does it actually give you on top of just using Rx with a virtual dom with components?

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.

@benlesh

This comment has been minimized.

Show comment
Hide comment
@benlesh

benlesh Dec 31, 2015

Contributor

I'd expect Rx to optimize this under the hood and not really create a new subscription/observable/observer until it has to

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 filter, map and scan when chained back to back.

Contributor

benlesh commented Dec 31, 2015

I'd expect Rx to optimize this under the hood and not really create a new subscription/observable/observer until it has to

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 filter, map and scan when chained back to back.

@robwormald

This comment has been minimized.

Show comment
Hide comment
@robwormald

robwormald Dec 31, 2015

Member

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.

@alxhub covered this in his talk at AngularConnect - https://youtu.be/bVI5gGTEQ_U?t=905 - Template Transforms.

A falcor example:
The sugared syntax, where graph is a sort of flag/hook that a custom template transform can look for:

<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>
Member

robwormald commented Dec 31, 2015

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.

@alxhub covered this in his talk at AngularConnect - https://youtu.be/bVI5gGTEQ_U?t=905 - Template Transforms.

A falcor example:
The sugared syntax, where graph is a sort of flag/hook that a custom template transform can look for:

<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>
@benjamingr

This comment has been minimized.

Show comment
Hide comment
@benjamingr

benjamingr Dec 31, 2015

I moved the optimization discussion to the Rx repo at ReactiveX/rxjs#1121 (I hope that's ok).

I moved the optimization discussion to the Rx repo at ReactiveX/rxjs#1121 (I hope that's ok).

@benjamingr

This comment has been minimized.

Show comment
Hide comment
@benjamingr

benjamingr Dec 31, 2015

I think one of the more compelling things about Angular 2 will be build-time tooling around statically analyzing templates.

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 import all other components you use it's very easy to generate dependency graphs (we need those for hierarchical bundling anyway). We really want to move to something like falcor (I'm a big fan) but our backend runs C# and nothing that runs falcor on .net is production ready. When we do - I suspect we can use our dependency map (or our injector, if I get my way) to do that.

I'll watch the AngularConnect talk. Maybe it'll make be a believer :)

I think one of the more compelling things about Angular 2 will be build-time tooling around statically analyzing templates.

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 import all other components you use it's very easy to generate dependency graphs (we need those for hierarchical bundling anyway). We really want to move to something like falcor (I'm a big fan) but our backend runs C# and nothing that runs falcor on .net is production ready. When we do - I suspect we can use our dependency map (or our injector, if I get my way) to do that.

I'll watch the AngularConnect talk. Maybe it'll make be a believer :)

@eliOcs

This comment has been minimized.

Show comment
Hide comment
@eliOcs

eliOcs Oct 22, 2016

All the examples that are defending the use of Obsevables are showing the edge cases where using them make sense.

¿Why don't you show an example that represents the common use case using Observables?

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 http.get returns I'm going to transform it into json and then update the capitol property with the result. But http.get will only ever return one result, so I'm subscribing for only one update which is very misleading.

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.

eliOcs commented Oct 22, 2016

All the examples that are defending the use of Obsevables are showing the edge cases where using them make sense.

¿Why don't you show an example that represents the common use case using Observables?

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 http.get returns I'm going to transform it into json and then update the capitol property with the result. But http.get will only ever return one result, so I'm subscribing for only one update which is very misleading.

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.

@fxck

This comment has been minimized.

Show comment
Hide comment
@fxck

fxck Oct 22, 2016

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.

fxck commented Oct 22, 2016

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.

@eliOcs

This comment has been minimized.

Show comment
Hide comment
@eliOcs

eliOcs Oct 22, 2016

You are forcing the use of a stream of events which can handle unlimited events when there is only one event to handle.

eliOcs commented Oct 22, 2016

You are forcing the use of a stream of events which can handle unlimited events when there is only one event to handle.

@fxck

This comment has been minimized.

Show comment
Hide comment
@fxck

fxck Oct 22, 2016

Yes, and why do you, as the consumer, care? Performance wise, it's the same, apart from using subscribe function instead of then it's all the same for you. If you don't want to use any of the extra handy features that comes with it being observable, you don't have to, use it as if it were a promise. You can even turn it into a promise just by doing .toPromise(). Also none is forcing you to use Http, you can use fetch or whatever else, the whole framework is written in a way that allows you easily swap libraries.

fxck commented Oct 22, 2016

Yes, and why do you, as the consumer, care? Performance wise, it's the same, apart from using subscribe function instead of then it's all the same for you. If you don't want to use any of the extra handy features that comes with it being observable, you don't have to, use it as if it were a promise. You can even turn it into a promise just by doing .toPromise(). Also none is forcing you to use Http, you can use fetch or whatever else, the whole framework is written in a way that allows you easily swap libraries.

@benlesh

This comment has been minimized.

Show comment
Hide comment
@benlesh

benlesh Oct 25, 2016

Contributor
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 Http lib if you choose. In fact, even with RxJS code, anything that takes an Observable will also accept a Promise. It's all very interop friendly by design.

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.

Contributor

benlesh commented Oct 25, 2016

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 Http lib if you choose. In fact, even with RxJS code, anything that takes an Observable will also accept a Promise. It's all very interop friendly by design.

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.

@thelgevold

This comment has been minimized.

Show comment
Hide comment
@thelgevold

thelgevold Oct 25, 2016

Contributor

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 async/await becoming a "thing".... Will this perhaps start this debate back up again?

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.

Contributor

thelgevold commented Oct 25, 2016

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 async/await becoming a "thing".... Will this perhaps start this debate back up again?

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.

@benlesh

This comment has been minimized.

Show comment
Hide comment
@benlesh

benlesh Oct 25, 2016

Contributor

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.

Contributor

benlesh commented Oct 25, 2016

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.

@benjamingr

This comment has been minimized.

Show comment
Hide comment
@benjamingr

benjamingr Oct 25, 2016

... only the second one can't be cancelled and will trap your errors.

@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.

... only the second one can't be cancelled and will trap your errors.

@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.

@benlesh

This comment has been minimized.

Show comment
Hide comment
@benlesh

benlesh Oct 28, 2016

Contributor

@blesh you've got to stop saying that

No I don't. ¯_(ツ)_/¯

Contributor

benlesh commented Oct 28, 2016

@blesh you've got to stop saying that

No I don't. ¯_(ツ)_/¯

@ahmad-moussawi

This comment has been minimized.

Show comment
Hide comment
@ahmad-moussawi

ahmad-moussawi Apr 25, 2017

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.
especially in such cases when you get onetime response.

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 Car[], the http module will return Observable<Car[]>, while what really needed is to manage the list items Observable<Car> and not the response itself, in this case we could get benefit of the Rx Operators on the data.

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. Promise<LodashExplicitArrayWrapper<Car>>.

this.http.get('/api/cars')
.then(q => q.sortBy(x => x.price ) )
.then(q => q.map(x => x.name) )
.then(q => q.value() ) 

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.
especially in such cases when you get onetime response.

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 Car[], the http module will return Observable<Car[]>, while what really needed is to manage the list items Observable<Car> and not the response itself, in this case we could get benefit of the Rx Operators on the data.

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. Promise<LodashExplicitArrayWrapper<Car>>.

this.http.get('/api/cars')
.then(q => q.sortBy(x => x.price ) )
.then(q => q.map(x => x.name) )
.then(q => q.value() ) 
@ghost

This comment has been minimized.

Show comment
Hide comment
@ghost

ghost May 11, 2017

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.

ghost commented May 11, 2017

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.

@msieurtoph

This comment has been minimized.

Show comment
Hide comment
@msieurtoph

msieurtoph Jun 1, 2017

One big difference between Promises and Observables, is that Observables are functions!
You need to subscribe to get the action done. Which is not the case with Promises.

I understand the power of Observables and the lacks of Promises.
But I got really confused because sometimes you need to execute something that return a Observable but you dont care about the output, you just need the job to be done, you dont need to chain any other instructions after.

Using Promises, I could do : return http.get(...) or return http.get(...).then(...).
In both cases, the http call was executed.

Using Observables, I have to chain a subscription absolutely. Because Observables are just functions. If I do not, the http call is never executed :
the simple return http.get(...) won't work.
I need to add a blank subscription : return http.get(...).subscribe() to get the http call executed.
And by the way, I need to share() it too, in case of any other subscription after. If I do not there will be one call by subscription...
So the final instruction is : return http.get(...).share().subscribe() which is more indigestible than the simple Promise instruction return http.get(...) ... and I think it is too bad about the Observables.

One big difference between Promises and Observables, is that Observables are functions!
You need to subscribe to get the action done. Which is not the case with Promises.

I understand the power of Observables and the lacks of Promises.
But I got really confused because sometimes you need to execute something that return a Observable but you dont care about the output, you just need the job to be done, you dont need to chain any other instructions after.

Using Promises, I could do : return http.get(...) or return http.get(...).then(...).
In both cases, the http call was executed.

Using Observables, I have to chain a subscription absolutely. Because Observables are just functions. If I do not, the http call is never executed :
the simple return http.get(...) won't work.
I need to add a blank subscription : return http.get(...).subscribe() to get the http call executed.
And by the way, I need to share() it too, in case of any other subscription after. If I do not there will be one call by subscription...
So the final instruction is : return http.get(...).share().subscribe() which is more indigestible than the simple Promise instruction return http.get(...) ... and I think it is too bad about the Observables.

@stanislavromanov

This comment has been minimized.

Show comment
Hide comment
@stanislavromanov

stanislavromanov Sep 18, 2017

this.http.get('./country-info/' + country + '.json')
    .map((res: Response) => res.json())
    .subscribe(res => this.capitol = res.capitol);

vs

const res = await this.http.get('./country-info/' + country + '.json').json();
this.capitol = res.capitol;

I totally don't see the point of Observable in this case TBH.

this.http.get('./country-info/' + country + '.json')
    .map((res: Response) => res.json())
    .subscribe(res => this.capitol = res.capitol);

vs

const res = await this.http.get('./country-info/' + country + '.json').json();
this.capitol = res.capitol;

I totally don't see the point of Observable in this case TBH.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment