我在我的应用程序中使用以下史诗来处理api请求:
action$ => {
return action$.ofType(actions.requestType)
.do(() => console.log('handled epic ' + actions.requestType))
.switchMap((action) => (
Observable.create((obs) => {
obs.next({ type: type, value: action.value, form: action.form });
})
.debounceTime(250)
.switchMap((iea) => (
Observable.ajax(ajaxPost(url(iea.value), body ? body(iea.value) : action.form))
.mergeMap(payload => {
return Observable.merge(
Observable.of(actions.success(payload)),
/* some other stuff */
);
})
.catch(payload => {
return [actions.failure(payload)];
})
))
))
.takeUntil(action$.filter((a) => (a.type === masterCancelAction))
.repeat();
};
基本上,每次执行api请求时,我都会发出请求操作。如果我快速发送另一个请求,则使用debounceTime忽略前一个请求。此外,可以使用masterCancelAction取消请求,取消时,repeat()重新启动epic。这部史诗在所有情况下均可用于预期。
当用户在请求期间使用浏览器时,会发生故障情况。在这种情况下,我将masterCancelAction触发到请求。但是,在与masterCancelAction相同的执行上下文中,另一个请求操作将调度以在同一个epic上执行新请求,但不会发生api请求(尽管console.log确实发生),就好像没有重复一样()。在其他发生取消的情况下,下一个请求不是从同一个执行上下文调用的,它工作正常,所以在这种情况下我的代码似乎没有重复机会重启史诗?
我发现一个肮脏的解决方法是对取消后调度的请求使用setTimeout(dispatch(action),0)。这似乎允许repeat()执行。我尝试将不同的调度程序重复,但这似乎没有帮助。另外,将takeUntil和repeat附加到我的内部switchMap中可以解决问题,但是在我的下一个请求不在同一个调用堆栈中执行的其他情况会失败。
有没有办法可以在不使用setTimeout的情况下解决这个问题?也许这不是一个重复相关的问题,但似乎是这样。
使用rxjs 5.0.3和redux-observable 0.14.1。
答案 0 :(得分:2)
如果没有像jsbin这样的东西看你的意思,这个问题不是100%明确的,但我确实看到了一些可能有帮助的一般性问题:
创建自定义匿名Observable时,如果您确实要完成,请致电observer.complete()
。在大多数情况下,不这样做会导致订阅内存泄漏,也可能是其他奇怪的行为
Observable.create((observer) => {
observer.next({ type: type, value: action.value, form: action.form });
observer.complete();
})
Observable.of
本来是等价的:
Observable.of({ type: type, value: action.value, form: action.form })
但是,不清楚为什么要这样做,因为它发出的值是在范围内捕获的。
debounceTime
在这种情况下不会去抖,它会延迟由于匿名可观察,它仅适用于发出单个项目,debounceTime
将仅作为常规.delay(250)
。我打赌您打算去辩护actions.requestType
行动,在这种情况下,您需要在switchMap
之后action$.ofType(actions.requestType)
之外应用您的去抖动。
Observable.of
接受任意数量的发射这更像是一个"你知道吗?"而不是一个问题,但我注意到你正在合并你的of
和/* some other actions */
我认为将合并其他of
个可观察对象。相反,你可以只返回一个of
Observable.of(
actions.success(payload),
/* some other actions */
actions.someOtherOne(),
actions.etc()
);
1}}并将操作作为参数传递。
.takeUntil
此外,当您发现自己同步发出多个动作时,请考虑减少器是否应该侦听相同的单个动作而不是两个或更多动作。有时这不是有意义的,因为你希望他们有完全不相关的行为,只需要记住人们经常忘记的事情 - 所有减速器都会接受所有动作,因此多个减速器可以从同一动作改变它们的状态。
takeUntil
将阻止史诗聆听未来的行动将action$.ofType(actions.requestType)
放在顶级可观察链上会导致史诗停止收听.repeat()
,这就是您之后添加switchMap
的原因。这可能在某些情况下有效,但效率低下并且可能导致其他难以实现的错误。史诗应该被认为是类似于通常"启动的边车过程"使用该应用程序然后继续侦听特定操作,直到应用程序关闭"即用户离开应用程序。它们不是实际过程,它只是有助于从概念上将它们视为一种抽象概念。
因此,每当它与其特定操作匹配时,它通常会mergeMap
,concatMap
,exhaustMap
或.takeUntil
进入某些副作用,就像ajax调用一样。那个内部可观察链是你想要取消的东西。因此,您可以在链中的适当位置放置const someRequestEpic = action$ => {
return action$.ofType(actions.requestType)
.debounceTime(250)
.do(() => console.log('handled epic ' + actions.requestType))
.switchMap((action) =>
Observable.ajax(ajaxPost(url(action.value), body ? body(action.value) : action.form))
.takeUntil(action$.ofType(masterCancelAction))
.mergeMap(payload => {
return Observable.of(
actions.success(payload),
/* some other actions */
...etc
);
})
.catch(payload => Observable.of(
actions.failure(payload)
))
);
};
。
如上所述,如果没有像jsbin这样的更完整的例子,它不清楚你打算做什么以及问题是什么。但严格基于提供的代码,这是我的猜测:
function* subsetSum(numbers, target, partial = [], partialSum = 0) {
if(partialSum === target) yield partial
if(partialSum >= target) return
for(let i = 0; i < numbers.length; i++){
const remaining = numbers.slice(i + 1)
, n = numbers[i]
yield* subsetSum(remaining, target, [...partial, n], partialSum + n)
}
}
查看redux-observable文档中的Cancellation页面。
如果这有点令人困惑,我建议深入研究一下Observable是什么以及什么是&#34;运算符&#34;是的,并且这样做,它不会感到神奇,你应该把操作员放在哪里更有意义。
Ben Learning Observable by Building Observable上的帖子是一个好的开始。