Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Calling a new transition from inside a lifecycle method #143

Open
reubenab opened this issue Nov 8, 2017 · 8 comments
Open

Calling a new transition from inside a lifecycle method #143

reubenab opened this issue Nov 8, 2017 · 8 comments

Comments

@reubenab
Copy link

reubenab commented Nov 8, 2017

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.

@adami
Copy link

adami commented Nov 8, 2017 via email

@rickbsgu
Copy link

rickbsgu commented Nov 9, 2017

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
)}

@adamworrall
Copy link

I've been doing process.nextTick(() => transition()) and it seems to be working fine.

@telemmaite
Copy link

telemmaite commented Feb 5, 2018

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...

@williscool
Copy link

williscool commented Oct 16, 2018

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

@pmmcoder
Copy link

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?

@germancmartinez
Copy link

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.

@vsorrokin
Copy link

vsorrokin commented Jan 21, 2021

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants