21

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

想象一下,我们正在调度一个动作,该动作触发了一些状态变化,并且还附加了一些效果。所以我们的代码必须做两件事——改变状态和做一些副作用。但是这些任务的顺序是什么?我们是否同步进行?我相信首先,我们改变状态然后做副作用,但是有没有可能,这两个任务之间可能会发生其他事情?像这样:我们改变状态,然后得到一些我们之前做的 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

所以问题很简单,糟糕的情况是否真的会发生?

4

1 回答 1

19

所以我们的代码必须做两件事——改变状态和做一些副作用。但是这些任务的顺序是什么?我们是否同步进行?

假设我们调度动作 A。我们有一些处理动作 A 的 reducer。它们将按照传递给 StoreModule.provideStore() 的对象中指定的顺序被调用。然后接下来会触发听动作 A 的副作用。是的,它是同步的。

我相信首先,我们改变状态然后做副作用,但是有没有可能,这两个任务之间可能会发生其他事情?像这样:我们改变状态,然后得到一些我们之前做的 HTTP 请求的响应并处理它,然后做副作用。

自去年年中以来,我一直在使用 ngrx,但我从未观察到这种情况。我发现每次调度一个动作时,它都会经历首先由减速器处理,然后是副作用,然后再处理下一个动作的整个周期。

我认为这必须是这样,因为 redux(由 ngrx 演变而来)在其主页上将自己标榜为可预测的状态容器。通过允许发生不可预测的异步操作,您将无法预测任何事情,redux 开发工具也不会很有用。

编辑#1

所以我只是做了一个测试。我运行了一个动作“LONG”,然后副作用将运行一个需要 10 秒的操作。与此同时,我能够继续使用 UI,同时向该州进行更多调度。最后,“LONG”的效果完成并发送了“LONG_COMPLETE”。我错了减速器和副作用是交易。

在此处输入图像描述

也就是说,我认为仍然很容易预测正在发生的事情,因为所有状态更改仍然是事务性的。这是一件好事,因为我们不希望 UI 在等待长时间运行的 api 调用时阻塞。

编辑#2

因此,如果我正确理解这一点,那么您问题的核心是关于 switchMap 和副作用。基本上你是在问如果响应在我运行减速器代码的那一刻返回怎么办,然后将运行带有 switchMap 的副作用以取消第一个请求。

我想出了一个测试,我相信它可以回答这个问题。我设置的测试是创建 2 个按钮。一个叫Quick,一个叫Long。Quick 将发送“QUICK”,Long 将发送“LONG”。听 Quick 的 reducer 会立即完成。听 Long 的 reducer 需要 10 秒才能完成。

我设置了一个同时收听 Quick 和 Long 的副作用。这假装通过使用“of”来模拟 api 调用,让我从头开始创建一个 observable。然后,这将等待 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 秒内完成的可观察对象。
  • 长按钮点击
  • 'LONG' 已发送
  • 减速机处理 LONG 需要 10 秒。在 5 秒标记处,来自副作用的原始 observable 完成,但没有调度“QUICK_LONG_COMPLETE”。又过了5秒。
  • 听“LONG”的副作用做了一个开关图,取消了我的第一个副作用。
  • 5 秒过去了,'QUICK_LONG_COMPLETE' 被调度。

在此处输入图像描述

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

于 2017-06-30T19:35:22.053 回答