gpt4 book ai didi

rxjs - 链式 redux-observable 史诗仅正确触发一次

转载 作者:行者123 更新时间:2023-12-02 20:38:44 32 4
gpt4 key购买 nike

我已经设置了一个史诗,等待另一个史诗完成,就像@jayphelps在这里的回答:Invoking epics from within other epics

但是我发现它似乎只运行一次。之后,我可以在控制台中看到 CART_CONFIG_READY 操作,但 DO_THE_NEXT_THING 操作未触发。

我尝试了 mergeMapswitchMap 的各种组合,无论有没有 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 上可运行的示例:

https://jsbin.com/vovejibuwi/1/edit?js,console

最佳答案

这绝对是一个棘手的问题!

原因

何时 state.config === true你返回一个 Observable CART_CONFIG_READY同步发出,而在第一次 http 请求(或延迟,在 jsbin 中)意味着它总是异步的。

为什么这会产生影响,请参见 checkCart史诗般的,你返回一个可观察的链,监听 CART_CONFIG_READYaction$.ofType('CART_CONFIG_READY')但也适用 .startWith({ type: 'GET_CART_CONFIG' }) 。这意味着GET_CART_CONFIG将在 之前 action$.ofType('CART_CONFIG_READY') 同步发出已订阅,因为 startWithbasically 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 中监听它。所以它没有得到答复。然后该调用堆栈返回,并返回 concat 中的下一个 Observable,即我们的 action$.ofType('CART_CONFIG_READY')链,被订阅。但为时已晚,它监听的 Action 已经发出了!

解决方案

解决此问题的一种方法是发出 CART_CONFIG_READY始终异步,或者在我们调度 GET_CART_CONFIG 之前开始在另一个史诗中监听它.

1。发出 CART_CONFIG_READY 异步

Observable.of接受调度程序作为其最后一个参数,并且 RxJS supports several of them

在这种情况下,您可以使用 AsyncScheduler (宏任务)或 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

因为startWithconcat 的简写(我们不想这样做)我们需要使用某种形式的 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 顺便说一句,它使调试变得容易容易。


根据您的评论进行编辑:

Out of curiosity did you figure this out through experience or debugging?

两者的结合。我处理过大量的异步/预定代码,而排序通常是问题的根源。我扫描了代码,在脑海中想象执行情况,注意到异步与同步取决于代码路径的差异,然后我创建了一个快速运算符,让我可以轻松确认任何 Observable 链的订阅顺序。

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在另一个史诗监听它之前就被发出了。

关于rxjs - 链式 redux-observable 史诗仅正确触发一次,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46450262/

32 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com