发出操作,然后使用redux observable和rxjs请求并发出另一个操作

发出操作,然后使用redux observable和rxjs请求并发出另一个操作,redux,rxjs,redux-observable,Redux,Rxjs,Redux Observable,因此,我有一个epic,它接收一个SUBMIT_登录操作,然后它应该启动generateDeviceId函数,该函数返回一个id为有效负载的操作。在reducer处理并更新存储之后,它应该请求登录,然后将其解析为存储,最后将用户重定向到我们的仪表板 const generateDeviceId = (deviceId) => (({type: GENERATE_DEVICE_ID, payload: deviceId})); const resolveLogin = (response

因此,我有一个epic,它接收一个SUBMIT_登录操作,然后它应该启动generateDeviceId函数,该函数返回一个id为有效负载的操作。在reducer处理并更新存储之后,它应该请求登录,然后将其解析为存储,最后将用户重定向到我们的仪表板

const generateDeviceId = (deviceId)  => (({type: GENERATE_DEVICE_ID, payload: deviceId}));

const resolveLogin = (response) => ({type: RESOLVE_LOGIN, payload: response});

const submitLogin = (email, password) => ({type: SUBMIT_LOGIN, payload: {email, password}});

const requestLogin = (email, password) => ({type: REQUEST_LOGIN, payload: {email, password}});

const loadAbout = () => ({type: LOAD_ABOUT});

const submitLoginEpic = (action$) =>
        action$
            .ofType(SUBMIT_LOGIN)
            .mapTo(generateDeviceId(uuidv1()))
                .flatMap(({payload}) => login(payload.email, payload.password)
                    .flatMap(({response}) => [resolveLogin(response.content), loadAbout()])
                );
ps:
login
函数是来自
rx-dom
ajax
函数,它返回一个流:

const AjaxRequest = (method, url, data) => {

    const state = store.getState();
    const {token, deviceId} = state.user;

    return ajax({
        method,
        timeout: 10000,
        body: data,
        responseType: 'json',
        url: url,
        headers: {
            token,
            'device-id': deviceId,
            'Content-Type': 'application/json'
        }
    });

};



 const login = (email, password) => AjaxRequest('post', 'sign_in', {email, password});
ps2:
uuidv1
函数只生成一个随机键(它是一个库)

我认为(事实上我肯定)我做错了,但两天后我真的不知道如何继续/

更新 在Sergey的第一次更新之后,我将epic改为epic,但不幸的是,由于某种原因,
RXDOM的
ajax不能像Sergey的login$observable那样工作。我们目前正在做这个

const generateDeviceId = (deviceId)  => (({type: GENERATE_DEVICE_ID, payload: deviceId}));

const resolveLogin = (response) => ({type: RESOLVE_LOGIN, payload: response});

const submitLogin = (email, password) => ({type: SUBMIT_LOGIN, payload: {email, password}});

const requestLogin = (email, password) => ({type: REQUEST_LOGIN, payload: {email, password}});

const loadAbout = () => ({type: LOAD_ABOUT});

const submitLoginEpic = action$ =>
  action$.ofType(SUBMIT_LOGIN)
    .mergeMap(({payload}) =>
      Observable.of(generateDeviceId(uuid()))
        .concat(login(payload.email, payload.password)
          .concatMap(({response}) => [resolveLogin(response.content), loadAbout()])
更新2 在Sergey的第二次更新之后,我再次更改了代码,最终得到了一个解决方案,我使用了
两个epics
.concatMap
操作符,以便
同步地
调度操作,并且它
按预期工作

const generateDeviceId = (deviceId)  => (({type: GENERATE_DEVICE_ID, payload: deviceId}));

const resolveLogin = (response) => ({type: RESOLVE_LOGIN, payload: response});

const submitLogin = (email, password) => ({type: SUBMIT_LOGIN, payload: {email, password}});

const requestLogin = (email, password) => ({type: REQUEST_LOGIN, payload: {email, password}});

const loadAbout = () => ({type: LOAD_ABOUT});

const submitLoginEpic = (action$) =>
  action$
    .ofType(SUBMIT_LOGIN)
    .concatMap(({payload}) => [
      generateDeviceId(uuid()),
      requestLogin(payload.email, payload.password)
    ]);

const requestLoginEpic = (action$) =>
  action$
    .ofType(REQUEST_LOGIN)
    .mergeMap(({payload}) => login(payload.email, payload.password)
      .concatMap(({response}) => [resolveLogin(response.content), loadAbout()])

如果我没弄错,您希望您的epic生成以下操作序列,以响应每个
SUBMIT\u登录

GENERATE_DEVICE_ID -- RESOLVE_LOGIN -- LOAD_ABOUT
另外,我猜在收到
SUBMIT\u LOGIN
后需要立即发布
GENERATE\u DEVICE\u ID
, 而
RESOLVE\u LOGIN
LOAD\u ABOUT
只能在
LOGIN()返回的流发出后发出

如果我的猜测是正确的,那么您只需要启动嵌套的observable(每个
SUBMIT\u登录创建的一个)
使用
GENERATE\u DEVICE\u ID
操作和
startWith
操作,操作员可以准确地执行以下操作:

const submitLoginEpic = action$ =>
    action$.ofType(SUBMIT_LOGIN)
        .mergeMap(({ payload }) =>
            login(payload.email, payload.password)
                .mergeMap(({ response }) => Rx.Observable.of(resolveLogin(response.content), loadAbout()))
                .startWith(generateDeviceId(uuidv1()))
        );
更新:一种可能的替代方法是使用
concat
操作符:
obs1。concat(obs2)
仅在
obs1
完成时订阅
obs2

另外请注意,如果在调度
生成设备\u ID
后需要调用
login()
,您可能希望将其包装在一个“冷”的可观察对象中:

const login$ = payload =>
    Rx.Observable.create(observer => {
        return login(payload.email, payload.password).subscribe(observer);
    });

const submitLoginEpic = action$ =>
    action$.ofType(SUBMIT_LOGIN)
        .mergeMap(({ payload }) =>
            Rx.Observable.of(generateDeviceId(uuidv1()))
                .concat(login$(payload).map(({ response }) => resolveLogin(response.content)))
                .concat(Rx.Observable.of(loadAbout()))
        );
这样,在调用
login()
之前会发出
GENERATE\u DEVICE\u ID
,也就是说,序列是

GENERATE_DEVICE_ID -- login() -- RESOLVE_LOGIN -- LOAD_ABOUT
更新2:之所以
login()
不能按预期工作,是因为它取决于外部状态(
const state=getCurrentState()
),在调用
login()
和订阅
login()
返回的可观察状态的时间点不同
AjaxRequest
捕获调用
login()
时的状态,该状态发生在
GENERATE\u DEVICE\u ID
发送到存储之前。此时还没有执行任何网络请求,但是已经基于错误状态配置了ajax
observable

为了看看会发生什么,让我们将事情简化一点,并以这种方式重写史诗:

const createInnerObservable = submitLoginAction => {
    return Observable.of(generateDeviceId()).concat(login());
}

const submitLoginEpic = action$ =>
    action$.ofType(SUBMIT_LOGIN).mergeMap(createInnerObservable);
SUBMIT\u LOGIN
操作到达时,
mergeMap()
首先调用
createInnerObservable()
函数。函数需要创建一个新的可观察对象,为此必须调用
generateDeviceId()
login()
函数。调用
login()
时,状态仍然旧,因为此时尚未创建内部可观察对象,因此没有机会调度
GENERATE\u DEVICE\u ID
。因此,
login()
返回一个使用旧数据配置的
ajax
可观察对象,它成为结果内部可观察对象的一部分。只要
createInnerObservable()
返回,
mergeMap()
订阅返回的内部observable并开始发出值
GENERATE\u DEVICE\u ID
首先出现,被分派到存储,状态被更改。之后,
ajax
observable(现在是内部observable的一部分)被订阅并执行网络请求。但是新的状态对这一点没有影响,因为
ajax
observable已经用旧数据初始化了

login
包装到
可观察对象中。create
将推迟调用,直到订阅了
observatable.create
返回的可观察对象,此时状态已经是最新的

另一种方法是引入额外的epic,该epic将对
生成设备ID
操作(或不同的操作,以适合您的域为准)作出反应,并发送登录请求,例如:

const submitLogin = payload => ({ type: "SUBMIT_LOGIN", payload });

// SUBMIT_LOGIN_REQUESTED is what used to be called SUBMIT_LOGIN
const submitLoginRequestedEpic = action$ =>
    action$.ofType(SUBMIT_LOGIN_REQUESTED)
        .mergeMap(({ payload }) => Rx.Observable.of(
            generateDeviceId(uuidv1()),
            submitLogin(payload))
        );

const submitLoginEpic = (action$, store) =>
    action$.ofType(SUBMIT_LOGIN)
        .mergeMap(({ payload }) => {
            // explicitly pass all the data required to login
            const { token, deviceId } = store.getState().user;
            return login(payload.email, payload.password, token, deviceId)
                .map(({ response }) => resolveLogin(response.content))
                .concat(loadAbout());
        });
学习资源 由于
redux observatable
基于RxJS,因此首先熟悉Rx是有意义的

我强烈推荐观看安德烈·斯泰尔茨的演讲。它应该给出什么是可观测的以及它们如何在引擎盖下工作的直觉

安德烈还写了这些关于书呆子的精彩课程:


此外,杰伊·菲尔普斯在《可观察的redux》
上也做了介绍,这绝对值得一看。

请阅读。嘿,谢尔盖,谢谢你的回答!我已经尝试了.startWith,但问题是在登录请求之前应该发出
GENERATE\u DEVICE\u ID
。所以答案是:
GENERATE\u DEVICE\u ID--登录请求--解析\u Login--加载\u ABOUT
Login(payload.email,payload.password)<--这是登录请求Hey Lucas!您的意思是
login()
依赖于或需要生成的设备id才能在存储中?是否应该将
LOGIN\u请求
操作也发送到商店?您还可以澄清一下:
login()
是在调用时立即发送请求,还是仅在订阅了它返回的可观察对象时才发送请求?我用一个使用
concat
操作符的解决方案更新了答案,希望这个