matthewp / robot

🤖 A functional, immutable Finite State Machine library

Home Page:https://thisrobot.life

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Cancel invokation events if already transitioned

oskarhane opened this issue · comments

Hi!

Is there a way to stop an invokation from sending the done event if we already transitioned away from the state where it started?

The done is a fixed event name there can be multiple states listening for that event, which means that a done event for a past state can trigger a transition for the "wrong" state.

Example: https://svelte.dev/repl/074d23edf65a4a4a82c68e2f5ec16b1a?version=3.12.1

This will toggle the state between active / inactive every two seconds via the done event from a promise.
A click on the button will send a toggle event to transition between the same states.

Click the button rapidly a few times to see the strange behavior.

The intended behavior here is to toggle between the states efter 2 seconds unless the user clicks the button, then it should transition directly and then restart the 2 second timer (and transition again unless user interrupts with a click).

How should I model this to get my intended behavior?

Yeah this has come up a few times and we should figure out a better solution, perhaps it should be possible to customize the 'done' event name or something.

I think what you want in your case is a nested machine (like described here: https://thisrobot.life/guides/nested-states.html)

You have an implicit state here is the problem. You want to mark a previous invocation as canceled, but since promises don't have such a concept you can't really model that! State machine to the rescue :). When you create a nested machine and you finish the active machine, any 'done' events will be ignored because they will happen in another (now dead) service.

Yes, I think customizing the 'done' even name would solve most use cases @matthewp, but not sure if it's the right solution.
Not having an event at all but rather a callback (could use event with random name in the background) would be easier for developers I think.

But if we head to the nested machine solution, how would you solve that?
I found that the last example on the page you linked didn't work at all (don't you need to invoke it?).

What I've come up with is https://svelte.dev/repl/d53ea3faa3c54d6981c06afb43c5b0a4?version=3.12.1 but that's not working properly (better than my first version though).
Click the button at a 1 second interval and you see that some clicks doesn't toggle the state.
I probably messed up the implementation of the nested machines.

Hey, sorry but I'm a bit busy today. I'll see if I can take a look this weekend. From looking at your example it seems to be working ok to me... but I'll admit that I'm probably missing something. One small thing is I would move transition('toggle', 'active') to the nested machine. since it's behavior of that state.

btw, thanks a lot for bringing this up. Sorry for not having a solution in mind. It is making me think we should be doing invoke differently for promises. Possibly we should be creating a submachine of our own so we have complete control.

Hello. Thanks for this library!

I had a similar issue and arrived at the same implementation idea to allow the event names to be customised.

Here are some premature PRs (in case they make discussion easier):

Hey, sorry but I'm a bit busy today. I'll see if I can take a look this weekend. From looking at your example it seems to be working ok to me... but I'll admit that I'm probably missing something. One small thing is I would move transition('toggle', 'active') to the nested machine. since it's behavior of that state.

Don't worry about it, thanks for taking time and responding!
I'm evaluating this lib for use in a project, that's where my questions are coming from.
Since the docs are a bit sparse I figured I ask here to see if anyone else has run into the same issues.
Looking forward to how you'd solve it, when you have time to look at it.

Hello. Thanks for this library!

I had a similar issue and arrived at the same implementation idea to allow the event names to be customised.

Here are some premature PRs (in case they make discussion easier):

Thanks for that comment, I'll have a look at your prs over the weekend!

I had a similar issue and arrived at the same implementation idea to allow the event names to be customised.

@matthewp Obviously please feel free to close those PRs if you decide nested machines is a cleaner solution.

As a reference here's a xstate implementation of the desired behavior (couldn't use the svelte repl with xstate): https://codesandbox.io/s/white-morning-fs81o?file=/machine.js

This is the part in the xstate docs that explains why it works:

If the state where the invoked promise is active is exited before the promise settles, the result of the promise is discarded.

For me, that is the expected behavior.
If I leave a state, events from invokations started in that state should be swallowed IMO.

If that's how it works using nested machines, then it's all good.
I don't manage to get it working though (see my last link).

I'm in the process of writing state machines for mobx. I'm using robot and xstate for inspiration and come across this problem as well. I'm solving this by storing anything invoked (promise or machine) into a WeakMap, mapping the promise with its transition config removing it when resolved or when transitioned to a new state.

Whenever the promise is ready to resolve, just check if the promise is still in the WeakMap. If it's not, then it means the state has been changed and resolves nothing

@matthewp @oskarhane looks like a serious issue to me. Now i think i know what causes stange behaviour in my app now.
I thought that in this exaple toggle transiton will overtake done transiton, and thats how my app is built. And now need to find soultion ASAP😥

import { createMachine, state, transition, invoke, reduce, immediate } from 'robot3';
import { useMachine } from 'svelte-robot-factory';

const wait = ms => () => new Promise(resolve => setTimeout(resolve, ms));

const machine = createMachine({
	inactive: invoke(
		wait(2000),
		transition('done', 'active'),
		transition('toggle', 'active')
	),
	active: invoke(
		wait(2000),
		transition('done', 'inactive'),
		transition('toggle', 'inactive')
	)
});

const service = useMachine(machine);
export default service;

Hi!
Thank you for this library, it's been great to work with so far.

I'm running into the same issue where I want to implement a state that can be interrupted. This issue is causing a strange behaviour in my app and took a while to find out what was causing it. I did not expect that a done event for state B can trigger a transition for state A (and therefore the wrong state).

Are there any updates or development on this issue?
I see that there is an open PR but there has been no updates to it since Nov 17, 2022.

Either solution of customizing the 'done' event name or cancelling the invoke event if the state has already transitioned would be great.