我已经制作了一部等待另一部史诗完成的史诗,就像@ jayphelps'在这里回答:Invoking epics from within other epics
然而,我发现它似乎只运行一次。之后,我可以在控制台中看到CART_CONFIG_READY
操作,但不会触发DO_THE_NEXT_THING
操作。
我尝试了mergeMap
和switchMap
的各种组合,包含和不包含take
,但似乎没有任何帮助。
这是我的代码的样子。
import { NgRedux } from '@angular-redux/store';
import { Observable } from 'rxjs/Observable';
import { ActionsObservable } from 'redux-observable';
export class CartEpicsService {
checkCart = (action$: ActionsObservable<any>, store: NgRedux<any>) => {
return action$.ofType('CHECK_CART')
.switchMap(() => {
console.log('___LISTENING___');
return action$.ofType('CART_CONFIG_READY')
.take(1) // removing this doesn't help
.mergeMap(() => {
console.log('___RECEIVED___');
// do stuff here
return Observable.of({
type: 'DO_THE_NEXT_THING'
});
})
.startWith({
type: 'GET_CART_CONFIG'
});
});
}
getCartConfig = (action$: ActionsObservable<any>, store: NgRedux<any>) => {
return action$.ofType('GET_CART_CONFIG')
.switchMap(() => {
const config = store.getState().config;
// we already have the config
if (config) {
return Observable.of({
type: 'CART_CONFIG_READY'
});
}
// otherwise load it from the server using out HTTP service
return this.http.get('/cart/config')
.switchMap((response) => {
return Observable.concat(
Observable.of({
type: 'CART_CONFIG_SUCCESS'
}),
Observable.of({
type: 'CART_CONFIG_READY'
})
);
})
.catch(error => Observable.of({
type: 'CART_CONFIG_ERROR',
error
}));
});
}
}
对于上下文,我需要来自/ cart / config端点的响应来检查购物车的有效性。我只需要下载一次配置。
这是JS Bin上的一个可运行的例子:
答案 0 :(得分:2)
当state.config === true
返回同步发出的CART_CONFIG_READY
的Observable时,而第一次http请求(或jsbin中的延迟)意味着它始终是异步的。
为什么这有所不同是在checkCart
史诗中你返回一个可观察的链,用CART_CONFIG_READY
来监听action$.ofType('CART_CONFIG_READY')
,但也应用.startWith({ type: 'GET_CART_CONFIG' })
。这意味着,GET_CART_CONFIG
会在订阅 action$.ofType('CART_CONFIG_READY')
之前同步发出,因为startWith
是basically shorthand for a concat,这可能会使问题更加清晰如果你熟悉它。它几乎完全相同:
Observable.concat(
Observable.of({
type: 'GET_CART_CONFIG'
}),
action$.ofType('CART_CONFIG_READY') // not subscribed until prior complete()s
.take(1)
.mergeMap(() => {
// stuff
})
);
总而言之,第二次GET_CART_CONFIG
同步调度时发生了什么,getCartConfig
收到它并看到配置已经在商店中,因此它同步调度CART_CONFIG_READY
。但我们还没有在checkCart
中听取它,所以它没有得到答复。然后该callstack返回,并且我们的action$.ofType('CART_CONFIG_READY')
链中的concat中的下一个Observable被订阅。但为时已晚,它所听到的动作已经被发出!
解决此问题的一种方法是让 在这种情况下,您可以使用 我个人建议在这种情况下使用 由于 您只需要执行其中一种解决方案,但同时执行这两种解决方案并不会造成任何伤害。我可能会建议使用两者,以便事情是一致的和预期的,即使它们有点冗长。 您可能也很高兴知道 非常感谢jsbin btw,它使 更容易调试。 根据您的评论进行修改: 出于好奇,您是通过经验还是调试来解决这个问题的? 两者的结合。我已经处理了大量的异步/预定代码,并且排序通常是问题的根源。我扫描了代码,精神上描绘了执行,注意到异步vs同步的差异取决于代码路径,然后我做了一个快速操作符,以便我很容易确认任何Observable链订阅的顺序。 我把它应用到了几个地方,但最重要的是这两个: 它确认在第二个代码路径CART_CONFIG_READY
的发送始终保持异步,或者在发送GET_CART_CONFIG
之前在其他史诗中开始侦听它。< / p>
1。发出CART_CONFIG_READY async
Observable.of
接受调度程序作为其最后一个参数,并RxJS supports several of them。 AsyncScheduler
(macrotask)或AsapScheduler
(微任务)。两者都适用于这种情况,但它们在JavaScript事件循环中的不同时间安排。如果您不熟悉事件循环任务check this out。AsyncSheduler
,因为它会提供与发出http请求最接近的异步行为。import { async } from 'rxjs/scheduler/async';
// later inside your epic...
return Observable.of({
type: 'CART_CONFIG_READY'
}, async);
2。在发出
之前听取CART_CONFIG_READY
GET_CART_CONFIG
startWith
是concat
(我们不想做)的简写,我们需要使用某种形式的merge
,我们的ofType
首先链接,以便我们在发出之前听。action$.ofType('CART_CONFIG_READY')
.take(1)
.mergeMap(() => {
// stuff
})
.merge(
Observable.of({ type: 'GET_CART_CONFIG' })
)
// or
Observable.merge(
action$.ofType('CART_CONFIG_READY')
.take(1)
.mergeMap(() => {
// stuff
}),
Observable.of({ type: 'GET_CART_CONFIG' })
)
// both are exactly the same, pick personal preference on appearance
Observable.of
接受任意数量的项目,这些项目将按顺序发出。因此,您不需要使用concat
:// before
Observable.concat(
Observable.of({
type: 'CART_CONFIG_SUCCESS'
}),
Observable.of({
type: 'CART_CONFIG_READY'
})
)
// after
Observable.of({
type: 'CART_CONFIG_SUCCESS'
}, {
type: 'CART_CONFIG_READY'
})
Observable.prototype.logOnSubscribe = function (msg) {
// defer is a pretty useful Observable to learn if you haven't yet
return Observable.defer(() => {
console.log(msg);
return this; // the original source
});
};
action$.ofType('CART_CONFIG_READY')
.take(1)
.mergeMap(() => {
// stuff
})
.logOnSubscribe('listening for CART_CONFIG_READY') // <--- here
.startWith({
type: 'GET_CART_CONFIG'
});
// and in the other epic...
if (hasConfig) {
return Observable.of({
type: 'CART_CONFIG_READY'
})
.logOnSubscribe('emitting CART_CONFIG_READY'); // <--- and here
}
CART_CONFIG_READY
在另一个史诗正在收听它之前被发出。