Search code examples
node.jsclassxstate

What's the proper way to invoke actions in XState FMS?


My definition for XState FMS in the file task_statemachine.js is as follows:

module.exports = {
  id: 'runner',
  initial: 'setup',
  states: {
    setup: {
      on: {
        RECEIVED: {
          target: 'running',
          actions: 'runTask',
        },

        ERROR: {
          target: 'error',
          actions: 'taskError',
        },

        TERMINATED: {
          target: 'terminated',
          actions: 'terminate',
        },
      },
    },

    running: {
      on: {
        COMPLETE: {
          target: 'complete',
          actions: 'taskComplete',
        },

        ERROR: {
          target: 'error',
          actions: 'taskError',
        },

        TERMINATED: {
          target: 'terminated',
          actions: 'terminate',
        },
      },
    },

    terminated: {
      type: 'final',
    },

    complete: {
      type: 'final',
    },

    error: {
      type: 'final',
    },
  },
}

The actual machine itself and service are created in the constructor of the TASK() class like this:

if (!this.state) this.state = interpret(Machine(require('./task_statemachine'), {
      actions: {
        runTask: this.runTask,
        taskComplete: this.taskComplete,
        taskError: this.taskError,
        terminate: this.terminate
      }
    })).start();

I have a problem when trying to run actions which are supposed to call the functions defined in the class. I'm sending the events in the following way this.state.send('COMPLETE');

If I define actions as array of callbacks, like this runTask: this.runTask() the actions seemingly run as they should. According to my colleagues it's a bad practice to do so. What the correct way to invoke the actions once the class in loaded?


Solution

  • The issue was caused by this binding. The solution was to warp the function callback into an arrow function in the actions array.

    if (!this.state) this.state = interpret(Machine(require('./task_statemachine'), {
          actions: {
            // High Level Functions
            runTask: () => {
              this.runTask()
            },
            taskComplete: () => {
              this.taskComplete()
            },
            taskError: () => {
              this.taskError()
            },
            terminate: () => {
              this.terminate()
            }
    
            // runTask() Functions
          }
        })).start();