Rxjs Redux Observable-如何发送一个动作来启动一个单独的epic,然后等待epic响应(或超时)
所以我基本上有一个,这允许我通过WEBSOCKET发送普通消息,通过WEBSOCKET发送消息,通过WEBSOCKET接收消息 但是,在某些情况下,我希望以类似于Ajax REST调用的方式发出请求。例如,要为用户请求一份文档列表,我可能需要一份epic:Rxjs Redux Observable-如何发送一个动作来启动一个单独的epic,然后等待epic响应(或超时),rxjs,redux-observable,Rxjs,Redux Observable,所以我基本上有一个,这允许我通过WEBSOCKET发送普通消息,通过WEBSOCKET发送消息,通过WEBSOCKET接收消息 但是,在某些情况下,我希望以类似于Ajax REST调用的方式发出请求。例如,要为用户请求一份文档列表,我可能需要一份epic: 接收操作,例如({type:GET\u DOCUMENTS}) 生成一个随机密钥来跟踪当前请求,我们将其称为'request\u id' 发送({type:WEBSOCKET\u MESSAGE\u Send,request\u id})操作
({type:GET\u DOCUMENTS})
'request\u id'
({type:WEBSOCKET\u MESSAGE\u Send,request\u id})
操作({type:WEBSOCKET\u MESSAGE\u RECEIVED,request\u id,MESSAGE})
**必须具有匹配的“request\u id”,否则应忽略该操作。
- ->发出一个动作,例如
({type:GET\u DOCUMENTS\u SUCCESS,DOCUMENTS:message})
- ->发出一个动作,例如
({type:GET\u DOCUMENTS\u TIMEOUT})
我一直在努力把它放进代码中,我认为整个史诗中最尴尬的部分是我想在我的史诗中间发射一个动作等待。我觉得这不太对。。。ani模式?但我真的不知道该怎么做。没错。在史诗的中间没有一个好的行动方式。把史诗分成两半怎么样
const getDocumentsEpic=action$=>
动作$.pipe(
类型(“获取文档”),
地图(()=>{
const requestId=generateRequestId();
返回{
键入:“WEBSOCKET\u MESSAGE\u SEND”,
请求ID
};
})
);
const websocketMessageEpic=操作$=>
动作$.pipe(
类型(“WEBSOCKET\u MESSAGE\u SEND”),
开关映射(请求ID=>{
返回操作$.pipe(
类型(“WEBSOCKET\u消息\u已接收”),
过滤器(action=>action.requestId===requestId),
超时(10000),
映射({message})=>({
键入:“获取文档\u成功”,
文件:信息
})),
catchError(()=>的({type:“GET_DOCUMENTS\u TIMEOUT”}))
);
})
);
更新答案(2020-04-17):
我对我最初的答案不满意,所以决定再试一次
NotificationOperators.js
import { of } from 'rxjs';
import { map, switchMap, filter, timeout, catchError, first, mergeMap } from 'rxjs/operators';
import { notificationActionTypes } from '../actions';
const NOTIFICATION_TIMEOUT = 60 * 1000;
const generateRequestId = () => Math.random().toString(16).slice(2);
const toNotificationRequest = notificationRequest => input$ =>
input$.pipe(mergeMap(async action => ({
type: notificationActionTypes.WEBSOCKET_MESSAGE_SEND,
message: {
request_id: generateRequestId(),
...(
typeof notificationRequest === "function" ?
await Promise.resolve(notificationRequest(action)) :
({ eventType: notificationRequest })
)
}
})));
const mapNotificationRequestResponses = (notificationRequest, mapper) => $input =>
$input.pipe(
filter(action =>
action.type === notificationActionTypes.WEBSOCKET_MESSAGE_SEND &&
action.message.eventType === notificationRequest),
concatMap(sendAction =>
$input.pipe(
filter(receiveAction => {
return (
receiveAction.type === notificationActionTypes.WEBSOCKET_MESSAGE_RECEIVED &&
receiveAction.message.request_id === sendAction.message.request_id
)
}),
first(),
timeout(NOTIFICATION_TIMEOUT),
map(({ message }) => mapper(message.success ? false : message.error, message.result, sendAction.message)),
catchError(errorMessage => of(mapper(errorMessage && errorMessage.message, null, sendAction.message))))));
export { toNotificationRequest, mapNotificationRequestResponses };
用法:
export const getDocumentsReqEpic = action$ => action$.pipe(
ofType(documentActionTypes.REFRESH_DOCUMENTS_REQUEST),
toNotificationRequest(EventTypes.get_user_documents_req)
);
export const getDocumentsRecEpic = action$ => action$.pipe(
mapNotificationRequestResponses(
EventTypes.get_user_documents_req,
(error, result) => error ? refreshDocumentsError(error) : refreshDocumentsSuccess(result))
);
原始答案:
因为我觉得我可能需要多次重复这个过程,这似乎是一个合理数量的复制样板,我应该创建一个方法来根据需求生成epics。出于这个原因,我扩展了@sneas令人敬畏的答案,并在下面发布了它,以帮助其他人
注意:此实现假定websocket实现来自。它还假设服务器websocket实现将接受“请求id”并使用相同的“请求id”进行响应,以便可以链接请求和响应消息。可能还值得注意的是,“epicLinkId”仅用于客户端,只允许创建的两个Epic相互链接,否则您只能调用createNotifyReqResEpics()
一次
createNotifyReqResEpics.js(基于上述代码的助手)
documents.js(epics)
从documents.js中导出的两部史诗将其转化为combineEpics。感谢您的回答,让我走上正确的道路,并使我能够根据自己的需要扩展它。
import { ofType } from 'redux-observable';
import { of } from 'rxjs';
import { map, switchMap, filter, timeout, catchError, first } from 'rxjs/operators';
import { notificationActionTypes } from '../actions';
const generateRequestId = () => Math.random().toString(16).slice(2);
export default ({
requestFilter,
requestMessageMapper,
responseMessageMapper
}) => {
if (typeof requestFilter !== "function")
throw new Error("Invalid function passed into createNotifyReqResEpics 'requestFilter' argument.");
if (typeof requestMessageMapper !== "function")
throw new Error("Invalid function passed into createNotifyReqResEpics 'requestMessageMapper' argument.");
if (typeof responseMessageMapper !== "function")
throw new Error("Invalid function passed into createNotifyReqResEpics 'responseMessageMapper' argument.");
const epicLinkId = generateRequestId();
const websocketSendEpic = action$ =>
action$.pipe(
filter(requestFilter),
map(action => ({
epic_link_id: epicLinkId,
type: notificationActionTypes.WEBSOCKET_MESSAGE_SEND,
message: {
request_id: generateRequestId(),
...requestMessageMapper(action)
}
}))
);
const websocketReceiveEpic = action$ =>
action$.pipe(
ofType(notificationActionTypes.WEBSOCKET_MESSAGE_SEND),
filter(action => action.epic_link_id === epicLinkId),
switchMap(sendAction =>
action$.pipe(
ofType(notificationActionTypes.WEBSOCKET_MESSAGE_RECEIVED),
filter(receiveAction => receiveAction.request_id === sendAction.request_id),
first(),
timeout(10000),
map(receiveAction => responseMessageMapper(false, receiveAction.message)),
catchError(errorMessage => of(responseMessageMapper(errorMessage && errorMessage.message, null))))));
return [websocketSendEpic, websocketReceiveEpic];
};
import EventTypes from '../shared-dependencies/EventTypes';
import { documentActionTypes, refreshDocumentsError, refreshDocumentsSuccess } from '../actions';
import { createNotifyReqResEpics } from '../utils';
const [getDocumentsReqEpic, getDocumentsRespEpic] = createNotifyReqResEpics({
requestFilter: action => action.type === documentActionTypes.REFRESH_DOCUMENTS_REQUEST,
requestMessageMapper: action => ({ eventType: EventTypes.get_user_documents_req }),
responseMessageMapper: (error, action) => error ? refreshDocumentsError(error) : refreshDocumentsSuccess(action.result)
});
export { getDocumentsReqEpic, getDocumentsRespEpic };