A state machine implementation featuring:
-
on<state>
life-cycle events, allowing the triggering of further (internal) events during the callback. -
async
event functions that can be awaited. Depending on the implemented logic, multiple state changes can be awaited. -
Generic and awaitable
waitUntilStateEnters(<state>)
andwaitUntilStateLeaves(<state>)
functions providing full flexibility to state machine clients business logic.
Define the transition table as a json object,
const transitionTable = {
initial: 'disconnected',
transitions: [
{ ev: 'connect', from: 'disconnected', to: 'connecting' },
{ ev: '_connectDone', from: 'connecting', to: 'connected' },
{ ev: 'disconnect', from: 'connected', to: 'disconnecting' },
{ ev: '_disconnectDone', from: 'disconnecting', to: 'disconnected' }
]
}
then apply this logic to your object:
const StateMachine = require('fsm-async')
class MyClient extends StateMachine {
constructor () {
const transitionTable = {
initial: 'disconnected',
transitions: [
{ ev: 'connect', from: 'disconnected', to: 'connecting' },
{ ev: '_connectDone', from: 'connecting', to: 'connected' },
{ ev: 'disconnect', from: 'connected', to: 'disconnecting' },
{ ev: '_disconnectDone', from: 'disconnecting', to: 'disconnected' }
]
}
super(transitionTable)
}
}
This injects the events as proper callable functions to your instance, hence you can write:
myClient = new MyClient()
myClient.connect()
In the body of your class you can define life-cycle functions on<event>
and
on<state>
, which are automatically called and can be used to trigger
further events:
const StateMachine = require('fsm-async')
class MyClient extends StateMachine {
constructor () {
const transitionTable = {
initial: 'disconnected',
transitions: [
{ ev: 'connect', from: 'disconnected', to: 'connecting' },
{ ev: '_connectDone', from: 'connecting', to: 'connected' },
{ ev: 'disconnect', from: 'connected', to: 'disconnecting' },
{ ev: '_disconnectDone', from: 'disconnecting', to: 'disconnected' }
]
}
super(transitionTable)
}
// Use async here to be able to await internally
async onConnecting () {
// Simulate connection establishment
await new Promise(resolve => setTimeout(resolve, 1000))
// Internally trigger an event bringing the machine to connected state
this._connectDone()
}
async onDisconnecting () {
// Simulate disconnection
await new Promise(resolve => setTimeout(resolve, 1000))
// Internally trigger an event bringing the machine to disconnected state
this._disconnectDone()
}
}
Now, outer code can await
the connect()
of your client and/or use other
utility functions injected by the StateMachine
. The utility functions are:
getState()
returns current statewaitUntilStateEnters(<state>)
waits until a given state is enteredwaitUntilStateLeaves(<state>)
waits until a given state is leftonStateChange(<callback(state)>)
notifies about state changesonInvalidTransition(<callback(event, state)>)
notifies about invalid transitions
The StateMachine
class at the same time is an event emitter. Hence,
stateMachine.on('state', <callback(state)>)
stateMachine.on('invalidTransition', <callback(event, state)>)
is also possible.
Please see the provided example code (examples
folder) for more details and
usage patterns.
You can run the example code via:
npm run example