fsm-async
v2.2.0
Published
Simple asynchronous state machine
Downloads
85
Readme
fsm-async
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.
Example
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