ngRx状态更新和效果执行顺序

时间:2017-06-30 19:12:17

标签: javascript angular ngrx ngrx-effects ngrx-store

我对这个问题有自己的看法,但最好仔细检查并确定。感谢您的关注并尝试提供帮助。这是:

想象一下,我们正在调度一个触发某些状态更改的动作,并且还附加了一些效果。所以我们的代码必须做两件事 - 改变状态并做一些副作用。但这些任务的顺序是什么?我们是在同步吗?我相信,首先,我们改变状态,然后做副作用,但有可能,这两个任务之间可能会发生其他事情吗?像这样:我们改变状态,然后对我们之前做过的HTTP请求做出一些响应并处理它,然后做副作用。

[编辑:]我决定在这里添加一些代码。而且我也简化了很多。

州:

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 个答案:

答案 0 :(得分:15)

  

所以我们的代码必须做两件事 - 改变状态并做一些事情   效果。但这些任务的顺序是什么?我们在做吗   同步?

让我们说我们发送动作A.我们有一些处理动作A的减速器。它们将按照传递给StoreModule.provideStore()的对象中指定的顺序被调用。然后,听取动作A的副作用将在下一次触发。是的,它是同步的。

  

我相信,首先,我们改变状态,然后做副作用,但是   是否有可能在这两个任务之间发生   别的什么?像这样:我们改变状态,然后得到一些回应   我们之前做过的HTTP请求并处理它,然后执行   的效果。

自去年年中以来,我一直在使用ngrx,而且我从来没有注意到这种情况。我发现,每次调度一个动作时,它都会经历由减速器首先处理的整个循环,然后在处理下一个动作之前通过副作用。

我认为必须如此,因为redux(ngrx从中演变而来)将自己称为主页上可预测的状态容器。通过允许发生不可预测的异步操作,您将无法预测任何内容,而redux dev工具也不会非常有用。

编辑#1

所以我刚做了一个测试。我开了一个动作&#39; LONG&#39;然后副作用将运行一个需要10秒的操作。与此同时,我可以继续使用UI,同时向州提供更多的调度。最后效果为&#39; LONG&#39;完成并发送&#39; LONG_COMPLETE&#39;。我认为减速器和副作用是一种交易。

enter image description here

这就是说我认为现在仍然很容易预测会发生什么,因为所有州的变化仍然是交易性的。这是一件好事,因为我们不希望在等待长时间运行的api呼叫时阻止UI。

编辑#2

因此,如果我理解正确,你的问题的核心是关于switchMap和副作用。基本上你问的是,如果响应在我运行reducer代码时返回,然后使用switchMap运行副作用以取消第一个请求。

我想出了一个我认为可以回答这个问题的测试。我设置的测试是创建2个按钮。一个叫Quick,一个叫Long。快速发送&#39; QUICK&#39;和龙将派遣长期&#39;。收听Quick的reducer将立即完成。收听Long的reducer需要10秒钟才能完成。

我设置了一个可以同时监听Quick和Long的单一副作用。这假装通过使用&#39;来模拟api调用。让我从头开始创造一个可观察的东西。然后在调度&#39; QUICK_LONG_COMPLETE&#39;之前等待5秒(使用.delay)。

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

在测试过程中,我点击了快速按钮,然后立即点击了长按钮。

以下是发生的事情:

  • 点击快速按钮
  • &#39; QUICK&#39;被派遣
  • 副作用会启动一个可在5秒内完成的观察。
  • 点击长按钮
  • &#39; LONG&#39;被派遣
  • 减速机处理时间长达10秒。在5秒标记处,副作用的原始观察结果完成,但不会发送“QUICK_LONG_COMPLETE”#39;。又过了5秒钟。
  • 倾听&#39; LONG&#39; switchmap取消了我的第一个副作用。
  • 5秒后通过&#39; QUICK_LONG_COMPLETE&#39;被派遣。

enter image description here

因此,switchMap确实取消了,你的不良情况不应该发生。