Xstate: [Promise as a service] Missing `onError` handler for invocation despite being defined

Created on 15 May 2020  路  9Comments  路  Source: davidkpiano/xstate

Description
I'm running into a very strange issue where the onError handler is never called. It works for most of my machines and services flawlessly, but there is one that always prints an error in the console that I'm missing the onError handler.

Expected Result

onError handler to be taken for all my throwing services

A simplified version of my service looks like this:

invoke: {
  id: 'getUser',
  src: (context, event) => Promise.reject(),
  onDone: {
    target: 'success',
    actions: console.log
  },
  // never called, no state transition occurs. Just the error is logged
  onError: {
    target: 'failure',
    actions: console.error
  }
}

Actual Result

It can't seem to find my handler and prints Missing onError handler for invocation 'getUser'.

When I set a breakpoint in the interpreter, I can see that it points to the correct service/origin id.
https://github.com/davidkpiano/xstate/blob/560e28fbb208967d29be0244fe507a021c73f242/packages/core/src/interpreter.ts#L1034

Reproduction

I'm using services + promises all over the place and have never experienced this before. As I said, everything works fine but in one service. I tried to replicate my exact scenario within this codesandbox. I have a spawned actor that sends an async function which can throw an error to a parent machine. The parent is responsible for invoking the promise as a service.

It works fine in the Codesandbox so I'm opening this issue in case anyone else is running into the same problem and may be able to reproduce it or to get some guidance on how to proceed further.

I also tried to replicate it in this codesandbox with an invoked child machine (instead of a spawned actor). It works fine here too as the onError handler seems to always get correctly executed.

Any idea what could be causing this? I stared at the "onError" for like 15 minutes because I first thought I might have misspelled it 馃榿

I also made sure that the service id is unique.

Additional context

xstate: 4.9.1
@xstate/react: 0.8.1
bug

All 9 comments

That's really weird that you can't replicate it, but I'm not able to fix this without a proper reproduction.

Let's reopen this if you are able to reproduce the issue. Try reinstalling/clearing node_modules/etc. as well.

Still spooky and haunting me 馃懟

Not paying too much attention to it. If I can't reproduce until alpha (this is the error handling of the login and the machine is just stuck in the service), I just may make the promise never throw and return some flag in event.data since the onDone works stellar 馃憤 Alternatively, tell the users they really need to make sure to not mistype their password as they'll have to F5 when it errors 馃榿

I will make sure to report back

I have the exact same problem, same package versions. onError works if the action is sendParent('someEvent') but including event data in sendParent, assign and forwardTo('parentMachine') actions all result in the Missing onError handler for invocation error so I have no way of passing error details back to the parent machine from the child machine's state's invoked promise.

@robertvorthman Do you have a reproduction?

@davidkpiano No, I just started learning the library on Saturday and it's difficult to run the code because it's a react-native app that uses Bluetooth so it only runs on a physical iPhone. The parent machine scans for bluetooth devices. When a bluetooth device is found, an event is forwardTo'd the child machine. I want the child machine to be able to report back to the parent that connecting failed. I'm just using invoked callbacks for side effects and inter-machine communication but I'd rather use the libraries syntax for handling promises natively.

@robertvorthman welcome to XState! 馃枻

Try playing around with the Codesandbox I posted if you can somehow reproduce it.

I reproduced the error here: https://codesandbox.io/s/xstate-bug-with-promise-rejection-in-child-machine-s8rkl?expanddevtools=1

The parent (bluetoothMachine) searches for a device and sends it to the child (deviceMachine). The child machine invokes promise device.connect(), and forwards the result back to the parent, but the promise rejection never triggers the onError transition, causing a JavaScript error: Missing onError handler for invocation 'connectPromise', error was 'device rejected connection'.

My current workaround is to invoke a callback, you can see it commented out in the sandbox. If you uncomment it and comment out the promise invoke, the parent and child machine will handle the rejection, and retry searching and connecting until successful.

Thank you so much for reproducing!
My signInMachine is also a child (while having another child actor) so this should be the exact same bug.

Okay, so the main issue is that you're trying to pass XState-specific error events around. The behavior is that if a machine encounters an error it does not know what to do with, then it will throw the event.data as an error.

So, in this case, you're trying to send yourself the original XState error, which it does successfully handle, but since you're trying to send it again, it will fail.

The solution is to send _custom errors_ to the parent. Also, use sendParent(...) to target the parent, not forwardTo since that's for children.

Was this page helpful?
0 / 5 - 0 ratings