tc39 / proposal-observable

Observables for ECMAScript
https://tc39.github.io/proposal-observable/
3.07k stars 90 forks source link

Discussion: Subscribe returns a cancellation function #48

Closed zenparsing closed 9 years ago

zenparsing commented 9 years ago

In the current version, subscribe returns a cancellation function. Previously, subscribe returned a "subscription" object with an "unsubscribe" method.

Rationale:

  1. Since working on this project, I've typed in "subscription.unsubscribe" many, many times. It's ugly and a real pain.
  2. We currently haven't identified any essential capabilities that need to be returned from "subscribe" other than the cancellation function. It's nice to avoid useless wrapper objects.
  3. Any additional capabilities returned from "subscribe" are probably going to be POLA-problematic.
  4. The return types of "subscribe" and the subscriber function align nicely.

Regarding 4:

We frequently want to "chain" observables like this:

function x(observable) {
    return new Observable(observer => observable.subscribe({
        // ... whatever
    });
}

If "subscribe" returns something other than a () => void function, then we have to bake-in some messy overloading in order to make that chaining nice and pleasant. RxJS makes use of overloading in several places (also "subscribe") which I would like to avoid.

Thoughts?

benjamingr commented 9 years ago

I just remembered we've had a similar debate on whether or not subscribe should just be a function and it was decided against for explicitness/readability reasons. Another data point.

benlesh commented 9 years ago

The truth is it's pretty rare for and end user of Observable to manually create an observable. It is very common to subscribe to observables though. The subscriber function being allowed to return a function or and object isn't as important as what they'll be getting back when they subscribe.

The explicit nature of having to call x.unsubscribe() on an object is cleaner. An object with an isUnsubscribed property makes sense. A function with the same makes less sense.

zenparsing commented 9 years ago

Here's a comparison of "zip" implemented with subscribe returning a cancellation function and with it returning a "subscription" object. I hope I have the semantics right.

https://gist.github.com/zenparsing/9ab5a930fdf43f47de77

It looks equivalent to me, provided that the subscription object has something like isUnsubscribed.

One point that I'd like to make clear is that from my perspective, subscribe returns a capability, rather than a state-bearing object. This is how I've always viewed the situation, and is similar to how resolve and reject express capabilities in the Promise API.

As the zip example demonstrates, it's always possible (and quite easy) to create state-bearing "subscription-like" objects by using the right kind of observer. You can even do it in a completely general way if you really want to:

function makeSubscription(observable, observer) {
    let subscriber = {};
    new Observable(s => observable.subscribe(subscriber = s)).subscribe(observer);
    return {
        unsubscribe() { subscriber.cancel() },
        isUnsubscribed() { return subscriber.closed },
    };
}

One cool thing here is that returning a cancellation capability allows us to avoid bikeshedding and controversy about what a subscription type should look like (it looks like there's already a desire to put things like "add" and "isSubscribed" on it).

I appreciate the strong objection to subscribe returning a cancellation function. I would just like to see evidence that the "subscription" object is worth the icky overloading it's going to necessitate, and I haven't seen it yet.

benlesh commented 9 years ago

icky overloading

I'm not sure I would consider allowing an additional return type from the subscriber function "icky overloading". You're already allowing function and void. Besides, isn't that an implementation detail for Observable? I don't see how it would impact the user in a negative way, this is already a practice that's been in existence for years with this very type.

I think it's worth it to be explicit at the very least. We could have implemented everything in JavaScript as functions. Promises, for example, could have just been the then() function. But that would have made it less readable: a.then(fn1).then(fn2) as opposed to a(fn1)(fn2). The latter is more terse, for sure, but it's not really apparent what you're doing. Likewise: a.unsubscribe() vs a()... one is clearly more readable. What if I want to search my code for everywhere I've unsubscribed from something? If we return functions, that will be impossible unless I come up with my own convention and follow it. On a large team, that's really doubtful.

The pros and cons as I see them make this pretty clear.

Plain Function

Pros

Cons

Object

Pros

Cons:

zenparsing commented 9 years ago

I don't see how it would impact the user in a negative way, this is already a practice that's been in existence for years with this very type.

True enough, and it may be that I'm trying to make the "insides" too pretty.

I would feel better about conceding if I could see a demonstration of why subscription objects are superior to cancellation capabilities.

Regarding the cons of cancellation functions:

harder to read because it lacks an explicit call

Nah. You just have to name the variable something reasonable, like, um, cancel or unsubscribe.

Doesn't convey the idea of "state" as clearly as an object.

I wouldn't want the cancellation capability to convey state. In my mind it only represents a capability to cancel the subscription, in the same sense that resolve only represents the capability to resolve a promise. Now, it may be that it's really inconvenient to not have the subscription "state" represented in the return value of subscribe. That's what I want to see. If that can't be demonstrated, then the subscription object is just an (arguably) useless noun wrapping the cancellation capability.

Plus, "unsubscribe" is just really uncomfortable for me to type. I've typed it like a zillion times since starting this work and I hate it. : )

benlesh commented 9 years ago

Nah. You just have to name the variable something reasonable, like, um, cancel or unsubscribe.

That's not a solution. That's a workaround.

There are various ways that maintaining some state on the object would be helpful. Usually it's for operator development, but often it's for things like composite subscriptions.

new Observable(observer => {
   const subscription = new Subscription();
   for(let source of sources) {
     subscription.add(source.subscribe(observer));
   }
   return subscription;
});

as opposed to:

new Observable(observer => {
  const subscriptions = [];
  for(let source of sources) {
    subscriptions.push(source.subscribe(observer));
  };
  return () => subscriptions.forEach(sub => sub());
});

... you might be able to do something with closures and reduce, but I doubt it would perform as well as the array.

benlesh commented 9 years ago

Or how about a switch(n) operator, where you switch to the most recent n observables provided by Observable<Observable<T>>? Or a mergeAll(concurrecyLimit) operator where you merge all Observables arriving from Observable<Observable<T>>, but you don't allow subscription for more than concurrencyLimit observables at a time?

If you have an add and remove function on a Subscription object (sub-classed or otherwise), it's going to be much, much easier to deal with.

RangerMauve commented 9 years ago

@zenparsing I think I'd agree with the statement that it's inconvenient to not know whether a subscription has been unsubscribed or not. I could see it being passed around everywhere, and it'd be a useful bit of data that would be similar to know whether a stream has been closed. Storing this data as a property of a function wouldn't be very convenient since it isn't a common pattern, so an object might be best for that.

benlesh commented 9 years ago

Let's say we have a class Subscription like so:

class Subscription {
  constructor(action) {
    this.action = action;
  }

  unsubscribe() {
    if(!this.isUnsubscribed && this.action) {
      this.isUnsubscribed = true;
      this.action();
    }
  }
}

I could subclass that into a CompositeSubscription very easily since it doesn't already behave like one:

class CompositeSubscription extends Subscription {
  get length() {
    return this.subscriptions.length;
  }

  constructor() {
    super();
    this.subscriptions = [];
  }

  add(...subs) {
    const subscriptions = this.subscriptions;
    subscriptions.push.apply(subscriptions, ...subs);
  }

  remove(sub) {
    const subscriptions = this.subscriptions;
    const i = subscriptions.indexOf(sub);
    if(i !== -1) {
      subscriptions.splice(i, 1);
    }
  }

  action() {
    const subscriptions = this.subscriptions;
    while(subscriptions.length > 0) {
      subscriptions.shift().unsubscribe();
    }
  }
}

Now everywhere I need a composite subscription (zip, combineLatest, withLatestFrom, merge, flatMap, forkJoin and many, many more), I have one, and I don't have to reinvent what amounts to a disposal combinator each time. I have it. I was able to sub class it.

benlesh commented 9 years ago

I realize you're probably trying to avoid adding an additional type to the proposal. But this is a fairly critical piece to Observable. Can you do it with plain functions? Of course you can, in a language with closures you can literally do anything with a function that you could do with an object. The question is "should you"? And I think in this case the answer is no.

zenparsing commented 9 years ago

@blesh thanks for providing some additional arguments. It will probably be a couple of days before I get back to this but I'd like to continue the discussion.

RangerMauve commented 9 years ago

I really want to begin using using the reference implementation for a project I'm starting, but this seems like a really breaking change so I'm gonna bump in the hopes of starting more discussion. :D

Macil commented 9 years ago

@RangerMauve

I think I'd agree with the statement that it's inconvenient to not know whether a subscription has been unsubscribed or not. I could see it being passed around everywhere, and it'd be a useful bit of data that would be similar to know whether a stream has been closed.

Application code passing the unsubscription capability around strikes me as pretty unidiomatic and breaking the principle of least authority, like leaking the resolve or reject functions from a Promise constructor. Packing extra methods or properties on an object with the unsubscription capability could lead people to designs that do this more.

@blesh CompositeSubscription seems like Kefir.Pool or Bacon.Bus, except that it entangles their functionality with subscription. A Kefir.Pool allows observables to be added to it, but it doesn't subscribe to them unless someone is subscribed to the pool itself. (Another benefit with a Pool not being tied to subscribe is that you'd be able to call forEach on it and pass it around to places expecting an observable.)

This and #61 make me a bit concerned that the existence of a subscription object encourages people to entangle functionality that could be better left separate with the unsubscription capability.

benlesh commented 9 years ago

@agentme, this paradigm of a subscription object already exists in a half dozen or more Rx implementations in various languages, some of which have existed for 6 years or more, and none of what you're concerned about has proved to be common place or even true.

RangerMauve commented 9 years ago

@blesh Which parts of what @AgentME said are you referring to specifically? I'm 100% sure that leaking resolve and reject from a promise constructor is considered bad practice by the community as a whole. Are you saying that the comments about Kefir.Pool are incorrect? Or is it just that you find the relationship between Kefir.Pool and the idea of a subscription object irrelevant?

@AgentME I was thinking more in a case where you have something orchestrating and keeping track of a bunch of observables and subscriptions. I could it being useful for making sure you don't try to unsubscribe something twice or to just have a status. One particular use I was thinking of for Observables is to facilitate something like NoFlo, but using Observables as the edges between nodes. So far I'm not 100% sure what the consequences of attempting to unsubscribe after an observable has already been unsubscribed are, so having a way to make sure you can check first would be useful.

benjamingr commented 9 years ago

I'm 100% sure that leaking resolve and reject from a promise constructor is considered bad practice by the community as a whole.

While I think leaking resolve and reject from the promise constructor is considered bad practice in most cases. Two things to consider are:

The observable analogy is leaking an observer though - not a subscription, which exists in most code bases I've read that use Rx (through subjects). Although that might go away once native APIs start returning observables or automatic observable conversion methods start being broadly used enough.

RangerMauve commented 9 years ago

@benjamingr The only really legitimate use that I've seen for doing that with promises is for creating deffered promises. But I guess there are likely others. Though I think that line of discussion is probably tangential to the actual discussion.

I agree that leaking the observer is a more proper analogy. I'm fully in favor of passing subscriptions around.

zenparsing commented 9 years ago

@blesh I might argue that adding .add, .remove, or merging the subscriber and the subscription types are just the kind of overreaches @AgentME is referring to : )

