обновление состояния ngRx и порядок выполнения эффектов

у меня есть свое мнение по этому вопросу, но лучше перепроверить и знать наверняка. Спасибо, что уделили внимание и попытались помочь. Вот это:

представьте, что мы отправляем действие, которое вызывает некоторые изменения состояния, а также имеет некоторые эффекты, связанные с ним. Поэтому наш код должен сделать 2 вещи-изменить состояние и сделать некоторые побочные эффекты. Но каков порядок выполнения этих задач? Мы делаем их синхронно? Я считаю, что сначала мы меняем состояние, а затем делаем сторону эффект, но есть вероятность, что между этими двумя задачами может произойти что-то еще? Например: мы меняем состояние, затем получаем ответ на HTTP-запрос, который мы делали ранее, и обрабатываем его, а затем делаем побочные эффекты.

[edit:] я решил добавить здесь код. А еще я его сильно упростил.

состояние:

export interface ApplicationState {
    loadingItemId: string;
    items: {[itemId: string]: ItemModel}
}

действия:

export class FetchItemAction implements  Action {
  readonly type = 'FETCH_ITEM';
  constructor(public payload: string) {}
}

export class FetchItemSuccessAction implements  Action {
  readonly type = 'FETCH_ITEM_SUCCESS';
  constructor(public payload: ItemModel) {}
}

редуктор:

export function reducer(state: ApplicationState, action: any) {
    const newState = _.cloneDeep(state);
    switch(action.type) {
        case 'FETCH_ITEM':
            newState.loadingItemId = action.payload;
            return newState;
        case 'FETCH_ITEM_SUCCESS':
            newState.items[newState.loadingItemId] = action.payload;
            newState.loadingItemId = null;
            return newState;
        default:
            return state;
    }
}

эффект:

@Effect()
  FetchItemAction$: Observable<Action> = this.actions$
    .ofType('FETCH_ITEM')
    .switchMap((action: FetchItemAction) => this.httpService.fetchItem(action.payload))
    .map((item: ItemModel) => new FetchItemSuccessAction(item));

и вот как мы отправляем FetchItemAction:

export class ItemComponent {
    item$: Observable<ItemModel>;
    itemId$: Observable<string>;

    constructor(private route: ActivatedRoute,
                private store: Store<ApplicationState>) {

        this.itemId$ = this.route.params.map(params => params.itemId);

        itemId$.subscribe(itemId => this.store.dispatch(new FetchItemAction(itemId)));

        this.item$ = this.store.select(state => state.items)
            .combineLatest(itemId$)
            .map(([items, itemId]: [{[itemId: string]: ItemModel}]) => items[itemId])
    }
}

сценарий:

User clicks on itemUrl_1;
we store itemId_1 as loadingItemId;
make the request_1;
user clicks on itemUrl_2;
we store itemId_2 as loadingItemId;
switchMap operator in our effect cancells previous request_1 and makes request_2;
get the item_2 in response;
store it under key itemId_2 and make loadingItemId = null.

плохой сценарий:

User clicks on itemUrl_1;
we store itemId_1 as loadingItemId;
make the request_1;
user clicks on itemUrl_2;
we store itemId_2 as loadingItemId;  
we receive the response_1 before we made the new request_2 but after loadingItemId changed;
we store the item_1 from the response_1 under the key itemId_2;
make loadingItemId = null;
only here our effect works and we make request_2;
get item_2 in the response_2;
try to store it under key null and get an error

Итак, вопрос просто в том, может ли плохой сценарий действительно произойти или нет?

1 ответов


поэтому наш код должен сделать 2 вещи-изменить состояние и сделать некоторую сторону эффекты. Но каков порядок выполнения этих задач? Мы делаем их синхронно?

предположим, мы отправляем действие A. У нас есть несколько редукторов, которые обрабатывают действие A. те будут вызываться в порядке, они указаны в объекте, который передается StoreModule.provideStore(). Затем срабатывает побочный эффект, который слушает действие A. Да, это синхронно.

Я считаю, что сначала мы меняем состояние, а затем делаем побочный эффект, но есть ли вероятность, что между этими двумя задачами может случиться что-то еще? Вот так: мы меняем состояние, а затем получаем ответ на HTTP-запрос мы делали ранее и обрабатываем его, затем делаем сторону эффекты.

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

Я думаю, что это должно быть так, поскольку redux (из которого ngrx эволюционировал) выставляет себя как предсказуемый контейнер состояния на своей главной странице. Позволяя непредсказуемым асинхронным действиям происходить, вы не сможете ничего предсказать, и инструменты redux dev не будут очень полезны.

редактировать #1

поэтому я просто сделал тест. Я побежал действие "долго", а затем побочный эффект выполнит операцию, которая займет 10 секунд. В то же время я смог продолжить использовать пользовательский интерфейс, делая больше отправок в состояние. Наконец, эффект для "LONG" завершен и отправлен "LONG_COMPLETE". Я ошибался насчет того, что редукторы и побочный эффект являются транзакцией.

enter image description here

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

редактировать #2

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

Я придумал тест, который я полагаю, ответить на этот вопрос. Тест Я программа для создания кнопок 2. Один звонил быстро, другой-долго. Быстрый отправит "быстрый", а длинный отправит "длинный". Редуктор который слушает быстро немедленно завершит. Редуктор, который слушает долго, займет 10 секунд.

я настраиваю один побочный эффект, который слушает как быстро, так и долго. Это делает вид, что эмулирует вызов api, используя "of", который позволяет мне создать наблюдаемый с нуля. Этот затем подождите 5 секунд (используя .delay) перед отправкой 'QUICK_LONG_COMPLETE'.

  @Effect()
    long$: Observable<Action> = this.actions$
    .ofType('QUICK', 'LONG')
    .map(toPayload)
    .switchMap(() => {
      return of('').delay(5000).mapTo(
        {
          type: 'QUICK_LONG_COMPLETE'
        }
      )
    });

во время моего теста я нажал на кнопку, и затем сразу же нажал на кнопку.

вот что получилось:

  • быстрая кнопка нажата
  • ' QUICK ' отправляется
  • побочный эффект начинает наблюдаемый который завершит в 5 секундах.
  • длинная кнопка нажата
  • "долго" - направил
  • редуктор регулируя длиной принимает 10 секунд. На 5-секундной отметке исходный наблюдаемый побочный эффект завершается, но не отправляет "QUICK_LONG_COMPLETE". Еще 5 секунд пройти.
  • побочный эффект, который слушает "длинный", делает switchmap, отменяющий мой первый побочный эффект.
  • проходит 5 секунд, и "QUICK_LONG_COMPLETE" отправляется.

enter image description here

поэтому switchMap делает отмените, и Ваше плохое дело никогда не должно произойти.