Created
December 12, 2022 13:03
-
-
Save intech/6273b75531f8ac8147108f170a74b07e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import FSM from "./fsm"; | |
const instance = new FSM([ | |
// Start state, default state | |
{ state: "Green", from: "Red", to: ["Yellow", "Error"] }, | |
// from is allowed array list states | |
{ state: "Yellow", from: "Green", to: ["Red", "Error"] }, | |
// to is single, auto change state to Green | |
{ state: "Red", from: "Yellow", to: ["Green", "Error"] }, | |
// End state | |
{ state: "Error", from: "*", end: true } | |
], "Green"); // Default state is Green | |
instance.preGreen = async (from, state) => { console.log(`Event 'preGreen' from '${from}' to ${state}`); return Promise.resolve(); }; | |
instance.postGreen = async (to, state) => { console.log(`Event 'postGreen' from '${state}' to ${to}`); return Promise.resolve(); }; | |
instance.preYellow = async (from, state) => { console.log(`Event 'preYellow' from '${from}' to ${state}`); return Promise.resolve(); }; | |
instance.postYellow = async (to, state) => { console.log(`Event 'postYellow' from '${state}' to ${to}`); return Promise.resolve(); }; | |
instance.preRed = async (from, state) => { console.log(`Event 'preRed' from '${from}' to ${state}`); return Promise.resolve(); }; | |
instance.postRed = async (to, state) => { console.log(`Event 'postRed' from '${state}' to ${to}`); return Promise.resolve(); }; | |
// example usages and results: | |
console.log(await instance.state()); // Yellow | |
console.log(await instance.state()); // Red | |
console.log(await instance.state()); // Green | |
try { | |
console.log(await instance.state("Red")); // throw StateError("Change state: Green -> Red"); | |
} catch (error) { | |
console.log(error.message); | |
} | |
try { | |
console.log(await instance.state("Error")); // change to end state Error | |
} catch (error) { | |
console.log(error.message); | |
} | |
try { | |
console.log(await instance.state("Green")); // throw StateError("Change end state: Error"); | |
} catch (error) { | |
console.log(error.message); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export default class FSM { | |
private currentState: string; | |
constructor(private states: FSMState[], init: string) { | |
const currentState = states.find(state => state.state === init)?.state; | |
if(!currentState) { | |
throw new StateError(`Cannot change init state to ${init}`); | |
} | |
this.currentState = currentState; | |
} | |
async state(toState?: string): Promise<string> { | |
const currentStateData = this.states.find(state => state.state === this.currentState); | |
const prevState = this.currentState; | |
if (this[`pre${this.currentState}`]) { | |
await this[`pre${this.currentState}`](toState, this.currentState); | |
} | |
if (currentStateData.end) { | |
throw new StateError(`End state reached: ${this.currentState}`); | |
} | |
if (toState) { | |
const toStateData = this.states.find(state => state.state === toState); | |
if (!currentStateData || !toStateData) { | |
throw new StateError("Invalid state provided"); | |
} | |
// Check if transition from the current state to the requested state is allowed | |
if (!currentStateData.to.includes("*") && !currentStateData.to.includes(toState)) { | |
throw new StateError(`Cannot change state from ${this.currentState} to ${toState}`); | |
} | |
// Check if transition from the requested state to the current state is allowed | |
if (!toStateData.from.includes("*") && !toStateData.from.includes(this.currentState)) { | |
throw new StateError(`Cannot change state from ${toState} to ${this.currentState}`); | |
} | |
this.currentState = toState; | |
} else { | |
// Transition to a new state according to the current state and the `to` property of the state data | |
this.currentState = Array.isArray(currentStateData.to) ? currentStateData.to[0] : currentStateData.to; | |
} | |
if (this[`post${prevState}`]) { | |
this[`post${prevState}`](this.currentState, prevState); | |
} | |
return this.currentState; | |
} | |
} | |
export interface FSMState { | |
state: string; | |
from: string | string[]; | |
to?: string | string[]; | |
end?: boolean; | |
} | |
export class StateError extends Error {} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment