angular - چگونه برای ۲ اکشن در @ngrx/effects انتظار بکشیم


آیا افکت میتواند مانند 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();
      });
}

بهروزرسانی شده چیزی که میخواهم به آن برسم، رفتار مشابه با Promise.all است. چگونه میتوان دو افکت را بهصورت موازی اجرا کرد، تا زمانی که همه افکتها حل شوند صبر کرد و سپس سومین اکشن را دیسپچ کرد. چیزی شبیه به https://redux-saga.js.org/docs/advanced/RunningTasksInParallel.html با پرامیسها این موضوع کاملاً واضح بود:

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

آیا این در ngrx/effects ممکن است؟ یا راه اشتباهی در ngrx/effects است؟

پاسخ

چند گزینه وجود دارد که میتوانید از آنها استفاده کنید:

1) از اقدامات کلی استفاده نکنید.

این قوانین را از ارائه مایک رایان دنبال کنید: https://youtu.be/JmnsEvoy-gY

مزایا: اشکالزدایی آسانتر

معایب: حجم زیادی از کدهای تکراری و اقدامات

2) از جریان پیچیده با اقدامات تو در تو استفاده کنید.

این مقاله را بررسی کنید: https://bertrandg.github.io/ngrx-effects-complex-stream-with-nested-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(),
            ];
        }),
    );
}

مزایا: شما میتوانید به آنچه میخواهید برسید

معایب: جریان پیچیده بیش از حد پیچیده است و نمیتواند پشتیبانی شود :) ظاهری زشت دارد و ممکن است سریعاً خراب شود، ناظرها تا زمانی که عملیات موفق یا شکست رخ ندهد لغو اشتراک نمیشوند، این بدان معناست که به طور نظری هر عملیات شخص ثالث میتواند سیگنالهایی به این ناظرها ارسال کند.

۳) از الگوی جمعکننده استفاده کنید.

ارائه ویکتور ساوکین درباره الگوهای مدیریت وضعیت و بهترین شیوهها با NgRx را بررسی کنید: https://www.youtube.com/watch?v=vX2vG0o-rpM

در اینجا یک نمونه ساده است:

اول باید اقداماتی با پارامتر correlationId ایجاد کنید. CorrelationId باید یکتا باشد، برای مثال میتواند یک guid باشد. این شناسه را در زنجیره اقدامات خود برای شناسایی اقداماتتان استفاده خواهید کرد.

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

مزایا: مانند نکته ۲، اما بدون اقدامات شخص ثالث.

معایب: همانند نکات 1 و 2

۴) برای 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);
        }),
    );
}

پس میتوانید بعداً آن را هر جایی ترکیب کنید، مثل:

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

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

۵) استفاده از ngxs: https://github.com/ngxs/store

مزایا: کدهای تکراری کمتر، این شبیه چیزهای انگولار است، سریع رشد میکند

معایب: امکانات کمتری نسبت به ngrx دارد

پاسخ شماره ۱

من تازه به RXJS آمدهام اما در مورد این چطور؟

میتوانید {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),
        );
}

پاسخ شماره ۲

این برای من در ngrx 8 کار کرد

waitFor2Actions$ = createEffect(() =>
    combineLatest([
      this.actions$.pipe(ofType(actions.action1)),
      this.actions$.pipe(ofType(actions.action2)),
    ]).pipe(
      switchMap(() => ...),
    )
  );