@RangerMauve The cancellation function is idempotent. It has no effect when called multiple times.

@RangerMauve Thanks for trying to push this forward. I've been on holiday and reflecting quietly on this issue. Let me try to state my current position.

If subscribe returns a subscription object instead of a cancellation function, then we must introduce a new Subscription type to the design. The Subscription type is not essential; it's entirely possible to construct the same type on top of cancellation functions using the approach here:

https://github.com/zenparsing/es-observable/issues/48#issuecomment-143775768

So the existence of a Subscription type must be justified on other grounds:

  1. Does the Subscription type address a backward-compatibility requirement for interop among existing libraries?
  2. Does the Subscription type offer a significant usability advantage?
  3. Does the Subscription type enable interesting use cases for Observable subclasses?

The answer to 1 must be "no", since there is currently no interop standard between Observable libraries.

I believe that @blesh has been asserting 2 (that it offers usability advantages), but in all of the combinator cases I've looked at so far, including zip and switch(n), I haven't seen any usability advantage when comparing the approaches side-by-side.

For the switch(n) case, you just need an array or a linked list of cancellation functions: https://gist.github.com/zenparsing/0d18579ed0c76ab70db7

I'm completely open to reversing my position based on a concrete example showing how subscription objects actually help.

That leaves 3. It may be that an Observable subclass might want to return a Subscription subtype which has additional capabilities attached to it. The counter argument is that the subtype can always introduce a different "subscribe-like" method that returns anything it wants to. Still, if we could come up with convincing concrete use-cases for Subscription subclassing that might be helpful.

