单个史诗中的多个异步操作执行顺序
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_REQUEST
和 LOGOUT_REQUEST
或 LOGIN_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
成功并在获取用户的过程中处理)。据我测试,它按预期工作。
如果有人有任何想法、改进、修复或反模式观察,请告诉我。
我正在尝试设置 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_REQUEST
和 LOGOUT_REQUEST
或 LOGIN_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
成功并在获取用户的过程中处理)。据我测试,它按预期工作。
如果有人有任何想法、改进、修复或反模式观察,请告诉我。