reduxrxjsredux-observable

Dispatching multiple actions in an async/await mergeMap


I'm still new with redux-observable so please excuse me if this is a stupid question.

Basically I need to await some Promises then dispatch multiple actions in an epic. But returning an array of actions just results in the error Actions must be plain objects. Instead, the actual type was: 'array', you can click the PING button from my CodeSandbox to test this.

I know that this can be solved using from() and pipe() and all that stuff, but I want to keep the format of try/catch with async/await if possible.

I've tried wrapping the action array with of() and from() but it's still the same error

Thanks in advance to anyone that can help me.


Solution

  • You need to have a consistent return type for your mergeMap function.

    Because it's an async function, this currently returns Promise<Action> or Promise<Action[]>, and there's no way you can emit two values from a promise / async function.

    Then, your resulting observable will be an Observable<Action | Action[]>, but redux-observable needs Action without the array.

    In my opinion, the best solution if you want to keep the async function is to have it consistently return Promise<Action[]>, then add one additional mergeMap(array => array) just after the original one to convert Observable<Action[]> to Observable<Action>:

    mergeMap(async (action: any) => {
      try {
        // I need to call some APIs here
        const res1 = await axios.get(
          "https://jsonplaceholder.typicode.com/posts"
        );
    
        const res2 = await axios.get(
          "https://jsonplaceholder.typicode.com/comments"
        );
    
        // Let's say an API throws an error
        throw new Error("Mock error");
    
        return [{
          type: PONG,
          data: res1.data
        }];
      } catch (error) {
        // On error, we should dispatch 2 actions
        return [
          {
            type: PONG,
            data: []
          },
          {
            type: HANDLE_ERROR,
            error: error
          }
        ];
      }
    }),
    mergeMap(array => array)
    

    Another possible solution, which is fun but maybe a bit more complex, is to use async generators instead. These are functions that can yield (kind of return) more than one value:

    // Note the `*` meaning it's a generator function. Also can't be an arrow function
    mergeMap(async function *(action: any) {
      try {
        // I need to call some APIs here
        const res1 = await axios.get(
          "https://jsonplaceholder.typicode.com/posts"
        );
    
        const res2 = await axios.get(
          "https://jsonplaceholder.typicode.com/comments"
        );
    
        // Let's say an API throws an error
        throw new Error("Mock error");
    
        yield {
          type: PONG,
          data: res1.data
        };
      } catch (error) {
        // On error, we should dispatch 2 actions
        yield {
          type: PONG,
          data: []
        };
        yield {
          type: HANDLE_ERROR,
          error: error
        };
      }
    })
    

    This second solution I can't really recommend since async generators are quite hard to reason about, and they are not that well known in the community either.