3

I've read the Invoking Multiple Services section, which says that one could invoke multiple promises, but in my own tests they look to be invoked without waiting for the previous to finish

// ...
invoke: [
  { id: 'service1', src: 'someService' },
  { id: 'service2', src: 'someService' },
  { id: 'logService', src: 'logService' }
],
// ...

Here is an answer as well with the workaround of creating intermediary states

states: {
    first: {
      invoke: {
        src: 'one',
        onDone: {
          target: 'second',
        }
      }
    },
    second: {
      invoke: {
        src: 'two',
        onDone: {
          target: 'success',
        }
      }
    },
    success: {
      type: 'final'
    }
}

Is there a way to do chaining like Promise.each, with invokes, making the invoke([]) run serially maybe ?

I can only see two options:

  1. Intermediate states
  2. Call one promise that does the chaining in itself.
Coding Edgar
  • 1,285
  • 1
  • 8
  • 22
  • Yes, these two seem to be your options. What's wrong with them? Surely you could even write a simple function that produces intermediate states from an array of services automatically. – Bergi Sep 10 '19 at 23:32
  • Well the invoke `SingleOrArray` interface looked very nice and put together, seems like a nice way to chain and be very clear of what's happening. but its not, because is parallel and seems that there's no way to tell otherwise, what i'm trying find is something like redux-saga effects, [put](https://redux-saga.js.org/docs/api/#putaction) or `call`for example, but in xstate context, invoking different services, and being able to combine them as the app grows without creating intermediary functions. – Coding Edgar Sep 11 '19 at 00:44
  • 1
    So the problem with both promise chaining and with an internal machine is that you cannot invoke services defined in the outer machine? In that case, you probably should post a feature request on the library's repo. – Bergi Sep 11 '19 at 22:15

1 Answers1

9

All stateful behavior needs to be explicit in the state machine. If you want promises to run sequentially, that is stateful - you are checking the state of each promise before the next promise starts. Therefore, you need multiple states, just like the solution you posted.

However, you can create a helper function that does what you want:

async function sequence(...promiseCreators) {
  for (const promiseCreator of promiseCreators) {
    await promiseCreator();
  }

  return;
}

// ...
invoke: {
  src: () => sequence(createPromise1, createPromise2, createAsyncLogger)
}
// ...

But this poses further questions:

  • What if an error occurs? (add try/catch)
  • What about interruption/cancellation? How do you handle that?
  • How do you aggregate data resolved from the promises?
  • How do you handle dependency resolution; i.e., promise A depends on data from promise B?

These can be easily answered for your use-case but there are many possible use-cases to consider. That is why it's best to be explicit about the sequence of promises (using intermediary states) rather than let the library (XState) make assumptions about your use-cases.

David Khourshid
  • 4,918
  • 1
  • 11
  • 10
  • all those points would be solved with an option of serial `invoke`, as `invoke` handles all those branches. – Coding Edgar Oct 01 '19 at 15:18
  • After a long time, I came back to this question, and agree it is way better to use composition outside of XState for better ad-hoc. I guess I was trying to see if XState had a monadic interface to combine states/machines at that point. – Coding Edgar Oct 27 '21 at 18:57
  • @CodingEdgar What would an idea monadic interface for this look like? – David Khourshid Oct 28 '21 at 20:38
  • 1
    David, I guess it would be a sequential invoke when / if any of them fails, the rest of the promises are not invoked and we go directly to an onError handler. Also a cancellation would again not invoke further promises. Your other questions stand though. – RestOfTheBothWorlds Apr 17 '22 at 21:15