如何在redux-observable中正确使用forkJoin?

时间:2017-11-27 08:13:30

标签: javascript redux rxjs rxjs5 redux-observable

我正在使用redux-observable。

我试图在所有附件单独上传后发送邮件。

此发送邮件API的过程是

  1. 将草稿内容添加到草稿
  2. 将附件单独添加到草稿
  3. 上传所有附件后,从草稿
  4. 发送邮件

    现在,以下代码可以单独成功上传所有附件。但是,我不知道如何等待它们。

    在学习this doc之后,我认为我需要使用forkJoin,但在这种情况下,我还没有弄清楚如何正确使用它。

    export const addMailContentSucceedEpic = (action$, store) =>
      action$
        .ofType(ADD_MAIL_CONTENT_SUCCEED)
        .expand(action => {     // expand here helps upload all attachments separately
          const restAttachments = removeFirstAttachment(action.payload.attachments);
    
          if (isEmpty(restAttachments)) return Observable.empty();
    
          return Observable.of({
            ...action,
            payload: {
              ...action.payload,
              attachments: restAttachments
            }
          });
        })
        .map(action => addAttachment({ mailId: action.payload.mailId, attachment: first(action.payload.attachments) }));
    
    export const addAttachmentEpic = (action$, store) =>
      action$
        .ofType(ADD_ATTACHMENT)
        .mergeMap(action => getBase64FromFile(action))
        .mergeMap(action =>
          ajax
            .patch(url, { base64: action.payload.base64 })
            .map(() => addAttachmentSucceed({ mailId: action.payload.mailId }))
            .catch(addAttachmentFailed)
        );
    
    export const addAttachmentSucceedEpic = (action$, store) =>
      action$
        .ofType(ADD_ATTACHMENT_SUCCEED)
        // Here is wrong, I need wait all attachments be uploaded
        // I tried to add a global `let = tasks$[];` on the top, but I am not clear where and how I can push each task (add attachment) to it
        // Then add .mergeMap(action => Observable.forkJoin(tasks$)) here, but it probably wrong to be added after .ofType(ADD_ATTACHMENT_SUCCEED)
        // In my mind, it needs to be after something `ADD_ALL_ATTACHMENTS_SUCCEED`
        .map(action => sendMailFromDraft({ mailId: action.payload.mailId }));
    

    更新

    我会尝试更改下面的结构。

    也许我可以将addAttachments$作为有效负载(?)传递或将其创建为全局变量。我稍后会提供更多更新。

    const addAttachments$ = [
      ajax.patch(url, { base64: getBase64(first(action.payload.attachments) })),
      ajax.patch(url, { base64: getBase64(second(action.payload.attachments) })),
      ajax.patch(url, { base64: getBase64(third(action.payload.attachments) })),
      // ...
    ];
    
    export const addMailContentSucceedEpic = (action$, store) =>
      action$
        .ofType(ADD_MAIL_CONTENT_SUCCEED)
        .mergeMap(action =>
          Observable.forkJoin(addAttachments$)
          .map(() => sendMailFromDraft({ mailId: action.payload.mailId }))
        )
        .catch(/* ... */);
    

1 个答案:

答案 0 :(得分:3)

这是我的最终解决方案。

由于读取文件也是异步的,因此此解决方案中有两个forkJoin

一个forkJoin用于等待读取所有文件。另一个forkJoin用于等待上传所有附件。

对于代码中的getBase64FromFile$,请查看How to handle async function in redux-observable?

export const addMailContentSucceedEpic = (action$, store) =>
  action$
    .ofType(ADD_MAIL_CONTENT_SUCCEED)
    .mergeMap(action => addTasks$(action))
    .mergeMap(action => Observable.forkJoin(action.payload.posts$)
      .map(() => sendMailFromDraft({ mail: action.payload.mail }))
    );


function getPosts(base64Array, action) {
  let posts$ = [];

  for (let i = 0; i < base64Array.length; ++i) {
    posts$ = [...posts$, ajax.patch(url, { base64: base64Array[i] })];
  }

  return {
    ...action,
    payload: {
      ...action.payload,
      posts$
    }
  };
}

function addTasks$(action) {
  let readFiles$ = [];

  for (let i = 0, attachmentIds = Object.keys(action.payload.attachments); i < attachmentIds.length; ++i) {
    const attachmentId = attachmentIds[i];
    const attachment = action.payload.attachments[attachmentId];

    readFiles$ = [...readFiles$, getBase64FromFile$(attachment)];
  }

  return Observable.forkJoin(readFiles$)
    .map(base64Array => getPosts(base64Array, action));
}