简体   繁体   中英

How to use forkJoin correctly in redux-observable?

I am using redux-observable.

I am trying to send mail after all attachments be uploaded separately.

The process of this send mail API is

  1. Add mail content to the draft
  2. Add attachments separately to the draft
  3. Once all attachments are uploaded, send mail from draft

Right now the code below can upload all attachments separately successfully. However, I don't know how to wait them all done.

After learning this doc , I think I need use forkJoin , however I didn't figure out how to use it correctly in this case.

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 }));

UPDATE :

I will try to change to the structure below.

Maybe I can pass addAttachments$ as a payload(?) or create it as a global variable. I will give more update later.

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(/* ... */);

This is my final solution.

Since read file is also async, so there are two forkJoin in this solution.

One forkJoin is used to wait all files are read. The other forkJoin is used for waiting all attachments are uploaded.

For getBase64FromFile$ in the code, please check 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));
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM