0
const s1$ = of(Math.random())
const s2$ = ajax.getJSON(`https://api.github.com/users?per_page=5`)
const s3$ = from(fetch(`https://api.github.com/users?per_page=5`))
const click$ = fromEvent(document, 'click')
click$.pipe(
    switchMap(() => s1$)
).subscribe(e => {
    console.log(e)
})

I was confused by the code above and can not reason about them properly. In the first case(s1$), the same result is received every time, it LOOKs fine to me even though I can not understand why switchMap do not start a new stream each time. OK, it is fine

The really wired thing happen when you run s2$ and s3$, the looks equivalent, right? WRONG!!! the behaviours are completely different if you try them out!

The result of s3$ is cached somehow, i.e. if you open the network panel, you will see the http request was send only ONCE. In comparison, the http request is sent each time for s2$

My problem is that I can not use something like ajax from rx directly because the http request is hidden a third-party library, The solution I can come up with is to use inline stream, i.e. create new stream every time

click$.pipe(
    switchMap(() => from(fetch(`https://api.github.com/users?per_page=5`)))
).subscribe(e => {
    console.log(e)
})

So, how exactly I can explain such behaviour and what is the correct to handle this situation?

artur grzesiak
  • 20,230
  • 5
  • 46
  • 56
Guichi
  • 2,150
  • 1
  • 19
  • 27
  • 1
    where can one find your full code? What is ajax? Are you behind a service worker? You ask a particular question, but you don't provide a way to test the behavior you describe. Please create a code snippet that reproduces the issue (which can be used to prove what you say and to further debug it), put all relevant code in there and update the question – smnbbrv Feb 12 '19 at 09:41
  • 1
    `switchMap` doesn't cache anything. – martin Feb 12 '19 at 09:44
  • @Guichi Including me in this comment like that unfortunately will send me a notification. But if you have a question about my answer, you can just leave a comment there. – Yoshi Feb 12 '19 at 11:49
  • @Guichi I am not pretending to be `rxjs` expert in any meaning. I am just one of those who tries to answer your question. All I ask is being nice to ones you ask help from. Unlike @Yoshi you did not provide a running code, what you did is posted some **partial** code as is. I read this as "here is something, I don't care how you answer this". And you made @Yoshi to work for you. I would also call the latest comment as not respectful. However I hope you got your answer. – smnbbrv Feb 12 '19 at 11:51
  • @Yoshi you answer is pretty useful, I am looking into the highly related thread https://stackoverflow.com/questions/38764578/rxjs-understanding-defer. I can understand the problem of my code is `too eager` and only run once. Still can not fully understand how `defer` do the magic. I think it is the different between javascript object and function. function can be called over and over again while object can not – Guichi Feb 12 '19 at 11:57
  • @Guchi `defer` simply *wraps* a function (it's only argument) to be called *later*. In the context it's also a stream and as such can be subscribed to. – Yoshi Feb 12 '19 at 12:01

1 Answers1

2

One problem is that you actually execute Math.random and fetch while setting up your test case.

// calling Math.random() => using the return value
const s1$ = of(Math.random())

// calling fetch => using the return value (a promise)
const s3$ = from(fetch(`https://api.github.com/users?per_page=5`))

Another is that fetch returns a promise, which resolves only once. from(<promise>) then does not need to re-execute the ajax call, it will simply emit the resolved value.

Whereas ajax.getJSON returns a stream which re-executes every time.

If you wrap the test-streams with defer you get more intuitive behavior.

const { of, defer, fromEvent } = rxjs;
const { ajax }                 = rxjs.ajax;
const { switchMap }            = rxjs.operators;

// defer Math.random()
const s1$ = defer(() => of(Math.random()));

// no defer needed here (already a stream)
const s2$ = ajax.getJSON('https://api.github.com/users?per_page=5');

// defer `fetch`, but `from` is not needed, as a promise is sufficient
const s3$ = defer(() => fetch('https://api.github.com/users?per_page=5'));

const t1$ = fromEvent(document.getElementById('s1'), 'click').pipe(switchMap(() => s1$));
const t2$ = fromEvent(document.getElementById('s2'), 'click').pipe(switchMap(() => s2$));
const t3$ = fromEvent(document.getElementById('s3'), 'click').pipe(switchMap(() => s3$));

t1$.subscribe(console.log);
t2$.subscribe(console.log);
t3$.subscribe(console.log);
<script src="https://unpkg.com/@reactivex/rxjs@6/dist/global/rxjs.umd.js"></script>

<button id="s1">test random</button>
<button id="s2">test ajax</button>
<button id="s3">test fetch</button>
Yoshi
  • 54,081
  • 14
  • 89
  • 103
  • `defer` is exactly what I expect even before I realise the existence of it. My initial solution is using a function or inline the stream just to create a new instance rather than reuse the old one. Now, I think `defer` works the same way somehow – Guichi Feb 12 '19 at 12:03