ifandelse / machina.js

js ex machina - finite state machines in JavaScript
http://machina-js.org/
Other
1.93k stars 147 forks source link

proposal to improve handler priorities #126

Closed se-m closed 6 years ago

se-m commented 8 years ago

Hi. I have wrote some code to find out hander priority during the transition. My test code

var machina = require('machina');

var handleTest = new machina.Fsm({
   initialState: "a",

    states: {
      a :{
        "e1": function (){
          console.log("a e1");
          this.deferUntilTransition();
          this.transition("b");
          this.handle ("e4");
        },

        "e2": function (){ console.log("a e2"); },       
        "e3": function (){ console.log("a e3"); },        
        "e4": function (){ console.log("a e4"); },

        _onExit: function (){
          this.handle ("e2");
        },
      },

      b :{
        _onEnter: function (){ 
          console.log ('enter');
          this.handle ("e3");
        },     
        "e1": function (){ console.log("b e1"); },        
        "e2": function (){ console.log("b e2"); },        
        "e3": function (){ console.log("b e3"); },
        "e4": function (){ console.log("b e4"); }
      }
   }
})

handleTest.handle ("e1");

Script out is

a e1
enter
b e3
b e1
b e4

Problem that is unable to overpass code in _onEnter.

Out should be

a e1
enter
b e1
b e3
b e4

First handlers in deferUntilTransition, then _onEnter, last others. I.e. normal event queue. Pure example when need to overpass _onEnter

   hello_state: {
      _onEnter : function (){
        this.someUsableObject = new SomeObject();
        this.someObjectNeedToFree = new BigObject();
        this.handle ("say_hello");
      },      
      "say_hello" : function (){
        someUsable.hello();
      },      
      "error" : function (){
        //someUsableObject - is destoyed
        this.deferUntilTransition();
        this.transition("bye_state");
      }
    },
    bye_state : {
      _onEnter : function (){
        this.handle ("say_goodbye"); //put code in handler, we expect that we overpass it on error
      },      
      "say_goodbye" : function (){
        this.someUsableObject.goodbye(); //on error in hello_state this handler not used...
      },      
      "error":{
        this.transition("exit");//...because we exit
      },      
      _onExit : function (){
        this.someObjectNeedToFree.free(); //that is why we need this state
      }
    }, 
    exit : {}

In my case i was needed to overpass _onEnter code in state_3 when error throwed in state_1, but state_2 must do his job in-beetween state_3 and state_1. Yes, weird and complicated.

ifandelse commented 6 years ago

@se-m By design, a normal handler in a state will not fire before the _onEnter for that state does - this is intentional. If you have conditional logic resulting in a need to fire a handler before the same state's _onEnter, that's likely an indicator that you need to move all of that into separate handlers and branch that way.