ngrx Effects: Are actions dispatched by one effect processed immediately by other effects?

与世无争的帅哥 提交于 2019-12-07 05:40:51

问题


I have an Angular (2) app with four ngrx actions:

  • START
    • Not processed by the reducer (no state change)
    • ngrx Effect calls an async task and maps to SUCCESS or ERROR
  • SUCCESS
    • Processed by the reducer
    • ngrx Effect maps to ADVANCE
  • ADVANCE
    • Not processed by the reducer
    • ngrx Effect navigates to a different route
  • ERROR
    • Processed by the reducer
    • No Effect

The problem is that the Effect that catches ADVANCE seems to run before the reducer that processes SUCCESS

Here's the Effects code:

@Effect() start$ = this.actions$
    .ofType('START')
    .map(toPayload)
    .switchMap(input => doAsyncTask(input)
        .map(result => ({type: 'SUCCESS', payload: result}))
        .catch(error => ({type: 'ERROR', payload: error})));

@Effect() success$ = this.actions$
    .ofType('SUCCESS')
    .map(() => ({type: 'ADVANCE'}));

@Effect({dispatch: false}) advance$ = this.actions$
    .ofType('ADVANCE')
    .withLatestFrom(this.store$.select(state => state.route))
    .map(action_route => action_route[1])
    .do(route => this.router.navigate([route.foo.bar]));

The error that I am getting is Cannot read property 'bar' of null. The property foo is set by the reducer that processes SUCCESS.

If I add a delay to the SUCCESS effect, it all works nicely:

@Effect() success$ = this.actions$
    .ofType('SUCCESS')
    .delay(1)
    .map(() => ({type: 'ADVANCE'}));

But having to add this delay doesn't make sense to me.

I added console.log statements everywhere and the output looks like this:

  1. SUCCESS effect
  2. ADVANCE effect (showing route.foo === null)
  3. SUCCESS reducer (showing route.foo === something)
  4. Error

I expected the SUCCESS effect and the SUCCESS reducer to run before the ADVANCE effect.

Am I doing something wrong?

Is it incorrect to expect that actions are processed by the reducers in the same order that they are dispatched?


Versions:

  • @angular/cli: 1.0.0-beta.32.3
  • node: 7.5.0
  • os: darwin x64
  • @angular/common: 2.4.7
  • @angular/compiler: 2.4.7
  • @angular/core: 2.4.7
  • @angular/forms: 2.4.7
  • @angular/http: 2.4.7
  • @angular/platform-browser: 2.4.7
  • @angular/platform-browser-dynamic: 2.4.7
  • @angular/router: 3.4.7
  • @angular/cli: 1.0.0-beta.32.3
  • @angular/compiler-cli: 2.4.7
  • @ngrx/core@1.2.0
  • @ngrx/effects@2.0.0
  • @ngrx/store@2.2.1
  • rxjs: 5.1.1

回答1:


To answer your concluding questions:

  • Am I doing something wrong?
  • No, I don't reckon you are doing anything wrong.
  • Is it incorrect to expect that actions are processed by the reducers in the same order that they are dispatched?
  • That order would seem logical but apparently you can't expect that at the moment

There seems to be a bug causing this behaviour. You are most probably experiencing it since you are directly mapping to another action. Usually, if you have an asynchronous operation or something like that, the reducer has time to finish before the effect listening to the next action starts.

Perhaps not a part of your question, but a solution to your specified problem would be to navigate to your new route directly in SUCCESS with the help of your payload, or pass the payload to ADVANCE.

Links below are reported issues in effects-, store- and rxjs-projects that are related to yours:

  • https://github.com/ngrx/effects/issues/58
  • https://github.com/ngrx/store/issues/279
  • /ReactiveX/rxjs/issues/2155 (too little reputation to link to this one as well)

Seems like they're working on it :)




回答2:


It might work to replace your .map in the catch of the START action with a .concatMap and put both your map to SUCCESS and to ACTION there.

@Effect() start$ = this.actions$
    .ofType('START')
    .map(toPayload)
    .switchMap(input => doAsyncTask(input)
        .concatMap(result => {
            return Observable.from([type: 'SUCCESS', payload: result,
                                    type: 'ADVANCE'])
        .catch(error => ({type: 'ERROR', payload: error})));



回答3:


The purpose of ngex/effects is to act as the middle-ware in a redux based environment.

So what you are basically doing here is calling the 'success' action that should work on both reducer and continue to another action is the middle-ware. But that's the consonant of effecs, It should intercept actions and handle them by itself, Only after is async handling it dispatch action.

So what is happening here? you dispatch success to two different places, one should handle by the reducer and one by the middle-ware. I'm not sure if it always like that but middle-wares will intercept incoming action before it hits the reducer. I think that if you'll modify you code a little bit it should work. Try to do something like :

@Effect() start$ = this.actions$
    .ofType('START')
    .map(toPayload)
    .switchMap(input => doAsyncTask(input)
        .map(result =>{
               this.store$.dispatch(
               {type: 'SUCCESS',
                payload: result
                });
               this.store$.dispatch(
               {type: 'ADVANCE'})

}

        .catch(error => ({type: 'ERROR', payload: error})));

You see what I'm doing here? I've removed the middle-ware that intercept 'success', So not the 'success' hits the reducer. Right after it it dispatch another action of type 'advance' that should hit your middle-ware after the 'success' is done been handling.

Hope it help's.



来源:https://stackoverflow.com/questions/42731937/ngrx-effects-are-actions-dispatched-by-one-effect-processed-immediately-by-othe

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!