zenparsing commented 9 years ago

@blesh

Another design option:

Rename the subscribe of this specification with observe. This would allow libraries to define their own semantics for subscribe.

benjamingr commented 9 years ago

@zenparsing total bikeshed, but this will likely cause issues with the TC with the Object.observe proposal.

zenparsing commented 9 years ago

@benjamingr AFAIK Object.observe is dead. @jhusain confirm?

benjamingr commented 9 years ago

@arv ?

benlesh commented 9 years ago

@blesh I might argue that adding .add, .remove, or merging the subscriber and the subscription types are just the kind of overreaches @AgentME is referring to : )

I've thought about it, and that whole argument is silly. I can "overreach" with a function just as easily as an Object. The whole argument is moot. Give me a reference to a function and I'll hand it to your mother in global scope, just to see what she'll do with it... that's after I put seven new functions on it and wrap it in my own function that completely alters it's behavior and closes over a giant array of created but unattached DOM elements and sets up event handlers on them all for no reason. Also, the seventh call to it will define undefined as 2.

Adding an add or remove to a subscription is plainly NOT "overreaching". Every Rx implementation has the idea of "composite subscriptions". The nice thing about an interface for a Subscription is that you have the ability to create different types/combinators for subscriptions and unsubscriptions. Serial subscriptions, empty subscriptions, etc etc. But we've been over this, and I don't feel like it's falling on ears that want to hear it.

I'm completely open to reversing my position based on a concrete example showing how subscription objects actually help.

You know this is impossible. You can do anything with a function that you can do with an object. It doesn't make it right though. The best "concrete" example is as simple as: flibbertyFudge() vs flibbertyFudge.unsubscribe() ... guess which one of those is unsubscribing. Now search through your code base of 156 files maintained by 5 remote developers and find all unsubscriptions in your code. Cancellation functions? Too bad, you can't unless you've managed to keep everyone naming their functions the same way.

I've provided several arguments previously, which have been mostly met with the obvious "you can do anything with a function you can do with an object" sort of counter-arguments, as well as some hand-wringing about what people might do with these classes/objects rather than functions, even though a few comments above it was established they're equally powerful/extensible (if one of them is less explicit).

The facts as I see them are:

Rename the subscribe of this specification with observe. This would allow libraries to define their own semantics for subscribe.

That would solve the bulk of my issues with this. Not that I'm not willing to go through RxJS 5 and refactor to a whole bunch of crazy stuff to return cancellation functions, I am. I'll hate it, but I'll do it. I just strongly disagree with this one part of the design.

I also think that long-long-run, if Cancellable Promises make it, forEach returning a cancellable promise is all you'll need.

benlesh commented 9 years ago

I feel like I've made every argument I can to the best of my meager ability, honestly. I'm content with my effort, I'm happy with the discussion, I'm just disappointed in the outcome. If I'm the only reason this issue is open you can close it.

