typescript 如何在@ngrx/effects 中等待 2 个动作

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/44593306/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-21 04:38:36  来源:igfitidea点击:

How to wait for 2 Actions in @ngrx/effects

angulartypescriptngrxngrx-effectsngrx-store

提问by E. Efimov

Can effect wait two actions like Promise.all? Example:

effect 可以像 Promise.all 一样等待两个动作吗?例子:

@Effect()
pulic addUser() {
   return this.actions$.ofType(user.ADD)
      .switchMap(() => {
         return this.userService.add();
      })
      .map(() => {
         return new user.AddSuccessAction();
      });
}

@Effect()
pulic addUserOptions() {
   return this.actions$.ofType(userOptions.ADD)
      .switchMap(() => {
         return this.userOptionsService.add();
      })
      .map(() => {
         return new userOptions.AddSuccessAction();
      });
}

@Effect()
public complete() {
   return this.actions$.ofType(user.ADD_SUCCESS, userOptions.ADD_SUCCESS)
      // how to make it works like Promise.all ?
      .switchMap(() => {
         return this.statisticService.add();
      })
      .map(() => {
         return new account.CompleteAction();
      });
}

UPDATEDWhat I want to achieve is simillar behavior to Promise.all. How to dispatch two effects in parallel, wait until all the effects are resolved, then dispatch a third action. Something like https://redux-saga.js.org/docs/advanced/RunningTasksInParallel.htmlWith promises it was quite obviouse:

更新我想要实现的是与 Promise.all 类似的行为。如何并行调度两个效果,等到所有效果都解决了,然后调度第三个动作。像https://redux-saga.js.org/docs/advanced/RunningTasksInParallel.html 之类的东西,有承诺就很明显了:

Promise.all([fetch1, fetch2]).then(fetch3);

Is it possible in ngrx/effects? Or is it a wrong way in ngrx/effects?

在 ngrx/effects 中可能吗?或者它是 ngrx/effects 中的错误方式?

ANSWER

回答

There are few options which you can use:

您可以使用以下几个选项:

1) Do not use generic actions.

1) 不要使用通用操作。

Follow these rules from Myke Ryan's presentation: https://youtu.be/JmnsEvoy-gY

遵循 Myke Ryan 演讲中的这些规则:https://youtu.be/JmnsEvoy-gY

Pros: easier to debug

优点:更容易调试

Cons: tons of boilerplate and actions

缺点:大量的样板和动作

2) Use complex stream with nested actions.

2) 使用具有嵌套操作的复杂流。

Check this article: https://bertrandg.github.io/ngrx-effects-complex-stream-with-nested-actions/

查看这篇文章:https: //bertrandg.github.io/ngrx-effects-complex-stream-with-nested-actions/

Here is simple example for two actions:

这是两个操作的简单示例:

@Effect()
public someAction(): Observable<Action> {
    return this.actions$.pipe(
        ofType(actions.SOME_ACTION),
        map((action: actions.SomeAction) => action.payload),
        mergeMap((payload) => {
            const firstActionSuccess$ = this.actions$.pipe(
                ofType(actions.FIRST_ACTION_SUCCESS),
                takeUntil(this.actions$.pipe(ofType(actions.FIRST_ACTION_FAIL))),
                first(),
            );

            const secondActionsSuccess$ = this.actions$.pipe(
                ofType(actions.SECOND_ACTION_SUCCESS),
                takeUntil(this.actions$.pipe(ofType(actions.SECOND_ACTION_FAIL))),
                first(),
            );

            const result$ = forkJoin(firstActionSuccess$, secondActionsSuccess$).pipe(
                first(),
            )
                .subscribe(() => {
                    // do something
                });

            return [
                new actions.FirstAction(),
                new actions.SecondAction(),
            ];
        }),
    );
}

Pros: you can achieve what you want

优点:你可以达到你想要的

Cons: complex stream is too complex to support :) looks ugly and may quickly become to hell, observables won't unsubscribe until succes or fail actions, it means that in theory any third-party actions can emit signals to these observables.

缺点:复杂的流太复杂而无法支持:) 看起来很丑,可能很快就会变成地狱,直到操作成功或失败,observable 才会取消订阅,这意味着理论上任何第三方操作都可以向这些 observable 发出信号。

3) Use aggregator pattern.

3)使用聚合器模式。

Check Victor Savkin's presentation about State Management Patterns and Best Practices with NgRx: https://www.youtube.com/watch?v=vX2vG0o-rpM

查看 Victor Savkin 关于 NgRx 的状态管理模式和最佳实践的演示:https: //www.youtube.com/watch?v =vX2vG0o-rpM

Here is simple example:

这是一个简单的例子:

First you need to create actions with correlationId param. CorrelationId should be uniq, it may be some guid for example. This ID you will use in your chain of actions to identify your actions.

首先,您需要使用correlationId 参数创建操作。CorrelationId 应该是 uniq,例如它可能是一些 guid。您将在操作链中使用此 ID 来识别您的操作。

export class SomeAction implements Action {
    public readonly type = SOME_ACTION;

    constructor(public readonly correlationId?: string | number) { }
    // if you need payload, then make correlationId as a second argument
    // constructor(public readonly payload: any, public readonly correlationId?: string | number) { }
}

export class SomeActionSuccess implements Action {
    public readonly type = SOME_ACTION_SUCCESS;

    constructor(public readonly correlationId?: string | number) { }
}

export class FirstAction implements Action {
    public readonly type = FIRST_ACTION;

    constructor(public readonly correlationId?: string | number) { }
}

export class FirstActionSuccess implements Action {
    public readonly type = FIRST_ACTION_SUCCESS;

    constructor(public readonly correlationId?: string | number) { }
}

// the same actions for SecondAction and ResultAction

Then our effects:

然后我们的效果:

@Effect()
public someAction(): Observable<Action> {
    return this.actions$.pipe(
        ofType(actions.SOME_ACTION),
        mergeMap((action: actions.SomeAction) => {
            return [
                new actions.FirstAction(action.corelationId),
                new actions.SecondAction(action.corelationId),
            ];
        }),
    );
}

@Effect()
public firstAction(): Observable<Action> {
    return this.actions$.pipe(
        ofType(actions.FIRST_ACTION),
        switchMap((action: actions.FirstAction) => {
            // something
            ...map(() => new actions.FirstActionSuccess(action.correlationId));
        }),
    );
}
// the same for secondAction

@Effect()
public resultAction(): Observable<Action> {
    return this.actions$.pipe(
        ofType(actions.SOME_ACTION),
        switchMap((action: actions.SomeAction) => {
            const firstActionSuccess$ = this.actions$.pipe(
                ofType(actions.FIRST_ACTION_SUCCESS),
                filter((t: actions.FirstActionSuccess) => t.correlationId === action.correlationId),
                first(),
            );

            const secondActionsSuccess$ = this.actions$.pipe(
                ofType(actions.SECOND_ACTION_SUCCESS),
                filter((t: actions.SecondActionSuccess) => t.correlationId === action.correlationId),
                first(),
            );

            return zip(firstActionSuccess$, secondActionsSuccess$).pipe(
                map(() => new actions.resultSuccessAction()),
            )
        }),
    );
}

Pros: the same as point 2, but no third-party actions.

优点:与第 2 点相同,但没有第三方操作。

Cons: the same as point 1 and 2

缺点:与第 1 点和第 2 点相同

4) Do not use effects for API. Use good old services which emulate effects but return Observable.

4) 不要对 API 使用效果。使用模拟效果但返回 Observable 的旧服务。

In you service:

为您服务:

public dispatchFirstAction(): Observable<void> {
    this.store.dispatch(new actions.FirstAction(filter));

    return this.service.someCoolMethod().pipe(
        map((data) => this.store.dispatch(new actions.FirstActionSuccess(data))),
        catchError((error) => {
            this.store.dispatch(new actions.FirstActionFail());

            return Observable.throw(error);
        }),
    );
}

So you can combine it anywhere later, like:

所以你以后可以在任何地方组合它,比如:

const result1$ = this.service.dispatchFirstAction();
const result2$ = this.service.dispatchSecondAction();

forkJoin(result1$, result2$).subscribe();

5) Use ngxs: https://github.com/ngxs/store

5)使用ngxs:https: //github.com/ngxs/store

Pros: less boilerplate, this feels like angular stuff, it grows fast

优点:更少的样板,这感觉就像有棱有角的东西,它增长得很快

Cons: has got less features than ngrx

缺点:功能比 ngrx 少

回答by codeBelt

I am new to RXJS but what about this.

我是 RXJS 的新手,但是这个呢。

You can remove {dispatch: false}if you change the tapto a switchMap.

{dispatch: false}如果将 更改为tap,则可以删除switchMap

@Effect({dispatch: false})
public waitForActions(): Observable<any> {
    const waitFor: string[] = [
        SomeAction.EVENT_1,
        SomeAction.EVENT_2,
        SomeAction.EVENT_3,
    ];

    return this._actions$
        .pipe(
            ofType(...waitFor),
            distinct((action: IAction<any>) => action.type),
            bufferCount(waitFor.length),
            tap(console.log),
        );
}

回答by deumax

Using Observable.combineLatestworks for me.

使用Observable.combineLatest对我有用

@Effect()
  complete$ = this.actions$.ofType<Action1>(ACTION1).combineLatest(this.actions$.ofType<Action2>(ACTION2),
    (action1, action2) => {

      return new Action3();
    }
  ).take(1);

take(1) results in dispatching Action3() only once.

take(1) 导致只分派 Action3() 一次。

回答by Roman Rhrn Nesterov

Another combineLatestversion with pipesand switchMap

combineLatest带有pipes和的另一个版本switchMap

import { Observable, of } from 'rxjs'
import { combineLatest, switchMap, withLatestFrom } from 'rxjs/operators'

@Effect()
someEffect$: Observable<Actions> = this.actions$.pipe(
  ofType(Action1),
  combineLatest(this.actions$.ofType(Action2)),
  switchMap(() => of({ type: Action3 }))
)