简体   繁体   English

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

[英]ngRx state update and Effects execution order

I have my own opinion on this question, but it's better to double check and know for sure. 我对这个问题有自己的看法,但最好仔细检查并确定。 Thanks for paying attention and trying to help. 感谢您的关注并尝试提供帮助。 Here it is: 这里是:

Imagine that we're dispatching an action which triggers some state changes and also has some Effects attached to it. 想象一下,我们正在调度一个触发一些状态变化的动作,并且还附加了一些效果。 So our code has to do 2 things - change state and do some side effects. 所以我们的代码必须做两件事 - 改变状态并做一些副作用。 But what is the order of these tasks? 但这些任务的顺序是什么? Are we doing them synchronously? 我们是在同步吗? I believe that first, we change state and then do the side effect, but is there a possibility, that between these two tasks might happen something else? 我相信,首先,我们改变状态,然后做副作用,但有可能,这两个任务之间可能会发生其他事情吗? Like this: we change state, then get some response on HTTP request we did previously and handle it, then do the side effects. 像这样:我们改变状态,然后在我们之前做过的HTTP请求上得到一些响应并处理它,然后做副作用。

[edit:] I've decided to add some code here. [编辑:]我决定在这里添加一些代码。 And also I simplified it a lot. 而且我也简化了它。

State: 州:

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

Actions: 操作:

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

Reducer: 减速器:

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: 影响:

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

And this is how we dispatch FetchItemAction: 这就是我们派遣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])
    }
}

Desired scenario: 期望的场景:

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.

Bad scenario: 不好的情况:

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

So the question is simply if the bad scenario can actually happen or not? 所以问题是,如果不好的情况真的发生了吗?

So our code has to do 2 things - change state and do some side effects. 所以我们的代码必须做两件事 - 改变状态并做一些副作用。 But what is the order of these tasks? 但这些任务的顺序是什么? Are we doing them synchronously? 我们是在同步吗?

Let's say we dispatch action A. We have a few reducers that handle action A. Those will get called in the order they are specified in the object that is passed to StoreModule.provideStore(). 假设我们发送动作A.我们有一些处理动作A的reducers。它们将按照传递给StoreModule.provideStore()的对象中指定的顺序被调用。 Then the side effect that listens to action A will fire next. 然后,听取动作A的副作用将在下一次触发。 Yes, it is synchronous. 是的,它是同步的。

I believe that first, we change state and then do the side effect, but is there a possibility, that between these two tasks might happen something else? 我相信,首先,我们改变状态,然后做副作用,但有可能,这两个任务之间可能会发生其他事情吗? Like this: we change state, then get some response on HTTP request we did previously and handle it, then do the side effects. 像这样:我们改变状态,然后在我们之前做过的HTTP请求上得到一些响应并处理它,然后做副作用。

I've been using ngrx since middle of last year and I've never observed this to be the case. 从去年中期开始我一直在使用ngrx,我从来没有注意到这种情况。 What I found is that every time an action is dispatched it goes through the whole cycle of first being handled by the reducers and then by the side effects before the next action is handled. 我发现,每次调度一个动作时,它都会经历由减速器首先处理的整个循环,然后在处理下一个动作之前通过副作用。

I think this has to be the case since redux (which ngrx evolved from) bills itself as a predictable state container on their main page. 我认为这是必须的,因为redux(ngrx从中演变而来)将自己称为主页上可预测的状态容器。 By allowing unpredictable async actions to occur you wouldn't be able to predict anything and the redux dev tools wouldn't be very useful. 通过允许发生不可预测的异步操作,您将无法预测任何内容,并且redux dev工具不会非常有用。

Edited #1 编辑#1

So I just did a test. 所以我刚做了一个测试。 I ran an action 'LONG' and then the side effect would run an operation that takes 10 seconds. 我运行了一个'LONG'动作,然后副作用将运行一个需要10秒的操作。 In the mean time I was able to continue using the UI while making more dispatches to the state. 与此同时,我可以继续使用UI,同时向州提供更多的调度。 Finally the effect for 'LONG' finished and dispatched 'LONG_COMPLETE'. 最后,'LONG'的效果已完成并发送'LONG_COMPLETE'。 I was wrong about the reducers and side effect being a transaction. 我认为减速器和副作用是一种交易是错误的。

在此输入图像描述

That said I think it's still easy to predict what's going on because all state changes are still transactional. 这就是说我认为仍然很容易预测发生了什么,因为所有州的变化仍然是交易性的。 And this is a good thing because we don't want the UI to block while waiting for a long running api call. 这是一件好事,因为我们不希望UI在等待长时间运行的api调用时阻塞。

Edited #2 编辑#2

So if I understand this correctly the core of your question is about switchMap and side effects. 因此,如果我理解正确,你的问题的核心是关于switchMap和副作用。 Basically you are asking what if the response comes back at the moment I am running the reducer code which will then run the side effect with switchMap to cancel the first request. 基本上你问的是,如果响应在我运行reducer代码时返回,然后使用switchMap运行副作用以取消第一个请求。

I came up with a test that I believe does answer this question. 我想出了一个我认为可以回答这个问题的测试。 The test I setup was to create 2 buttons. 我设置的测试是创建2个按钮。 One called Quick and one called Long. 一个叫Quick,一个叫Long。 Quick will dispatch 'QUICK' and Long will dispatch 'LONG'. Quick将发送'QUICK',Long将发送'LONG'。 The reducer that listens to Quick will immediately complete. 收听Quick的reducer将立即完成。 The reducer that listens to Long will take 10 seconds to complete. 收听Long的reducer需要10秒钟才能完成。

I setup a single side effect that listens to both Quick and Long. 我设置了一个可以同时监听Quick和Long的单一副作用。 This pretends to emulate an api call by using 'of' which let's me create an observable from scratch. 这假装通过使用'of'来模拟api调用,这让我从头开始创建一个observable。 This will then wait 5 seconds (using .delay) before dispatching 'QUICK_LONG_COMPLETE'. 然后在调度'QUICK_LONG_COMPLETE'之前等待5秒(使用.delay)。

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

During my test I clicked on the quick button and then immediately clicked the long button. 在测试期间,我点击了快速按钮,然后立即点击了长按钮。

Here is what happened: 这是发生了什么:

  • Quick button clicked 单击快速按钮
  • 'QUICK' is dispatched 发送'QUICK'
  • Side effect starts an observable that will complete in 5 seconds. 副作用开始一个可在5秒内完成的观察。
  • Long button clicked 单击长按钮
  • 'LONG' is dispatched 'LONG'被派遣
  • Reducer handling LONG takes 10 seconds. 减速机处理时间长达10秒。 At the 5 second mark the original observable from the side effect completes but does not dispatch the 'QUICK_LONG_COMPLETE'. 在5秒标记处,副作用中的原始可观察量完成,但不会发送'QUICK_LONG_COMPLETE'。 Another 5 seconds pass. 又过了5秒钟。
  • Side effect that listens to 'LONG' does a switchmap cancelling my first side effect. 听“LONG”的副作用会使switchmap取消我的第一个副作用。
  • 5 seconds pass and 'QUICK_LONG_COMPLETE' is dispatched. 5秒后传递,并调度'QUICK_LONG_COMPLETE'。

在此输入图像描述

Therefore switchMap does cancel and your bad case shouldn't ever happen. 因此,switchMap确实取消了,不应该发生不良情况。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM