javascript-state-machine
javascript-state-machine copied to clipboard
Calling a new transition from inside a lifecycle method
If I want to decide between two transitions on entering a state, I am unable to call a transition from inside any lifecycle event. Say I have a very simple FSM with 3 states: A, B, and C. There is a transition A->B called step1() and another transition B->C called step2(). Say I only want to print hello
when we get to B then call step2(). How would this be achieved? If I write an onEnterB
or onAfterStep1
function, I get an error saying that a new transition cannot be called while the old one is still in effect.
You can't transition while in a transition. What i do is pass an object as a param, and the current transition sets a 'nextTransition' value in it, which i feed back to the state machine after the first transition is over.
On 9 Nov 2017 0:19, "Reuben Abraham" [email protected] wrote:
If I want to decide between two transitions on entering a state, I am unable to call a transition from inside any lifecycle event. Say I have a very simple FSM with 3 states: A, B, and C. There is a transition A->B called step1() and another transition B->C called step2(). Say I only want to print hello when we get to B then call step2(). How would this be achieved? If I write an onEnterB or onAfterStep1 function, I get an error saying that a new transition cannot be called while the old one is still in effect.
— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/jakesgordon/javascript-state-machine/issues/143, or mute the thread https://github.com/notifications/unsubscribe-auth/ACE68CjEmCed4ebsAwRRypxU94Ll9cTvks5s0ikAgaJpZM4QXIcP .
Pretty much any event system will choke if you try to invoke an event within an event.
The typical solution is to back out to the event loop (let the current event complete) before invoking the internal event. You can do this with either a custom event, or a timeout.
A 0 length timeout is the easiest:
setTimeout(function() {
fsm.newTrans() } , 0
)}
I've been doing process.nextTick(() => transition())
and it seems to be working fine.
Even though that the workaround will do it`s job, if before the execution of process.nextTick you go for some reason into another transition you will have a race condition and undefined state in the end.
For example A(init) -> calls (B) -> process.nextTick to (C) still on same tick but another piece of the code checks if Current State (B) -> process.nextTick move to (D)
That move to D should fail because you are already scheduled async transition to (C), but there is no way for the fsm to know this. Ideally you want to replicate the lock mechanism...
Update 11-7-2018
Reads even cleaner with async / await
const fsm = new StateMachine({
init: "init",
transitions: [
{ name: "step", from: "init", to: "dbWrite" },
{ name: "step", from: "dbWrite", to: "submitToApi" },
{ name: "step", from: "submitToApi", to: "imageUpload" },
{ name: "step", from: "imageUpload", to: "success" }
],
methods: {
onDbWrite: async () => {
await this.onDbWrite();
setTimeout(() => this.fsm.step(), 0);
},
onSubmitToApi: async () => {
await this.onSubmitTo311Api();
setTimeout(() => this.fsm.step(), 0);
},
onImageUpload: async () => {
await this.onImageUpload();
setTimeout(() => this.fsm.step(), 0);
},
onSuccess: async () => {
await this.onSuccess();
}
}
});
Got multiple state transitions working async.
Could just be my ignorance but it doesn't seem like it would be too crazy to support in the library
const fsm = new StateMachine({
init: "init",
transitions: [
{ name: "step", from: "init", to: "dbWrite" },
{ name: "step", from: "dbWrite", to: "submitToApi" },
{ name: "step", from: "submitToApi", to: "imageUpload" },
{ name: "step", from: "imageUpload", to: "success" }
],
methods: {
onTransition: lifecycle => this.onTransition(lifecycle),
onDbWrite: () =>
this.onDbWrite().then(() => setTimeout(() => this.fsm.step(), 0)),
onSubmitToApi: () =>
this.onSubmitToApi().then(() => setTimeout(() => this.fsm.step(), 0)),
onImageUpload: () =>
this.onImageUpload().then(() => setTimeout(() => this.fsm.step(), 0)),
onSuccess: () => this.onSuccess()
}
});
My fsm lives inside of a class with member functions that are async await or return promises.
Seems like a really neat way of implementing lifecycle hooks
Update 11-7-2018
Reads even cleaner with async / await
const fsm = new StateMachine({ init: "init", transitions: [ { name: "step", from: "init", to: "dbWrite" }, { name: "step", from: "dbWrite", to: "submitToApi" }, { name: "step", from: "submitToApi", to: "imageUpload" }, { name: "step", from: "imageUpload", to: "success" } ], methods: { onDbWrite: async () => { await this.onDbWrite(); setTimeout(() => this.fsm.step(), 0); }, onSubmitToApi: async () => { await this.onSubmitTo311Api(); setTimeout(() => this.fsm.step(), 0); }, onImageUpload: async () => { await this.onImageUpload(); setTimeout(() => this.fsm.step(), 0); }, onSuccess: async () => { await this.onSuccess(); } } });
Got multiple state transitions working async.
Could just be my ignorance but it doesn't seem like it would be too crazy to support in the library
const fsm = new StateMachine({ init: "init", transitions: [ { name: "step", from: "init", to: "dbWrite" }, { name: "step", from: "dbWrite", to: "submitToApi" }, { name: "step", from: "submitToApi", to: "imageUpload" }, { name: "step", from: "imageUpload", to: "success" } ], methods: { onTransition: lifecycle => this.onTransition(lifecycle), onDbWrite: () => this.onDbWrite().then(() => setTimeout(() => this.fsm.step(), 0)), onSubmitToApi: () => this.onSubmitToApi().then(() => setTimeout(() => this.fsm.step(), 0)), onImageUpload: () => this.onImageUpload().then(() => setTimeout(() => this.fsm.step(), 0)), onSuccess: () => this.onSuccess() } });
My fsm lives inside of a class with member functions that are async await or return promises.
Seems like a really neat way of implementing lifecycle hooks
Will this approach have any other effects or disadvantages?
Hi,
I could solve this problem calling the transtition inside the method writing:
setTimeout(() => fsm.step(),0);
If I wrote setTimeout(fsm.step(),0)
I get the error. I don´t know the difference, but using =>
solves the problem.
Maybe creator of the lib can provide an example how to make state machine which changes its state from first to last (without setTimeout) when previous transition is finished? A -> B -> C. I guess this is pretty often case.