简体   繁体   中英

How can I achieve a shareReplay with reconnection?

In the following code, I create a simple observable that produces one value and then complete. Then I share that observable replaying the last item and suscribe 3 times. The first right after, the second one before the value is produced and the third time after value is produced and the observable has completed.

let i = 0;
let obs$ = Rx.Observable.create(obs => {
  console.log('Creating observable');
  i++;
  setTimeout(() => {
     obs.onNext(i);
     obs.onCompleted();
  }, 2000);
}).shareReplay(1);

obs$.subscribe(
  data => console.log(`s1: data = ${data}`),
  () => {},
  () => console.log('finish s1')
);

setTimeout( () => {
  obs$.subscribe(
    data => console.log(`s2: data = ${data}`),
    () => {},
    () => console.log('finish s2')

  );  
}, 1000);

setTimeout( () => {
  obs$.subscribe(
    data => console.log(`s3: data = ${data}`),
    () => {},
    () => console.log('finish s3')

  );  
}, 6000);

You can execute this on jsbin

This results in the following marble diagram

Actual
s1: -----1$
s2:   \--1$
s3:           \1$

But I would expect

Expected
s1: -----1$
s2:   \--1$
s3:           \----2$

I can understand why someone would like to have the first behaviour, but my reasoning is that, unlike this example, where I'm returning a number, I could be returning an object susceptible to unsubscribe behaviour, for example a database connection. If the above marble diagram represents a database connection, where in the dispose method I call a db.close() , on the third subscription I would have an exception, because I'm receiving as value a database handler that was released. (because when the second subscription finished refCount = 0 and the source is disposed).

Also another weird thing this example has, is that even it's resolving with the first value and completing just after, its subscribing to the source twice (as you can see by the duplicated "Creating observable")

I know this github issue talks about this but what I'm missing is:

How can achieve (both in RxJs4 and 5) a shared observable that can replay the last item if the source observable hasn't completed, and if its done (refCount = 0), recreate the observable.

In RxJs5 I think the share method solves the reconnecting part of my problem, but not the sharing part.

In RxJs4 I'm clueless

If possible I would like to solve this using existing operators or subjects. My intuition tells me I would have to create a different Subject with such logic, but I'm not quite there yet.

A bit on shareReplay:

shareReplay keeps the same underlying ReplaySubject instance for the rest of the lifetime of the returned observable.

Once ReplaySubject completes, you can't put any more values into it, but it will still replay. So...

  1. You subscribe to the observable the first time and the timeout starts. This increments i from 0 to 1 .
  2. You subscribe to the observable the second time and the timeout is already going.
  3. The timeout callback fires and sends out onNext(i) , then onCompleted() .
  4. onCompleted() signal completes the ReplaySubject inside the shareReplay , meaning that from now on, that shared observable will simply replay the value it has (which is 1) and complete.

A bit on shared observables in general:

Another, separate issue is that since you shared the observable, it's only ever going to call the subscriber function one time . That means that i will only ever be incremented one time. So even if you didn't onCompleted and kill your underlying ReplaySubject , you're going to end up not incrementing it to 2 .

This isn't RxJS 5

A quick way to tell is onNext vs next . You're currently using RxJS 4 in your example, but you've tagged this with RxJS 5, and you've sighted an issue in RxJS 5. RxJS 5 is beta and a new version that is a complete rewrite of RxJS 4. The API changes were done mostly to match the es-observable proposal which is currently at stage 1

Updated example

I've updated your example to give you your expected results

Basically, you want to use a shared version of the observable for the first two calls, and the original observable for the third one.

let i = 0;
let obs$ = Rx.Observable.create(obs => {
  console.log('Creating observable');
  i++;
  setTimeout(() => {
     obs.onNext(i);
     obs.onCompleted();
  }, 2000);
})


let shared$ = obs$.shareReplay(1);

shared$.subscribe(
  data => console.log(`s1: data = ${data}`),
  () => {},
  () => console.log('finish s1')
);

setTimeout( () => {
  shared$.subscribe(
    data => console.log(`s2: data = ${data}`),
    () => {},
    () => console.log('finish s2')

  );  
}, 1000);

setTimeout( () => {
  obs$.subscribe(
    data => console.log(`s3: data = ${data}`),
    () => {},
    () => console.log('finish s3')

  );  
}, 6000);

Unrelated

Also, protip: be sure to return a cancellation semantic for your custom observable that calls clearTimeout .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM