-
Notifications
You must be signed in to change notification settings - Fork 90
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
Remove forEach and add "subscribe" overload #97
Conversation
I think That said - observable unsubscription is in-line with cancellation semantics. +1 on removing |
O_o |
Also, @domenic |
@benjamingr JS doesn't have a concept of "disposable" which is tied to resource cleanup like C# does. In a possible future we might want syntax for resource cleanup, in which case we'll probably want to introduce a symbol-named method for that. |
@zenparsing yes, but when you unsubscribe from an observable you're performing resource cleanup - like JS also doesn't have a concept of cancellation yet, so I'm not sure if I buy the "JS doesn't have a concept of" argument when JS doesn't have a concept of both things. |
@benjamingr I guess I fail to see the distinction between unsubscription and cancelling an action, if you view the "action" as subscribing (whatever that might entail). |
d398095
to
0b2f567
Compare
Reverted the renaming of "unsubscribe" to "cancel", should be considered separately, if at all. |
This doesn't really help with the two concerns raised at the TC39 meeting, namely that the close/error semantics should be built on promises and that cancelation should be built on cancel tokens. But maybe it is a stepping stone for getting there... |
I'm in favor of this, as I'd say 90% of people use RxJS observables with the |
Cancellation Tokens are a cool idea. I'd want to explore it more, because they do incur some rather unergonomic boilerplate. Handling completion and error in Promises I think is a no-go. That means that entirely sync observables are forced to handle errors asynchronously. It also means that errors will be caught in the promise trap, current incarnations of Observable actually throw. This is because unlike promise, which only has The promise cancellation design I saw recently needs a lot of work IMO. Tying observable to it will likely be a boat anchor. It's tying a new, untested, barely-vetted design of promise cancellation against a design that seems to have circled back to something that has existed for the better part of a decade in a consistent form. I'll grant that promise cancellation is mildly similar to .NET's Task, but the similarities stop at what IMO is the only solid feature, the CancelationToken. 👍 supporting CancellationTokens |
@Blesh cancellation is going to be based off tokens from what I understand. I also don't understand how error handling and completion can be done synchronously through a |
The committee discussion went roughly as follows. The impetus for standardizing observables, instead of leaving them as a library, is so that they can be used in other parts of the platform. (This is true of any data structure, and is why promises were standardized.) The web already has one data structure for signaling success/failure; what it could benefit from is one for repeated invocation, i.e. the "next" part of observables. Any DOM observables, per the document in this repo, would not even use the success/error parts. As such, it's a requirement of the Chrome team that observables layer cleanly with the platform's existing primitives for success/error, promises, if they are to become part of the JavaScript language and not simply a library. You mention "entirely sync observables", and that is quite worrying, as it reveals a disconnect between how this proposal has been presented to committee and how you are thinking about it. |
Indeed. I think it's important to involve related parties since Google, Microsoft and Netflix are all heavily invested in observables and their design. If Angular is rooting for zones and these sort of observables on one end and the Chrome team is rooting for different observables perhaps they should discuss the topic among themselves first. |
The topic has been discussed internally and our findings presented at the last TC39 meeting; I am reporting back to this repo. |
Why does the Chrome team dictate requirements? What makes that team any more important than the countless developers using the type? |
They're not more important (except insofar as we would be the ones implementing it). We all try to come to consensus, which means accommodating everyone's requirements. If that's not possible, things don't advance, but hopefully it won't come to that. |
Keep in mind these are the requirements for standardizing and implementing in the browser. We have no desire to change how developers use the type they're already using; they can continue doing that with no need to involve our requirements. We just need to be part of the consensus process if we're going to standardize something in the committee we sit on and implement it in the browser we ship. |
@domenic the reason I'm pondering is because the Angular team has been pushing observables pretty hard (for example, @jeffbcross and @robwormald). The requirements collected in the This is of course, no criticism towards you at all - you are the messenger of news and I appreciate these updates when the TC meeting results are less accessible to developers than ever (I don't know where to read meeting nodes, ESDiscuss is noisy and abandoned and only the ecma262 repo is reliably updated). |
HI I guess I'm in this thread now :).
News to me. Maybe some of us have expressed preferences or use cases for consideration, but there's a lot more prior art than Angular 2 driving this spec.
What is the Angular team blocking? |
@jeffbcross Hi! Well, a lot of the time we've discussed reasoning based on the Angular 2 use case - examples from it for examples. @Blesh is a very active participant here and was involved in the calls in Angular 2 and so on.
Well, the Chrome team is asking for observables that work with promises which means they report completion through a promise (think @Blesh and others are asking for observables to work like in Rx - in that they allow you to emit values synchronously - this means no unbounded buffers and other nice things but it breaks some other guarantees (like code execution order never changing). According to these people writing observables into JavaScript the way @domenic describes would make observables useless. Since you are a major consumer of Observables (Angular 2) and the last TC meeting consensus virtually means breaking every single bit of Angular 2 code using an Observable service (changing the signature of And I quote you:
Now, you guys all know each other which is why I found it odd you're asking for conflicting requirements - that's all. |
Note that we have no opposition to emitting values synchronously; that's in fact required for parity with DOM events. (Additionally, cancel tokens allow synchronous cancelation of the value emission.) It's the completion/error signals that are at issue here. |
I agree, I have yet to see a single line of userland angular2 code that uses the object signature.
We'd love to do that...
...that's very exciting, and we'd love to share our users' nascent experiences with Observable w/ Chrome.
I refer you to https://twitter.com/_alastair/status/734154161705603072 (and also that we've been pretty quiet on the spec itself, though perhaps that's our error and now you will likely not hear the end of me...) RE: spec changes:
Hypothetical example (this is trivial in RxJS today, so this is the "bar" for me) that might elucidate the use case (and represents the kind of interop I'd really like to see) Rx5 (using an Observable-based XHR wrapper, using the current cancellation semantics) //grab a DOM element
const searchInput = document.getElementById('searchInput');
//get an Observable of the input events
Observable.fromEvent(searchInput, 'input')
//map out just the text
.map(ev => ev.target.value)
//switchMap (nee "flatMapLatest") to make an http request
// *and cancel the in-flight request if a new value is emitted upstream before the previous request completes*
.switchMap(searchText => Rx.Ajax.get(`http://api.com?search=${searchText}`))
.subscribe(
response => console.log(response),
err => console.log('err', err)
) Let's imagine a future awesome-world where searchInput.on('input')
.map(ev => ev.target.value)
.switchMap(url => fetch(`http://api.com?search=${searchText}`))
.subscribe(
response => console.log(response),
err => console.log('err', err)
); Is such an API possible with the cancellation-token based Promises proposal + the TC39 recommendations for Observable? I realize this is a userland concern but this sort of thing is why Angular2 uses Observables (and why we're not currently using a lot of fetch) |
What @domenic is discussing would hypothetically look like this: searchInput.on('input')
.map(ev => ev.target.value)
.switchMap((url, token) => fetch(`http://api.com?search=${searchText}`, token))
.subscribe(response => console.log(response))
.then(done => console.log("Done, this shouldn't fire on an input in this case")),
.catch(err => console.log('err', err)); The issue here is whether or not it's fine for the callbacks that signal observable completion or error to fire synchronously. |
His rhetoric was right on, if a little pointed. You should probably just read the rest of the post and take it seriously. There are reasons this design illicits a negative response. The "I don't like your tone, so that invalidates your points" is a fallacy. |
I'll try and make time to wade through it later this week, but given this community's unwillingness to engage constructively, perhaps it's better that observables stay in userland. If there are fundamental conflicts between them and the rest of the primitives in the web platform (as claimed), and the community is not only unwilling to address them but is railing against any attempts to do so, then we simply can't build a solid collaborative standards foundation on this kind of interaction. |
How is the conversation not constructive if we're supposed to either "Show a convincing case for why synchronous completion/error is a requirement." or accept the cancellable Promise foundation, if what I did was precisely show convincing cases for sync completion/error? PS: I don't think the rhetoric I used is so unfamiliar to you. ;) It's the least to expect after solid examples are dismissed with "disturbing". |
Hundreds of code bases in RxJS isn't a large amount at all. In general, RxJS has not seen wide adoption until very recently - and that's arguably because it is being sold as part of the spec. I've been using Rx for a while and there hasn't ever really been a stable API surface since v2.x. The library has been changing a lot and it's still beta. That said, the
👍
That's entirely unfair. That's not the argument anyone is raising.
I think the easy way to explain this is that observables are like iterables and not like arrays. They're not even a collection to begin with - they're push streams.
To be fair, I'm sure though that we can come up with more examples where the promise-based proposal breaks. On another note, I agree with Domenic that this sort of rhetoric makes it hard to interact. If we ever want to see native observables (and I do) we need to interact in a much less intentionally argumentative tone. You don't like promises - that's fine but the TC does like promises, WHATWG and browsers are major consumers of promises and NodeJS is working on a promised API. Promises are generally seeing a very high adoption rates, language syntax already implemented in two browsers (async/await), new cancellation syntax and new native APIs. On the other hand, observables have not seen any platform adoption at all. I'd like to see that change. In order for that to change we need to work with WHATWG, we need to work with the TC and we need to work with the community. Just having a library we think is awesome does not justify spec inclusion. You have a lot of very smart things to say, you're a library author of a really cool library and are an Rx core contributor - you have a stake in this and your opinion is valued. If you don't say them in a way that makes interacting with you possible for the TC and DOM API stakeholders they won't listen to you and your feedback won't be considered. Domenic will have these discussions in private with jhusain and zenparsing without involving you or me in the process. This can in all likelihood slow down the process even further which is a real shame since we've been at this for over a year now and the proposal is still stage 1. |
promises-aplus/promises-spec#94 (comment)
;) |
@staltz ok that's pretty funny. Aside from the lols - you're criticizing the way a decision was made but you want to make one the same way. (Unrelated - in that case Domenic was fending off people who have no experience with the spec or language. In this case we're talking about the language technical committee itself - so it's hardly comparable) |
Also, I would like to see more use cases aside from Also note, we're arguing about @domenic what about something like a |
I think that's probably a two way street. Come on back to the table. I'd like you to look at #99 |
Yes, something along those lines makes a lot of sense to me. Synchronous promise inspection as a recipe for integrating promises and observables was one possibility we discussed with the champions at TC39. We hadn't considered exposing it directly, but instead simply using it to ensure that next() cannot be called after the promise is resolved or rejected, but it of course makes sense that if it's something the standard would need, it's something that libraries would want as well. In this case it would be |
Perhaps I don't understand the intent. |
It's the returned promise whose state would be synchronously inspectable. |
This isn't directed at @benjamingr, but I wanted to reply to the general sentiment this comment embodies that I've witnessed throughout this process. Perhaps JavaScript isn't the appropriate environment in which to judge Rx's merits, especially since "spec-compliant" version has been largely developed with minimal input from the original authors (@headinthebox, @bartdesmet, @mattpodwysocki, etc.). Here's the strongest case I can make for implementing the full Observable algebra (Observables, Observers, Schedulers, Disposables, etc.) as-is:
TC39 can certainly ignore the millions of dollars and dozens of man-years that companies like Microsoft, Google, and Netflix have poured into research, development, and battle-testing Rx, but considering this is the future of one of the most widely deployed programming languages in history, it would seem prudent to at least acknowledge the experience of those that have done it before, if not intensely study their findings. |
First of all - I just want to point out I use a lot of Rx in my code, both in our backend in C# and in our frontend in JS. If I wasn't interested in the spec progressing and native observables in JS I wouldn't be here. I am not pointing out Rx isn't as popular in JS because I don't personally use it - I do so because it is the general sentiment I've been seeing. I probably used it less than anyone in this repo but more than most other people and we're heavily invested in Rx at TipRanks. Second of all - I generally agree with your sentiment but remember: we're talking about the interface of the subscribe method of es-observable. This does not take away from the experience of people developing with reactive streams. This discussion is not about whether or not observables are useful and not a single person here disagrees wit that. The API surface of Rx itself however - is a moving target in JavaScript.
The original authors as well as authors of Rx-libraries from other languages have been consistently pinged. I've emailed some of them several times asking for involvement and they have been very helpful in helping es-observable figure out various points.
Yet JS is the first mainstream language to gain native observable support - it's a lot easier to create a language with observables in than to modify an existing language to support them. The fact a concept is popular does not mean it should be added to a language - the motivation to add observables to the language is for the platform and language APIs to use them, when asked about this the DOM people outlined requirements and we should do whatever we can to make observables work for them so they implement APIs on top of observables. I'd like to see us be able to do
I think this is a part of the issue - JavaScript does not really have
I think it's hard to talk about duality when the API of iterables is different across languages, if we were to go for a duality based design it would have to dual JavaScript's iterable which would create a strange interface. When this was discussed in this repo, all the Rx stakeholders were against it since it would imply: Observable.of(...).subscribe(({done, value}) => {
if(done) /* ... */
// work with value
}); Duality is not sacred since a lot of iteration protocols are isomorphic with a trivial isomorphism. The API can change and the algebra will be as expressive (since well.. isomorphism).
Companies battle testing Rx and working on Rx can't ignore millions of dollars and dozens of man-years that companies like Google, Microsoft, Netflix, Mozilla and the rest of the TC members put into the language and the platform - it's a two way street. I'm not happy about the outcome of the TC meeting any more than you are @trxcllnt, I want to make that much clear - but we need to work with the platform consumers of the proposal or focus on a non-native interop proposal between observable libraries. |
Bla bla, yes, we all know. We do. The real question is what in the vanilla Observable algebra represents an actual obstacle to (say) Chrome team or platform consumers? So far I've just heard aesthetic arguments like "the Web already has a primitive for signalling complete/error". |
Let me reiterate this @staltz - "the vanilla observable algebra" does not represent duality directly at all. There is no duality and there hasn't really ever been one. Moreover, unsubscription has never formally been dualized at all, especially not with subscriptions and disposables:
Whether or not duality is important is another issue. Now - to answer your question directly:
On another note, when you say things like "vanilla Observable algebra" it confuses me - I'm all game for discussing what that thing is but throwing it out there without formally defining well... an algebra over something (with well, a vector space and a map) is strange. |
What? I didn't even mention duality. Paul did.
Not too fast. The web is not settling on anything. Domenic is settling on cancellation tokens. So far, cancellation tokens have been really badly received by the community and by experts, so there is definitely no settling going on, specially because it's a stage 1 proposal, not based on community practices to begin with. Also, it's not for "cancelling things". It's for cancelling Promises. Observables are different beasts than Promises, they can well have their own cancellation mechanisms.
Sure, DOM APIs like clicks are always infinite Observables of zero or many click events, but the main selling point for Observables is composability, and there are plenty of motivating examples related to that. In the example I gave before, repeated below, the top Observable is entirely async, it could be very well representing a click Observable from the DOM for instance. It is the composition with other Observables (in this case, a sync Observable of
And the composability features depend on the solid foundation that:
Without composability, there is no point in Observables. No point at all, because you could just have an addEventListener-like API with three callbacks as args. And by the way, the list above could well be an informal spec for the "algebra". |
@staltz I let this one slide, just now answering.
The latter. Also, most.js Streams never produce an event/end/error before a consuming operator, e.g. |
@zenparsing @jhusain can you please elaborate on the consensus that was reached regarding cancellation tokens in the meetings?
No, the TC discussed and decided against promise cancellation in the meeting (see tc39/proposal-cancelable-promises#10 (comment)) - I'm not happy about that either :) Promises are obviously very different from observables - they have very different semantics. What the TC decided from what I understand is to use tokens for cancellation of results of asynchronous functions. When other TC participants respond we'll have more data about the decision.
I'm interested in what @headinthebox was discussing when he said cancellation tokens could have made Rx cleaner in retrospect. Either way - calling subscriptions cancellation tokens like @Blesh suggested doesn't sound too harmful to me.
I agree, and we need to show how that composability is hurt if I would appreciate it if from the height of your experience you would write about that (and not "Bla bla") :) p.s.
that's pretty creative :D
I challenge you to make one and formalize it - I would be interested in the results. |
One thing that hasn't been mentioned is that if success and error are signaled by a promise, those two pieces of Observable are now always multicast. That will fundamentally change the type, to what end I'm unsure. It seems like it would be fine, but it also seems like an unnecessary change. |
@Blesh as long as |
tl;dr: Changes to the API or behavior aren't necessarily isomorphic, such as enforcing async subscriptions, emissions, errors, or completions. Changing those things will change the expressiveness of the algebra.
TC39's latest thoughts on sync vs. async subscribe/forEach are unclear to me. Are
I'm sorry I didn't think about this last week, but scheduling errors or completion breaks synchronous Observable composition. Subscribing to groupByUntil on a synchronous source would also be affected. This example (using Rx5 groupBy with a duration selector) will never complete the inner groups, because the synchronous source emission blocks the asynchronous completion: Observable.of(1, 2, 2, 2, 3)
.repeat()
.groupBy((x) => x % 2 === 0, null, (group) => group.take(3))
.mergeAll()
I understand the desire for re-use, but Promises' enforced asynchrony seems to prohibit using them for this purpose. While I find Promises to be a worthwhile addition to the language, Promises can be expressed as a subset of the Observable algebra, but not the other way around :/
Has the Chrome team considered an Observable -> Promise interop/conversion? An Observable can be made to behave like a Promise at the exit (or subscription) point, yet maintain the compositional Observable semantics before then.
I apologize if my comment sounded like I was singling you out, that was not my intention. You've contributed valuable insights in good faith to all the discussions I've been involved in, whether we agreed or not. I'm also aware that you're probably already familiar with everything I've said below, but I've responded anyway for the benefit of anybody who isn't.
I'm not sure whether to regard LINQ, async/await in C# as native language support, but I understand what you're getting at.
100% agreed 👍
I would like to achieve this as well. However, I'm not aware of any problems that have been presented which clearly suggest Observable is deficient. I have heard arguments that Observable is over-kill, that it has too many methods, that it doesn't comply with the "language aesthetic," or that functional programming will never be mainstream. The mutability of these arguments frustrates me. There are a good many problems that Observables don't solve. I would understand if the pushback was from concern about async disposal, or back pressure. These are two problems which the JS Observable spec doesn't presently address, but prior art suggests can be added to the Observable type without altering the underlying semantics.
Let me state that I'm unequivocally sympathetic to the needs and desires of the parties invested in the web platform. I'm simply trying to express the idea that no amount of consensus can overrule a set of immutable mathematical principles. The Observable just can't do certain things if some of the changes that have been proposed are dictated. Again, whether that matters or not is up to each person to decide for themselves.
I understand their position, and it's defensible. The traditional GoF Observer pattern would entirely satisfy their requirement, as it has in nearly every other UI environment for over two decades. The great discovery @headinthebox et. al. made a few years ago is that with just two additional messages, error or complete, the Observer pattern can be extended to solve new classes of problems in asynchronous programming.
I couldn't agree more. I wasn't advocating for duality as a holy grail, especially not duality with JavaScript's Iterator/Iterable. I brought up duality with Enumerable because Enumerable, as it exists in C# and elsewhere, is a well researched, thoroughly specified, and foundational concept in many mainstream functional programming languages. By achieving duality, Observable extends that research effort to apply to data from the future too. Duality ensures you can perform identical calculations whether the input data is from the past, future, or even a mix of both. Maybe I'm alone in thinking this, but the implications of that guarantee are damn exciting. I feel like a goddamn Time Wizard every day I go into work. I would be very disappointed if we choose aesthetically pleasing but incompatible semantics at the expense of that truly fantastic capability. |
No consensus was reached with regard to replacing subscriptions with cancellation tokens. However we did discuss whether it was possible. The issue in my mind is that subscription guarantees that no more notification will be sent to the observer, whereas canceling a cancellation token makes no sense guarantee with regard to whether a promise will be resolved. Observable can enforce additional guarantees not implied by the cancellation token. They can intercept the cancel method and ensure that observers are never notified once it has been invoked. Maintaining this invariant would likely involve additional runtime checks, but should be possible. JH
|
Would it be possible to schedule a chat or hangouts session between interested participants so we find ourselves in a situation such that in the next TC meeting the proposal is ready to progress to stage two? Namely, I think an agenda could look something like:
Is this something that could happen? |
@jhusain @zenparsing If it will help I will be attending the next TC39 meetings as they are in Redmond anyhow. I can bring Barr de Smet with me as we designed RxJS together and he has a lot of insight with async subscription and disposal. Else I'm willing to be in any hangout on the matter. |
@benjamingr I don't see why not. More than willing to help |
@domenic Would it be possible to go into more detail here about what makes the RxJS model incompatible / conflicting with the existing spec? Would things break? Is it unnecessary bloat? |
I'm certainly open to this. It would be ideal if @domenic could attend. The committee expressed interest in us making a joint presentation. The committee wants to make sure that we cover the full spectrum of use cases with the types in proposed for language inclusion. Given the chrome teams concerns, it would be good if they were represented. JH
|
I'm going to pull this PR in, as I think it represents the ideal form of the Rx-style Observable API. As far as the promise-based observable design goes, apologies for not getting involved in the discussion sooner. I originally experimented with a promise-based observable design a few months ago here: https://github.com/zenparsing/zen-observable/tree/master/white-board/cancel-token My interest in the design fizzled out for a couple of reasons:
My hunch is that we'll eventually come back around to this more traditional API, in part because cancel tokens seem to want a synchronous listener registration API. let subscription = cancelToken.subscribe(() => {
// Do cleanup and stuff
});
subscription.unsubscribe(); I'm happy to further explore the promise-based API though. |
The easiest way to see the changes are just to look at the README diff.
Add a "closed" getter to Subscription and SubscriptionObserver
Combinators frequently need this information in order to correctly deal with synchronous dispatch use cases.
Remove the "forEach" method
The
forEach
method is not fundamental; it can be implemented on top of the API just like "filter" and "map". Also, it doesn't help with regard to a primary use case, DOM events, where we want the subscription object:Add an overload to "subscribe" which accepts 3 function arguments
This makes observing a DOM event stream, for instance, very ergonomic: