我对这个问题有自己的看法,但最好仔细检查并确定。感谢您的关注并尝试提供帮助。这是:
想象一下,我们正在调度一个触发某些状态更改的动作,并且还附加了一些效果。所以我们的代码必须做两件事 - 改变状态并做一些副作用。但这些任务的顺序是什么?我们是在同步吗?我相信,首先,我们改变状态,然后做副作用,但有可能,这两个任务之间可能会发生其他事情吗?像这样:我们改变状态,然后对我们之前做过的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
所以问题是,如果不好的情况实际发生或不存在?
答案 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;。我认为减速器和副作用是一种交易。
这就是说我认为现在仍然很容易预测会发生什么,因为所有州的变化仍然是交易性的。这是一件好事,因为我们不希望在等待长时间运行的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'
}
)
});
在测试过程中,我点击了快速按钮,然后立即点击了长按钮。
以下是发生的事情:
因此,switchMap确实取消了,你的不良情况不应该发生。