单个史诗中的多个异步操作执行顺序

Multiple async action execution order in single epic

我正在尝试设置 loginEpic,它在用户登录时发生。这就是逻辑流程的工作方式:

史诗应该从 LOGIN_REQUEST 动作开始。成功完成登录承诺后,应使用 SYNC_USER_REQUEST 操作获取用户信息(这基本上是整个其他史诗,因为在最初进入站点以获取用户信息或重定向到登录时也会调用它)。一旦成功完成(promise 和 SUCCESS/FAIL 调用在 syncUserEpic 中处理),SYNC_USER_SUCCESS 应该在 loginEpic 中捕获并且 LOGIN_SUCCESS 应该与 push 将用户重定向到起始页的操作。

这是我目前拥有的:

const loginEpic: Types.RootEpic = (action$) => 
    action$.ofType("LOGIN_REQUEST").pipe(
        switchMap(({ payload }) => 
            from(membershipService.login(payload.username, payload.password)).pipe(
                switchMap((response) => [
                    syncUser.request(), 
                    action$.ofType("SYNC_USER_SUCCESS").pipe( // I think this is the problem
                        filter(isActionOf(syncUser.success)),
                        map(r => login.success(response))
                    )
                ]),
                takeUntil(action$.ofType(["LOGOUT_REQUEST", "LOGIN_CANCEL"])),
                catchError(error => of(login.failure(error))),
                endWith(push("/"))
            )
        )
    )

但我得到

Actions must be plain objects. Use custom middleware for async actions.

我也在取消 LOGIN_REQUEST/SYNC_USER_REQUESTLOGOUT_REQUESTLOGIN_CANCEL 操作,并处理登录错误(我不确定我是否应该也处理此处同步用户错误)

我是这样实现的:

const loginEpic: Types.RootEpic = (action$) =>
    action$.pipe(
        filter(isActionOf(login.request)),
        switchMap(({ payload }) =>
            race(
                from(membershipService.login(payload.username, payload.password)).pipe(
                    mergeMap((token) =>
                        from(membershipService.getUser()).pipe(
                            mergeMap((user) => [syncUser.success(user), login.success(token)]),
                            catchError(error => {
                                // user fetch failed, tell login that user is responsible
                                return of(login.failure(new BaseError("6010", "User sync failed.")));
                            })
                        )
                    ),
                    catchError(error => of(login.failure(error))),
                ),
                action$.pipe(
                    filter(isActionOf(logout.request)),
                    map(() => login.cancel()),
                    take(1)
                )
            )

        )
    )

根据它的工作原理,race 实际上是在整个第一个街区比赛(所以不仅仅是外部 membershipService.login,还有 logout.request 动作。我没有意识到这一点,我我以为 membershipService.getUser 需要另一个 race 条件,因为我需要的是 logout.request 终止整个过程(即使 login 成功并在获取用户的过程中处理)。据我测试,它按预期工作。

如果有人有任何想法、改进、修复或反模式观察,请告诉我。