A finite state machine is an abstract machine that can have only a fixed number of states. That means it has a fixed number of inputs and a set of outputs. A keyword finite represents that it will have limited options and memory to play with.
A state machine takes input and generates output - a new state:
newState = input + currentSate
A simple example of a finite state machine could be a regular flashlight with one button. It has one input - a button. When you press it, the state changes from OFF to ON, and you see the light. When you press the same button again, it will change to OFF.
A single input can produce multiple states. Think of flashlight with several modes. On the first button press, you see the light; on the second press, the light starts to blink, and pressing the same button again, it'll switch off.
Such state transitions can be represented in the table:
Input | Current state | Next state |
---|---|---|
PRESS | OFF | ON |
PRESS | ON | BLINK |
PRESS | BLINK | OFF |
Structure
A state machine should be defined by the following properties:
- Initial state
- Actions for state transition
- Method to dispatch actions
The initial state is a default state when you initiate your machine. In the flashlight example, the initial state is OFF.
const machine = {
state: 'OFF',
...
};
Actions define state transitions. Each action tells what should happen when it is invoked in a current state. For example, if the flashlight state was OFF, and we dispatch a PRESS action, then the machine will look at the current state, which is OFF, search for the defined actions, which is press()
and invokes it. This will transition state from OFF to ON:
const machine = {
state: 'OFF',
transitions: {
OFF: {
press() {
this.state = 'ON'
}
},
ON: {
press() {
this.state = 'BLINK';
},
},
BLINK: {
press() {
this.state = 'OFF';
},
},
},
...
};
To pass actions, we need a method. This method takes action name as an argument (additional arguments are optional if state logic is more complex ). When a dispatch method invoked, it looks in transitions, current state, searches for the dispatched action, and triggers it.
const machine = {
...
dispatch(actionName) {
const action = this.transitions[this.state][actionName];
if (action) {
action.call(this);
} else {
console.log('Invalid action');
}
},
};
Putting it all together, we have a straightforward state machine that defines a sequence of state transitions depending on actions.
const machine = {
state: 'OFF',
transitions: {
OFF: {
press() {
this.state = 'ON'
}
},
ON: {
press() {
this.state = 'BLINK';
},
},
BLINK: {
press() {
this.state = 'OFF';
},
},
},
dispatch(actionName) {
const action = this.transitions[this.state][actionName];
if (action) {
action.call(this);
} else {
console.log('invalid action');
}
},
};
const flashlight = Object.create(machine);
console.log(flashlight.state); // OFF
flashlight.dispatch('press');
console.log(flashlight.state); // ON
flashlight.dispatch('press');
console.log(flashlight.state); // BLINK
We have created a new flashlight state object from the machine
to inherit all the properties and methods. Then we dispatched action, named 'press', which triggered a response for a state change.
To invoke action function in dispatch
we used action.call(this)
. A method Function.call()
provides a context (this
) for an action function, that refers to a newly created flashlight
object.
Summing up
Finite state machines let you control the flow of your application state. It defines what should happen when specific actions are determined during the current state and make your application less error-prone.
Top comments (4)
This is a great introduction Linas, thanks for sharing. I recently started to work with finite state machines in my project and use xstate.js.org.
If people find state machines interesting, XState is a good next step I think.
Very clean explanation! Thanks for sharing!
I think this is better for readibility:
I am learning typescript, how do I convert this into ts? Thanks.
State Transitions Flexibility: TypeScript Implementation
State Transitions No Flexible but Safety Typed: TypeScript Implementation