13

How do I know when an Observable producer is async or sync?

An sync example:

Observable.of([1, 2, 3])

another async example (ngrx Store, see here)

this.store.take(1);

And now an obvious async example:

this.http.get(restUrl)

I fully understand how this works, and that some Observables can be sync and others Async. What I don't understand is how i can tell the difference in advance? Is there for example an obvious interface on the producer that tells me that it will be async?

tl;dr

The main reason this question has come up is because of the answer from the link above (here). @Sasxa has (correctly) answered that we can use a synchronous call to get the latest value from the ngrx store:

function getState(store: Store<State>): State {
    let state: State;

    store.take(1).subscribe(s => state = s);

    return state;
}

However knowing that observables are usually asynchronous, I saw this and immediately thought RACE CONDITION! the method could return an undefined value before subscribe has called back the function.

I've debugged through the internals of Store and Observable and this example is indeed synchronous, but how should I have known that? For those that know ngrx, the select method of store (used to get the latest values) is asynchronous, without a doubt, as this is what gives us the reactive gui, which is why I came to the assumption I did.

It means that I cannot refactor the code above as follows:

function getLatest(observable: Observable): any {
    let obj: any;

    observable.take(1).subscribe(latest => obj = latest);

    return obj;
}

I could call this with any Observable and it would work for synchronous producers - it may even work SOME OF THE TIME for async producers, but this is without a doubt a race condition if an async observable is passed in.

Forge_7
  • 1,839
  • 2
  • 20
  • 19
  • I don't know more generally, but the store uses a BehaviouralSubject, which is always sync because it's pre-loaded with a value (initial state, in the ngrx example) and always provides you with the 'latest' value on subscription. – Joe Nov 06 '17 at 11:16
  • @Sami - apparently not for ngrx/store v2.x. If you read Sasxa answer in the link, you'll see the code that made me create this question in the first place. – Forge_7 Nov 06 '17 at 15:43
  • Joe - ah that's interesting. I noticed BehaviouralSubject when debugging through the code. Does seem that ngrx is quite a specific example of sync behaviour. Thanks! – Forge_7 Nov 06 '17 at 15:45
  • @Forge_7 Yes, I see, sloppy of me. I will delete the comment, since it's not helpful. – Sami Hult Nov 06 '17 at 15:48

2 Answers2

8

It is possible to determine if an observable is asynchronous for sure if was directly scheduled with asynchronous scheduler (Scheduler.async or Scheduler.asap), it's exposed as foo$.scheduler:

let schedulers = [null, Scheduler.asap];
let randomScheduler = schedulers[~~(Math.random()*2)]
let foo$ = Observable.of('foo', randomScheduler);

This information becomes even less available when foo$ is processed further, e.g. chained with other operators.

And it's impossible to determine if values will be produced synchronously (on same tick) or asynchronously because this depends on observable internals:

let foo$ = new Observable(observer => {
  if (~~(Math.random()*2))
    setTimeout(() => observer.next('foo'));
  else
    observer.next('foo');
});

TL;DR: it's impossible to know this for sure.

Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • Thanks @estus, that's the conclusion I'd come to myself having googled about for a day on the subject. Accepted Fan Cheung's answer as he just beat you to it, sorry! – Forge_7 Nov 06 '17 at 15:47
  • 'This information is lost when foo$ is processed further, e.g. chained with other operators.' - not really, look at an operaotr chain in console - `operator.source.operator.source.scheduler`. So a recursive method could pull the scheduler out from under a pile of operators. – Richard Matsen Nov 06 '17 at 20:08
  • @RichardMatsen That's true. I guess 'lost' is not a precise word here. Traversing operator chain relies on internal properties and is impractical. Also, IIRC `scheduler` is private too because there's no reason to expose it, so it doesn't look good either, I've used it for illustrative purposes. – Estus Flask Nov 07 '17 at 20:47
  • I'm not sure the notion of 'internal' properties makes much sense - all properties are 'internal'. Do you mean 'private' properties? These start with `_`, but 'source' and 'operator' properties do not start with `_`, so I think it's legit to use them. Impractical? Not if solves the problem. – Richard Matsen Nov 07 '17 at 20:59
  • @RichardMatsen _ is Hungarian notation which [isn't strictly followed in RxJS sources](https://github.com/ReactiveX/rxjs/blob/master/src/Observable.ts#L30-L32). Relying on internal undocumented props is bad practice (unless a dev knows what he/she's doing) because they are not parts of public API, may be changed without notice and result in broken code. – Estus Flask Nov 07 '17 at 21:13
  • Hey, I just pointed out an erroneous statement, which has since been withdrawn! – Richard Matsen Nov 07 '17 at 21:16
  • @RichardMatsen Yep, thanks for that. Nevertheless, the main point is that doing something like that is pointless because observables are potentially async by design, I guess that's why `scheduler` isn't exposed in public API. – Estus Flask Nov 07 '17 at 21:22
  • However (sorry to correct again), keep in mind that schedulers ***are*** passed into some operators (optionally) and it's worth exploring their role in rxjs. – Richard Matsen Nov 07 '17 at 21:28
5

It is hard to figure out whether an observable you get is sync or async (imagine you get observables return from a thrid party library). That's why you write it in a fashion that the execution order is controlled and predictable.

That's also why there are operators like concat, combineLatest, forkjoin, switchMap, race, merge to help you get the order and performance right for different scenario

Fan Cheung
  • 10,745
  • 3
  • 17
  • 39