我设置了一个史诗,等待另一个史诗完成,就像@jayphelps在这里的回答:从其他史诗中调用史诗
但是我发现它似乎只运行一次。之后,我可以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上的可运行示例:
this这绝对是一个棘手的人!
当state.config === true
您返回一个Observable时,CART_CONFIG_READY
它会同步发出,而在第一次期间,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
它基本上是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
所以它没有答案。然后,该调用栈返回,并且连接中的concat中的下一个Observable(我们的action$.ofType('CART_CONFIG_READY')
链)被订阅。但是为时已晚,它正在监听的动作已经发出!
解决此问题的一种方法是使CART_CONFIG_READY
总是异步发出,或者在我们调度之前在另一部史诗中开始监听它GET_CART_CONFIG
。
Observable.of
接受调度程序作为最后一个参数,RxJS支持其中的几个。
在这种情况下,您可以使用AsyncScheduler
(宏任务)或AsapScheduler
(微任务)。两者都可以在这种情况下工作,但是它们在JavaScript事件循环中的不同时间进行了安排。如果您不熟悉事件循环任务,请查看。
我个人建议AsyncSheduler
在这种情况下使用,因为它将提供与发出HTTP请求最接近的异步行为。
import { async } from 'rxjs/scheduler/async';
// later inside your epic...
return Observable.of({
type: 'CART_CONFIG_READY'
}, async);
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'
})
非常感谢jsbin btw,它使调试变得更加容易。
根据您的评论进行编辑:
出于好奇,您是通过经验还是调试来解决这个问题的?
两者结合。我已经处理了大量的异步/预定代码,排序通常是问题的根源。我扫描了代码,仔细地描绘了执行情况,注意到异步与同步的不同,具体取决于代码路径,然后我做了一个快速运算符,使我可以轻松地确定任何可观察链的订购顺序。
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
是在另一个史诗级文件监听之前发出的。
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句