rxjs switchMap кэширует устаревший результат и не создает новый поток

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)
})

Меня смутил приведенный выше код, и я не могу правильно рассуждать о них. В первом случае (s1$) каждый раз получается один и тот же результат, мне он ВЫГЛЯДИТ нормально, хотя я не могу понять, почему switchMap не запускает каждый раз новый поток. Хорошо, все в порядке

Когда вы запускаете s2$ и s3$, происходит действительно запутанная вещь, выглядит одинаково, верно? НЕПРАВИЛЬНЫЙ!!! поведение совершенно другое, если вы попробуете их!

Результат s3$ каким-то образом кэшируется, т.е. если вы откроете сетевую панель, то увидите, что http-запрос был отправлен только ОДИН РАЗ. Для сравнения, HTTP-запрос отправляется каждый раз для s2$

Моя проблема в том, что я не могу использовать что-то вроде ajax из rx напрямую, потому что http-запрос скрыт сторонней библиотекой. Решение, которое я могу придумать, - использовать встроенный поток, т.е. каждый раз создавать новый поток

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

Итак, как именно я могу объяснить такое поведение и как правильно поступить в этой ситуации?


person Guichi    schedule 12.02.2019    source источник
comment
где можно найти ваш полный код? Что такое аякс? Вы за сервисным работником? Вы задаете конкретный вопрос, но не даете способа проверить описанное вами поведение. Пожалуйста, создайте фрагмент кода, воспроизводящий проблему (который можно использовать для подтверждения того, что вы говорите, и для дальнейшей ее отладки), поместите туда весь соответствующий код и обновите вопрос.   -  person smnbbrv    schedule 12.02.2019
comment
switchMap ничего не кэширует.   -  person martin    schedule 12.02.2019
comment
@Guichi Включение меня в этот комментарий, к сожалению, отправит мне уведомление. Но если у вас есть вопрос по моему ответу, вы можете просто оставить комментарий там.   -  person Yoshi    schedule 12.02.2019
comment
@Guichi Я не претендую на звание rxjs эксперта ни в каком смысле. Я всего лишь один из тех, кто пытается ответить на ваш вопрос. Все, о чем я прошу, это быть добрым к тем, у кого вы просите помощи. В отличие от @Yoshi, вы не предоставили работающий код, то, что вы сделали, опубликовало некоторый частичный код как есть. Я читаю это, как здесь что-то, мне все равно, как вы ответите на это. И ты заставил @Yoshi работать на тебя. Я бы также назвал последний комментарий неуважительным. Однако я надеюсь, что вы получили свой ответ.   -  person smnbbrv    schedule 12.02.2019
comment
@Yoshi, ваш ответ очень полезен, я изучаю очень связанную тему /questions/38764578/rxjs-understanding-defer. Я могу понять, что проблема моего кода too eager и выполняется только один раз. Все еще не могу полностью понять, как defer творит магию. Я думаю, что это разница между объектом и функцией javascript. функция может вызываться снова и снова, в то время как объект не может   -  person Guichi    schedule 12.02.2019
comment
@Guchi defer просто оборачивает функцию (это единственный аргумент), которая будет вызываться позже. В контексте это также поток, и на него можно подписаться.   -  person Yoshi    schedule 12.02.2019


Ответы (1)


Одна проблема заключается в том, что вы на самом деле выполняете Math.random и fetch при настройке своего тестового примера.

// 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`))

Другое дело, что fetch возвращает промис, который разрешается только один раз. from(<promise>) тогда не нужно повторно выполнять вызов ajax, он просто выдаст разрешенное значение.

В то время как ajax.getJSON возвращает поток, который каждый раз выполняется повторно.

Если вы оберните тестовые потоки defer, вы получите более интуитивно понятное поведение.

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>

person Yoshi    schedule 12.02.2019
comment
defer — это именно то, что я ожидаю еще до того, как осознаю его существование. Мое первоначальное решение использует функцию или встроенный поток только для создания нового экземпляра, а не для повторного использования старого. Теперь я думаю, что defer работает так же как-то - person Guichi; 12.02.2019