benjamingr commented 9 years ago

For what it's worth I'm with @blesh on this, but it might just be my experience fixating my point of view.

I think just because we can make things functions because the language can do it doesn't mean we should. A subscription isn't just a cancellation function - it's something you can unsubscribe from. Just like an array isn't also a function for its map or Symbol.iterator and a RegExp isn't its exec and a promise isn't its then and a string is not its charAt.

benjamingr commented 9 years ago

Wow, so many typos thanks iPhone. Just another note: I'm also in favor of closing this if it's a big issue because I'd like to see this spec move forward already :)

RangerMauve commented 9 years ago

I agree with the sentiments that it feels more right to have it be an object representing the subscription, with the unsubscribe being an explicit method. Personally, I don't really have any comments on adding additional methods to it, though I could see the merit in allowing it.

trxcllnt commented 9 years ago

Side-note: it seems if we'd kept the original Disposable name for, "the thing that cleans up after all computations performed from each call to Observable subscribe," we would have avoided a lot of confusion. Seems like people have mistaken/will mistake the Subscription for an Observer (or worse, an Observable).

dead-claudia commented 9 years ago

Since subscribe returns an unsubscribe function at the moment, what alternative would there be to unsubscribe from an observable? For what it's worth, most current observables in JS and other OO languages rely on OO and tokens for managing subscriptions/etc. (including Node and the DOM), which is admittedly more complicated to deal with. It feels simpler to deal with a function in this context than an object of some sort, even if it winds up being a Symbol. I'm reusing an example from above here.

(with an object)

const o = new Observable(observer => {
  const subscription = new Subscription()
  for (let source of sources) {
    subscription.add(source.subscribe(observer))
  }
  return subscription
})

const subscriber = observable.subscribe({
  next(value) {
    // ...

    if (ready) subscriber.unsubscribe()
  }
})

(with a function)

const o = new Observable(observer => {
  const subscriptions = Array.from(sources)
    .map(source => source.subscribe(observer))

  return () => subscriptions.forEach(sub => sub())
})

const unsubscribe = observable.subscribe({
  next(value) {
    // ...

    if (ready) unsubscribe()
  }
})

@trxcllnt I agree that the function argument seems misnamed. Renaming that alone would fix this problem.

benlesh commented 9 years ago

It feels simpler to deal with a function in this context

Especially when you write one example to look more terse. That object version could be a lot cleaner.

(with an object, where the add impl returns this)

const o = new Observable(observer => {
  return sources.reduce((sub, source) => sub.add(source.subscribe(observer)), new Subscription());
})
zenparsing commented 9 years ago

@blesh One of the problems here is that we aren't anywhere near proposing something like Subscription.prototype.add.

There's actually another point of contention for subscribe other than the return type. Rx wants subscribe to take either 3 functions or an observer, whereas (I believe) such overloading was poorly received a couple of TC39's ago. I'm warming to the idea that it might be better to rename this thing observe and let Rx do whatever it wants to with subscribe.

(Provided that Object.observe is not a blocker, of course.)

let cancel = observable.observe({
    next(v) { /* Whatever */ },
});

And Rx can just do this:

RxObservable.prototype.observe = function(observer) {
    let s = this.subscribe(observer);
    return _=> { s.unsubscribe() };
};

And be done with it.

benlesh commented 9 years ago

@blesh One of the problems here is that we aren't anywhere near proposing something like Subscription.prototype.add.

I don't think anyone ever said you were. I was just saying you're proposing something makes adding add or remove more difficult less straightforward, and the contract less explicit.

Rx wants subscribe to take either 3 functions or an observer

That's a library implementation. A lot of the reason the "3 functions" overload exists is it's just more ergonomic in ES5.

benlesh commented 9 years ago

There is a weird irony to having the input to subscribe be an object where every handler and function is explicitly named, and then returning just a function as if explicitness never mattered.

