链式可观察到的Redux史诗只能正确触发一次

丹尼尔·克里斯普

我设置了一个史诗,等待另一个史诗完成,就像@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上的可运行示例:

https://jsbin.com/vovejibuwi/1/edit?js,控制台

Jayphelps

this这绝对是一个棘手的人!

原因

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')订阅之前同步发出因为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

1.发出CART_CONFIG_READY异步

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);

2.CART_CONFIG_READY发射前先GET_CART_CONFIG

因为startWithconcat(我们不想这样做)的简写,所以我们需要使用某种形式的mergeofType首先使用我们的链,以便我们在发出之前进行监听。

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] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

RxJava:如何使一次获取和重用可观察到?

只能对可观察作品进行一次改造

第一次调度后可观察到的redux多个动作

手动触发可观察到的?

可观察到的Redux:为多个动作触发相同的史诗

Redux可观察到的史诗链调度重复动作

可观察到的Redux:在多次点击(两次或更多次)上分派操作

在Redux可观察到的场景中对多个史诗进行编排和排序

取消先前的请求,仅使用可观察到的redux触发最新请求

可观察到与多个订户执行一次

跳过一次可观察到的延迟

触发可观察到的主题的onNext并共享结果

可观察到的开关/ flatMap立即触发

redux-observable和rxjs。将诺言转换为可观察-史诗仅被调用一次

在可观察到的redux中,我如何在执行任何其他操作之前触发一个操作

通过OnInit可观察到没有以角度触发

从最后一次观察到第一次观察在Stata中模拟AR(1)

如何在史诗中使用“ const”?Redux可观察到的ES6箭头功能

在Redux可观察到的史诗中的所有动作之后触发一个动作

Redux可观察史诗中的IF

任一可观察到的更新时触发代码

ngIf块内部可观察到的角度rxjs错过了第一次更新

可观察到的Redux-如何发送动作以启动单独的史诗,然后等待该史诗响应(或超时)

在ConcatMap中使用Delay只能观察到一次

如何在可观看Redux的史诗中链接可观察到的RxJS?

从组件订阅一个可观察到的两次无效

RxSwift)订阅了一个可观察到的两次无法正常工作

可观察到的集合仅删除项目一次,当再次尝试不起作用时-Windows Phone 8-C#

可观察到可观察的顺序