Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

How to handle errors during input creation?#5271

Unanswered
yotov asked this question inQ&A
Discussion options

In one of my states I am invoking a promise actor and have an input inside invoke. My invoke section includes onDone/onError and my input function could throw an error in some cases.

I've just noticed that the onError transition is executed only for errors thrown from promise actor.
What is a possible solution to this cause? Is there an option like the onError transition but for input errors?

You must be logged in to vote

Replies: 2 comments 2 replies

Comment options

I searched a lot across the issues and found some similar cases ( for example this one -#3645 ).

From what I understand, when the machine is instantiated, we can subscribe to errors. But there is no way inside the machine to subscribe to that error and make a transition?

One workaround that I see is to subscribe for error and once an error is caught we can send an event to the state machine and thus we can transition to the error state which is a final state?

You must be logged in to vote
2 replies
@davidkpiano
Comment options

That's right, there is currently no way to do this for input creation errors itself.

What would an intuitive API look like for you that would support this?

@yotov
Comment options

As searching for a solution I found that there were typestates inxstate 4 which are not supported in xstate 5.

Now I have some properties in Context that areset to null by default and during the state machine I populate them. In the following example I have to check whether userId is not null. If it is null I'm throwing an error as this is something that is not expected and what to move state-machine to a failure state.

import { assign, createActor, fromPromise, setup } from 'xstate';interface Context {    readonly userId: number | null;    readonly user: Record<string, string> | null;}const machine = setup({    actors: {        fetchUser: fromPromise(({ input }) => {            return fetch(`/users/${input.userId}`).then((res) => res.json());        }),    },    types: {        context: {} as Context,        events: {} as { type: 'provideUserId'; userId: number },    },}).createMachine({    context: {        userId: null,        user: null,    },    initial: 'waiting',    states: {        waiting: {            on: {                provideUserId: {                    target: 'fetchingUser',                    actions: assign({                        userId: ({ event }) => {                            return event.userId;                        },                    }),                },            },        },        fetchingUser: {            invoke: {                src: 'fetchUser',                onDone: 'complete',                onError: 'failure',                input: ({ context }) => {                    if (context.userId === null) {                        throw new Error('UserId is not provided');                                     }                    return { userId: context.userId };                },            },        },        complete: { final: true },        failure: { final: true },    },});const actor = createActor(machine);actor.subscribe({    error: (err) => {        console.log(err);    },});actor.start();actor.send({ type: 'provideUserId', userId: 1 });

Example is not the best one as this case is mostly avoidable due typescript warnings - e.g. ts will warn if you write:

actor.send({ type: 'provideUserId', userId: null });

But I make some more complex input functions that uses userId and also a mapping ( object that keeps userId to something ) and if there is not mapping for some userId I throw errors and then state machine stuck.

That's right, there is currently no way to do this for input creation errors itself.

What would an intuitive API look like for you that would support this?

Not sure how intuitive is but as I found that we can subscribe to errors I decided to try if I can subscribe for error event in top levelon clause -

on: {    error: {         target: "failure"    }}

This for example will help in some cases - when an error is a fatal one and want go to a final failure state where you can make some cleanup logic.

But my case is a little bit different and I iterate over items - e.g. users and it can fail for some of the users and I want to skip these failures but to keep info about them. Maybe something likeonError at the same level asinvoke which handles errors from input/actions/guards

Comment options

I was about to open an issue but I found this one.
Consider the following example code :

import{createActor,fromPromise,setup}from"xstate"typeInput={shouldThrow:"input"|"actor"|"onDone"|null}constmyPromiseActor=fromPromise<string,Input>(({ input})=>{returnnewPromise((resolve,reject)=>{if(input.shouldThrow==="actor"){console.error("Throwing error from within the actor's promise.")reject(newError("Error from actor"))}else{resolve("Promise resolved")}})})constpromiseMachine=setup({types:{context:{}asInput,input:{}asInput},actors:{ myPromiseActor}}).createMachine({id:"promiseMachine",initial:"loading",context:({ input})=>input,states:{loading:{invoke:{src:"myPromiseActor",input:({ context})=>{if(context.shouldThrow==="input"){console.error("Throwing error from input function.")thrownewError("Error from input function")}returncontext},onDone:{target:"success",actions:({ context})=>{if(context.shouldThrow==="onDone"){console.error("Throwing error from within the onDone hook.")thrownewError("Error from onDone hook")}console.log("onDone hook executed successfully.")}},onError:{target:"failure",actions:()=>{console.error("Caught error")}}}},success:{type:"final"},failure:{type:"final"}}})constactor=createActor(promiseMachine,{input:{shouldThrow:"onDone"}}).start()actor.subscribe((state)=>{console.log("Current state:",state.value)})

onError only catch error thrown in the actor. It doesn't catch other errors, such as the one that can be thrown in input or in onDone.
However there's several reasons to throw within input or onDone :
You could have nullable context properties that you need to check for, or you could have an actor that returns some kind of union type (Result|Error). For now if im not mistaken, you have to handle these doingself.send + early returns/ throws, which feels hacky.
Another loosely related issue is thatguard doesn't do type narrowing for unions. So even if you use guard to transition to an error state, you still need to throw/typecast to keep typesafety.

One solution to these issue would be for onError to catch allErrors happening in input and in onDone. This could be enabled with a small flag (catchAll: true) to be backward compatible, and eventually be the default in v6.

@davidkpiano

You must be logged in to vote
0 replies
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Category
Q&A
Labels
None yet
3 participants
@yotov@davidkpiano@Hebilicious

[8]ページ先頭

©2009-2025 Movatter.jp