var farglewarrble  = observable.subscribe({
  next(x) { doAThing(x); },
  error(err) { doAnError(err); },
  complete() { done() }
});

farglewarrble(); // unsubscribed!
benlesh commented 9 years ago

The observe thing helps me with the Rx impl, no doubt. So if that's compromise, then I'm in. But in my heart I want you to return Subscription objects from a subscribe method.

dead-claudia commented 9 years ago

@blesh APIs can't do a thing about bad variable names. Just like there's nothing stopping you from doing this:

var iterable = new Date();

@all

Maybe, implementing it as a single-method object would be better, as the method could be made a prototype method. This would reduce performance overhead in that the same function can be reused instead of generating a function each time? The constructor could take either a returned object singleton or function (overloaded for simplicity of API use).

On Wed, Oct 7, 2015, 17:46 Ben Lesh notifications@github.com wrote:

The observe thing helps me with the Rx impl, no doubt. So if that's compromise, then I'm in. But in my heart I want you to return Subscription objects from a subscribe method.

— Reply to this email directly or view it on GitHub https://github.com/zenparsing/es-observable/issues/48#issuecomment-146342408 .

benlesh commented 9 years ago

@impinball you completely miss the point:

iterable.getMilliseconds()

Guess what that variable has in it?

Not to mention, naming it something intentionally confusing is not the same as simply giving it a stupid name, which is much more likely. The hardest challenge in programming or whatever.

benjamingr commented 9 years ago

Maybe, implementing it as a single-method object would be better, as the method could be made a prototype method.

This is what me and @blesh want, but others disagree and we've both stated we're willing to let it go in order to see the proposal progress.

his would reduce performance overhead in that the same function can be reused instead of generating a function each time?

Not really a convincing argument. We've been over it before, you're welcome to read above.

The constructor could take either a returned object singleton or function (overloaded for simplicity of API use).

I don't think singleton is the correct term here, but yes.

Overall, it's true that there is nothing stopping you from doing:

var iterable = new Date();

But, you can't do:

var iterable = "Hello"; // or new Date(), or /someregex/g, or [1,2,3]
iterable();

And expect a function invocation on it to just work. All the APIs in ES do this. It helps clarity, typing and refactoring.

RangerMauve commented 9 years ago

I'm not too sure about the argument for re-using a function for unsubscribing in the prototype. Are you going to be keeping some sort of private variables on the subscription object to facilitate that?

dead-claudia commented 9 years ago

@RangerMauve Engines could keep private variables, and they already do.

dead-claudia commented 9 years ago

@blesh

My point was that a bad variable name for a returned value has nothing to do with the API, whether intentionally or not.

dead-claudia commented 9 years ago

@benjamingr

Maybe, implementing it as a single-method object would be better, as the method could be made a prototype method.

This is what me and @blesh want, but others disagree and we've both stated we're willing to let it go in order to see the proposal progress.

his would reduce performance overhead in that the same function can be reused instead of generating a function each time?

Not really a convincing argument. We've been over it before, you're welcome to read above.

True, since engines are already optimizing closures very well at this point.

The constructor could take either a returned object singleton or function (overloaded for simplicity of API use).

I don't think singleton is the correct term here, but yes.

Yeah...you got what I meant. (I know it wasn't the right term...I wasn't thinking)

Overall, it's true that there is nothing stopping you from doing:

var iterable = new Date();

But, you can't do:

var iterable = "Hello"; // or new Date(), or /someregex/g, or [1,2,3]
iterable();

And expect a function invocation on it to just work. All the APIs in ES do this. It helps clarity, typing and refactoring.

See my response above to @blesh. It wasn't about types as much as that APIs can't protect against generally bad variable names.

RangerMauve commented 9 years ago

@impinball I wasn't questioning whether it was possible, mostly just to confirm my suspicions. Also , I guess that user land implementations could use symbols as a sort of pseudo-private variable without any worries. Though I would assume that it'd be easier to just use closures.

benlesh commented 9 years ago

My point was that a bad variable name for a returned value has nothing to do with the API, whether intentionally or not.

And my point was that bad variable names don't matter as much if you have methods to call, because they have set names and read more explicitly. Your argument was hand-waving.

True, since engines are already optimizing closures very well at this point.

The work done trying to make RxJS performant has empirically proved this assumption false, this was work done along side @trxcllnt and @jhusain and was carefully measured. We tried a few approaches, and anything that introduced closure on common calls slowed the library down vs other methods. As of just 3-4 months ago, closures are much, much slower than using methods on objects or other methods to carry state into a function call. Likewise bind() is very slow. I doubt anything has changed in that time period. This might improve in the future. But it'll improve at the speed of all browsers and engines in the industry, which really isn't all that fast.

To get around the slowness of closures at the moment, if I had to return a cancellation function, I would have to use a trick @benjamingr actually taught me in another issue on this repository that uses named functions to ferry state into the function body. It works, but it's not very idiomatic, and looks pretty hacky.

RangerMauve commented 9 years ago

@blesh Does that mean you'd want the current way for defining observables to be changed as well?

dead-claudia commented 9 years ago

At this point, since I have no real investment in either option (I like and dislike both for varying reasons), I'm going to step out of this debate. It's awkward arguing for and against both sides. :wink:

benlesh commented 9 years ago

Does that mean you'd want the current way for defining observables to be changed as well?

Not really. I mean, I think it would be ideal if you could return a subscription from an Observable's subscriber function, but it's not a deal breaker as it doesn't change the semantics of consuming the Observable like the return value of subscribe does.

In RxJS 5, we allow both, but honestly, it's not really that necessary, as in that implementation, the observer passed into the subscriber function is actually a "Subscriber", which is both the "observer" and the "composite subscription". This was done because the relationship between observer and subscription is always 1:1, and they need to know about each other's state (isUnsubscribed for example) We could easily evolve either way.

benlesh commented 9 years ago

I'm back... and surprise: I still don't like the cancellation function. There is nothing else in JavaScript that you can cancel in a way you couldn't grep for. clearTimeout, clearInterval for example... or in the DOM: cancalAnimationFrame, removeEventListener, etc. You can grep to see where things have been cancelled or torn down.

The counter argument to this is always "why would you do that"? And in short, I do this all the time. Especially when reviewing code, the first thing I look for is to see if people are disposing of their subscriptions, because it's the most common mistake people make. To do that now I just CTRL+F and search "dispose" and the results are pretty clear.

Likewise, if I see some random variable foo and someone is calling foo.dispose() on it in RxJS 4, it's really clear what foo is... and people will name their subscriptions stupid things... foo, sub, clicks etc. (sub doesn't sound stupid until you see a Subject named sub in an adjacent file). An explicit method call to unsubscribe or dispose or whatever you like helps clarify code and what's hiding underneath poorly named variables.

While I'd obviously prefer that this spec align with what I'm doing and return an object, honestly, I wouldn't even care if it returned a token instead and there was a cancelSubscription function you had to call with that token. As long as I can search for it in my code and it's easy to spot.

Anything with a syntactically explicit means of tearing down the observable subscription would be preferred over the cancellation function, as far as I'm concerned.

If that's just simply not going to happen because of worries about adding additional types to the spec, then changing subscribe to observe so as not to collide with existing library's subscribe methods would be my second choice.

RangerMauve commented 9 years ago

I support having the return be an object that represents the subscription which has an unsubscribe() method. It seems that more than one current implementation of observables that's out right now would be interested in having addition data associated with the return in the form of an object, and I think that unsubscribe() is a nice, and explicit compliment to subscribe().

benjamingr commented 9 years ago

I'm just going to shim calling the return value with .dispose if the design stays this way. Again, not a blocker for me but I'm definitely in favor of an object return.

RangerMauve commented 9 years ago

Hmm, good point about referring to it as dispose, bacon and rx refer to it as dispose. I retract my suggestion of calling it unsubscribe