如何在@ngrx/effects 中等待 2 个动作 [英] How to wait for 2 Actions in @ngrx/effects

查看:59
本文介绍了如何在@ngrx/effects 中等待 2 个动作的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

Effect 可以像 Promise.all 这样等待两个动作吗?示例:

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

@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();
      });
}

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

UPDATED What 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.html With promises it was quite obviouse:

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

在 ngrx/effects 中可以吗?还是 ngrx/effects 的方式不对?

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

答案

您可以使用的选项很少:

There are few options which you can use:

1) 不要使用通用操作.

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

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

优点:更容易调试

缺点:大量样板和操作

2) 使用带有嵌套操作的复杂流.

查看这篇文章: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(),
            ];
        }),
    );
}

优点:你可以实现你想要的

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

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.

3) 使用聚合器模式.

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

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

这是一个简单的例子:

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

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.

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

然后是我们的效果:

@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()),
            )
        }),
    );
}

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

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

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

Cons: the same as point 1 and 2

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

为您服务:

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) 使用 ngxs:https://github.com/ngxs/store

优点:样板代码较少,感觉就像是有棱有角的东西,增长很快

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

缺点:功能比 ngrx 少

Cons: has got less features than ngrx

推荐答案

我是 RXJS 的新手,但是这个怎么样.

I am new to RXJS but what about this.

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

You can remove {dispatch: false} if you change the tap to a 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),
        );
}

这篇关于如何在@ngrx/effects 中等待 2 个动作的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