I know you're going to hate me for this, but I'd like to re-litigate the concerns I had on blink-dev:
const ob = new Observable((subscriber) => {
subscriber.next(1);
setTimeout(() => {
subscriber.next(2);
}, 1000);
setTimeout(() => {
subscriber.next(3);
subscriber.complete();
}, 2000);
});
ob.toArray().then((vals) => {
console.log(vals); // [1, 2, 3]
});
ob.toArray().then((vals) => {
console.log(vals); // [2, 3]
});
setTimeout(() => {
ob.toArray().then((vals) => {
console.log(vals); // [3]
});
}, 1500);
The model above appears to be:
- The
SubscribeCallback is called synchronously the first time something subscribes to the observable.
- Additional subscriptions do not result in
SubscribeCallback being called again
- Additional subscriptions may have missed values already emitted.
Even though this different to general observable behaviour, it makes sense to me. The fact that two synchronous subscriptions get different values is initially surprising, but it falls out of the synchronous nature of observables, and that's an essential part of the design.
However:
setTimeout(() => {
ob.toArray().then((vals) => {
console.log(vals); // [1, 2, 3]
});
}, 2500);
At this point, the behaviour seems to flip. The SubscribeCallback will be called again, since it's no longer active.
This seems internally inconsistent, and unreliable from the outside. In observable terms, it's both 'hot' and 'cold'. IMO one of the following patterns should be chosen:
- If values were emitted before you subscribed, tough luck, you missed them.
- The
SubscribeCallback will be called again so you get all values from the start.
…rather than it being 'it depends'. Option 1 seems the best, meaning in the above toArray() example, the fulfilled value should be [].
The current behaviour makes other things weird:
const ob = Observable.from([1, 2, 3]);
ob.toArray().then((vals) => {
console.log(vals); // [1, 2, 3]
});
ob.toArray().then((vals) => {
console.log(vals); // [1, 2, 3]
});
vs
const ob = Observable.from([1, 2, 3].values() /* ⬅️⬅️ */);
ob.toArray().then((vals) => {
console.log(vals); // [1, 2, 3]
});
ob.toArray().then((vals) => {
console.log(vals); // [] ⬅️⬅️
});
The first example exhibits the "get all the values from the start" behaviour. The second example tries to do this, but because the input is an iterator which doesn't support 'restarting', you get a seemingly inconsistent result.
When I raised this on blink-dev, folks said that my surprise at this was down to my inexperience with observables, and I accepted that. But, I've since talked to folks experienced in observables, and they seem surprised by this behaviour too.
Given that there's a single engine shipping this, and the above behaviour doesn't really come up when using element.when(…), I hope this can be changed without significant breakage.
@esprehn @bakkot @keithamus @acutmore what do you think? Am I way off here?
I know you're going to hate me for this, but I'd like to re-litigate the concerns I had on blink-dev:
The model above appears to be:
SubscribeCallbackis called synchronously the first time something subscribes to the observable.SubscribeCallbackbeing called againEven though this different to general observable behaviour, it makes sense to me. The fact that two synchronous subscriptions get different values is initially surprising, but it falls out of the synchronous nature of observables, and that's an essential part of the design.
However:
At this point, the behaviour seems to flip. The
SubscribeCallbackwill be called again, since it's no longer active.This seems internally inconsistent, and unreliable from the outside. In observable terms, it's both 'hot' and 'cold'. IMO one of the following patterns should be chosen:
SubscribeCallbackwill be called again so you get all values from the start.…rather than it being 'it depends'. Option 1 seems the best, meaning in the above
toArray()example, the fulfilled value should be[].The current behaviour makes other things weird:
vs
The first example exhibits the "get all the values from the start" behaviour. The second example tries to do this, but because the input is an iterator which doesn't support 'restarting', you get a seemingly inconsistent result.
When I raised this on blink-dev, folks said that my surprise at this was down to my inexperience with observables, and I accepted that. But, I've since talked to folks experienced in observables, and they seem surprised by this behaviour too.
Given that there's a single engine shipping this, and the above behaviour doesn't really come up when using
element.when(…), I hope this can be changed without significant breakage.@esprehn @bakkot @keithamus @acutmore what do you think? Am I way off here?