Skip to content
This repository has been archived by the owner on Apr 20, 2018. It is now read-only.

Subscription to an observable should probably be async #597

Closed
benlesh opened this issue Feb 22, 2015 · 7 comments
Closed

Subscription to an observable should probably be async #597

benlesh opened this issue Feb 22, 2015 · 7 comments

Comments

@benlesh
Copy link
Contributor

benlesh commented Feb 22, 2015

What I'm talking about: the actual .subscribe or .forEach call.

What I'm not talking about: side effects that occur after or during that call.

The problem

As it currently stands, if I were to give you an arbitrary Observable instance, you would have no way of knowing how a call to subscribe might effect the surrounding code block. This is because the actual call to subscribe itself is synchronous, as are the calls to onNext within it. However, those onNext calls may, or may not, be wrapped in asynchronous code. So you're not guaranteed that side effects will not affect your currently executing code block.

For example, given these two sources:

var source1 = Observable.fromArray([1,2,3]);
// to mimic some async source
var source2 = Observable.create(o => {
  setTimeout(() => {
    o.onNext(1);
  });
  setTimeout(() => {
    o.onNext(2);
  });
  setTimeout(() => {
    o.onNext(3);
  });
});

The following code will behave differently:

function foo(source) {
  console.log('start');
  source.forEach(function(x) {
    console.log(x);
  });
  console.log('end');
}

foo(source1);
/*
start
1
2
3
end
*/

foo(source2);
/*
start
end
1
2
3
*/

In the example, dealing with console.log is obviously fairly safe and innocuous, but developers might be doing other things such as incrementing a value or what have you. Things things might pass all tests and even run in production just fine because they're relying on the observable behavior being synchronous or asynchronous, but one day the observable being passed in switches, or worse, alternates between the two, and the developer is face to face with a heisenbug.

Proposed Solution

Simply make the act of subscription to Observables asynchronous. Essentially, this would be a subscribeOn(Scheduler.timeout) by default.

If someone wants the immediate behavior for subscription, they can still do it explicitly with subscribeOn.

Risks

This is clearly a breaking change. The only upside being that it should only break code that is probably poorly written to begin with.

Other considerations

It appears, to me at least, that @jhusain's async generator proposal for ES7 is going to make subscription behave asynchronously as above. I'm not entirely sure about that so I've submitted an issue there to ask, and hopefully strike up public conversation on the matter. I think (but I'm not sure) other libraries such as Bacon and Most.js have this behavior. It seems like something that should be standardized (ala Promises and A+).

@benlesh
Copy link
Contributor Author

benlesh commented Feb 22, 2015

EDIT: I changed the example above to be some async source. The first example wasn't quite what I meant.

@IgorMinar
Copy link

Looks like a reasonable way to fix a thorny issue without introducing performance penalty for hot operations.

@Igorbek
Copy link
Contributor

Igorbek commented Feb 23, 2015

Actually, you are talking about "async observables", which is a new world duality of "sync observables" (which the IObservable is). The Rx.NET is going to have IAsyncObservable (with SubscribeAsync method), IAsyncObserver (with On*Async methods). The Rx.NET Team confirmed it 4 yers ago, on January 11, 2011 =).

@benlesh
Copy link
Contributor Author

benlesh commented Feb 23, 2015

Yeah. Async has different implications in JavaScript though. It's not "concurrent" async, it's more "push this to the next tick" sort of async.

@briancavalier
Copy link

+1 The important thing is removing non-determinism around whether or not side-effects may occur in the current call stack. It protects consistency in the current stack much like promise.then does.

@urmastalimaa
Copy link
Contributor

+1 for the reasons that @briancavalier pointed out

@benlesh
Copy link
Contributor Author

benlesh commented Apr 17, 2015

I've actually changed my mind on this a bit.

The thing is, reactive streams programming is all about controlling side effects. The only place you should be creating side effects is in your subscription. RxJS has to default to this "sync if it can be" sort of behavior for performance reasons, otherwise every operator in your chain would incur another frame or tick. merge and flatMap exacerbate this.

RxJS gives you the option of using .subscribeOn(scheduler) if you need to program defensively around this. And that's probably fine.

@benlesh benlesh closed this as completed Apr 17, 2015
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants