Сохранение состояния при работе в switchMap

#rxjs #behaviorsubject #rxjs-pipeable-operators #switchmap

#rxjs #behaviorsubject #rxjs-pipeable-операторы #switchmap

Вопрос:

Предположим, что у вас есть функция, которая возвращает наблюдаемый объект rxjs, содержащий список объектов.

 const getItems = () =>
  of([
    {
      id: 1,
      value: 10
    },
    {
      id: 2,
      value: 20
    },
    {
      id: 3,
      value: 30
    }
  ]);
 

и вторая функция, которая возвращает наблюдаемое с одним объектом

 const getItem = id =>
  of({
    id,
    value: Math.floor(Math.random() * 30)   1
  });
 

Теперь мы хотим создать наблюдаемый объект, который получит первый список и через регулярные промежутки времени будет случайным образом обновлять любой элемент списка.

 const source = getItems().pipe(
  switchMap(items =>
    interval(5000).pipe(
      switchMap(x => {
        // pick up a random id
        const rId = Math.floor(Math.random() * 3)   1;

        return getItem(rId).pipe(
          map(item =>
            items.reduce(
              (acc, cur) =>
                cur.id === item.id ? [...acc, item] : [...acc, cur],
              []
            )
          )
        );
      })
    )
  )
);

source.subscribe(x => console.log(JSON.stringify(x)));

 

Проблема с приведенным выше кодом заключается в том, что каждый раз, когда запускается интервал, элементы из предыдущей итерации возвращаются к своей первоначальной форме. например,

 [{"id":1,"value":10},{"id":2,"value":13},{"id":3,"value":30}]
[{"id":1,"value":10},{"id":2,"value":20},{"id":3,"value":18}]
[{"id":1,"value":10},{"id":2,"value":16},{"id":3,"value":30}]
[{"id":1,"value":21},{"id":2,"value":20},{"id":3,"value":30}]
 

Как вы видите, на каждом интервале наш код сбрасывает список и обновляет новый элемент (например, значение 13 теряется на второй итерации и возвращается к 20).
Поведение кажется разумным, поскольку items аргумент в первом switchMap действует как замыкание.

Мне удалось каким-то образом решить проблему с помощью BehaviorSubject , но я думаю, что мое решение как-то грязно.

 const items$ = new BehaviorSubject([]);

const source = getItems().pipe(
  tap(items => items$.next(items)),
  switchMap(() =>
    interval(5000).pipe(
      switchMap(() => {
        const rId = Math.floor(Math.random() * 3)   1;

        return getItem(rId).pipe(
          map(item =>
            items$
              .getValue()
              .reduce(
                (acc, cur) =>
                  cur.id === item.id ? [...acc, item] : [...acc, cur],
                []
              )
          ),
          tap(items => items$.next(items)),
          switchMap(() => items$)
        );
      })
    )
  )
);
 

Есть ли лучший подход?

Пример кода можно найти здесь

Ответ №1:

Я считаю, что это должно делать то, что вы хотите:

 const source = getItems().pipe(
  switchMap(items =>
    interval(1000).pipe(
      switchMap(() => {
        const rId = Math.floor(Math.random() * 3)   1;
        return getItem(rId);
      }),
      scan((acc, item) => {
        acc[acc.findIndex(i => i.id === item.id)] = item;
        return acc;
      }, items),
    )
  )
);
 

Это в основном то, что вы делаете, но я использую scan (который инициализируется оригиналом items ), чтобы сохранить выходной массив, acc чтобы я мог обновить его позже снова.

Живая демонстрация: https://stackblitz.com/edit/rxjs-kvygy1?file=index.ts

Комментарии:

1. Спасибо! Я также безуспешно пробовал оператор сканирования