Closed createthis closed 5 years ago
The interpreter does not have a method called .state()
. It has a property called .state
that indeed gives the current state of the service (interpreter instance). Where in the docs do you see otherwise?
If you want the initial state, use service.initialState
.
@davidkpiano Sorry, that was a typo. I meant .start()
@davidkpiano I think I understand what I was confused about now. On this line:
https://github.com/davidkpiano/xstate/blame/master/docs/README.md#L48
it says:
// => 'inactive'
I thought that meant start()
returned the state, like in an interactive js console or something. I guess you intended it to mean that 'inactive'
would be returned by the console.log()
in the onTransition
callback?
At this point, I could get the state like this:
const toggleService = interpret(toggleMachine)
.onTransition(state => console.log(state.value))
.start();
let state = toggleService.state.value
Is that the preferred way to query the state at any given point in time?
Is that the preferred way to query the state at any given point in time?
In general, it is not preferred to arbitrarily query the state. You should subscribe to state changes in any reactive system, especially with statecharts.
It is a mutable property that is updated when the state is changed. Query it only when absolutely necessary. Prefer using .onTransition()
.
Instead, it is always guaranteed that the first state is the .initialState
:
const toggleService = interpret(toggleMachine)
.onTransition(state => console.log(state.value))
.start();
let state = toggleService.initialState;
In the world of async/await
, this makes me a little uncomfortable. But I admit React
's own setState
with it's callback makes me uncomfortable too.
I like to avoid race conditions. I feel like there is more potential for that sort of thing when you're firing callbacks in the background all the time.
I also like to minimize setState
calls as much as possible. It's difficult to batch an xstate send()
and setState
if you're worrying about the onTransition
callback.
In the world of
async/await
, this makes me a little uncomfortable.
Keep in mind that async/await
is only for single eventual values; e.g., a resolved value from a Promise. That is not the case with how statecharts work, which are more like Observables.
I.e., await service.send('SOME_EVENT')
makes no sense because:
I like to avoid race conditions. I feel like there is more potential for that sort of thing when you're firing callbacks in the background all the time.
Race conditions, within the context of a single statechart/state machine, are mathematically impossible. It's only when coordinating between multiple statecharts that race conditions can occur, depending on how you orchestrate events between each, but this can be avoided with an event-based architecture (including statecharts themselves).
Example:
const service = interpret(someMachine).start();
// assume we have some async remote streams
stream1.subscribe(event => service.send(event));
stream2.subscribe(event => service.send(event));
Those two streams, stream1
or stream2
can emit events at any time and they'll be processed in order by the service
. Since both streams (which can themselves be events emitted from other statecharts) are orchestrated by a single state machine (someMachine
), we can successfully avoid race conditions.
I also like to minimize setState calls as much as possible. It's difficult to batch an xstate send() and setState if you're worrying about the onTransition callback.
See the section on executing actions manually in the docs. You have full control over this behavior.
Yes, I see that xstate is very opinionated. I admittedly don't use 99% of the functionality it has to offer. I only use it to tell me the current state and the next state, given an event. I also appreciate the ability to generate visualizations so I can see my application's state flow at a glance. The rest of it is pretty meh to me as I handle it all externally of xstate.
Anyway, thanks for taking the time to talk it through with me. I appreciate it.
That's totally fine. It follows the SCXML spec so the opinions are not mine 😉 Using it as a pure transition()
function that gives you the next state given the current state + event provides the most flexibility. You are free to make your own interpreter and handle everything else externally.
Doing a quick search, it looks like there are promise based FSM libraries out there for ES6: https://github.com/vstirbu/fsm-as-promised
If it had xstate's visualization capability, I might be tempted. ;)
Look closely. Ironically, fsm-as-promised
uses callbacks primarily.
I'm not trying to convince you to use XState though - open source is a collaboration, not a competition. Here's a few other libraries for you to consider:
Looks to me like each event becomes a chainable promise returning method to me.
I see that, and it makes no sense. With statecharts and state machines, "...sending a signal takes zero time" (according to David Harel in his original paper). Also:
Transitions, assignment statements, signal transmitions, etc., are all assumed to be instantaneous, i.e., to take zero time.
_David Harel, http://www.inf.ed.ac.uk/teaching/courses/seoc/2005_2006/resources/statecharts.pdf_
So the fsm-as-promised
example:
fsm.warn().then(function () {
fsm.current === 'yellow';
// true
});
Would be written like this in xstate
:
fsm.send('warn'); // zero time!
fsm.state.value === 'yellow'; // true
I think vstirbu is using promises because fsm-as-promised allows the definition of callbacks. These are called when the event is sent. In xstate, the equivalent are side-effects. It makes perfect sense to return a promise from send()
and resolve it when all the side-effects have completed successfully.
I, and I think (for at least 5 years now) most of the JS community at this point, prefer promises to the Observer pattern. Now, with async/await
in ES6, anything that returns promises gets to use await
for free. So this:
fsm.warn().then(function () {
fsm.current === 'yellow';
// true
});
Becomes this:
await fsm.warn();
fsm.current === 'yellow'; // true
It makes sense, once you get past the idea of being bound to the observer pattern.
Bug or feature request?
Bug
Description:
Interpreter.start() does not return initial state. It appears to return the Interpreter object.
(Bug) Expected result:
Should return initial state.
(Bug) Actual result:
Returns Interpreter object.
(Bug) Potential fix:
Should either return initial state or else the documentation needs to be updated.