Typescript statemachine

Date: 2018-03-13
import Promise from 'bluebird';

export interface Transition {
    name: string,
    from: string,
    to: string
}

export class StateMachine {
    private previousState : string;
    private currentState : string;
    private transitions : Transition[];
    private transitionCallbacks : any = {};
    private enterCallbacks : any = {};
    private leaveCallbacks : any = {};
    constructor(intitialState : string, transitions: Transition[])
    {
        this.previousState = intitialState;
        this.currentState = intitialState;
        this.transitions = transitions;
    }
    private changeState(transitionName : string, newState : string) {
        console.log(`${transitionName}: ${this.currentState} -> ${newState}`);
        this.previousState = this.currentState;
        this.currentState = newState;

        let cb;
        cb = this.leaveCallbacks[this.previousState];
        if (cb) {
            setTimeout(cb, 0);
        }
        cb = this.enterCallbacks[this.currentState];
        if (cb) {
            setTimeout(cb, 0);
        }
    }
    findTransition(transitionName: string, doThrow : boolean = false) : Transition | null {
        let transitions = this.transitions.filter(t => t.name == transitionName);
        if (transitions.length < 1) {
            if (doThrow) {
                throw new Error(`transition '${transitionName}' does not exist`);
            }
            return null;
        }
        let transition = transitions.filter(t => t.from == '*' || t.from == this.currentState).pop();
        if (!transition) {
            if (doThrow) {
                throw new Error(`transition '${transitionName}' not possible from state '${this.currentState}'`);
            }
            return null;
        }
        return transition;
    }
    trigger(transitionName: string) : void {
        let transition = this.findTransition(transitionName, true);
        console.log(`triggered: ${transitionName}`);
        let cb = this.transitionCallbacks[transitionName];
        if (cb) {
            Promise.method(cb)({})
                .then(() => this.changeState(transitionName, transition!.to))
                .catch(() => Promise.resolve());
        } else {
            this.changeState(transitionName, transition!.to);
        }
    }
    canTrigger(transitionName: string) : boolean {
        let transition = this.findTransition(transitionName, false);
        if (!transition) {
            return false;
        }
        return true;
    }
    getState() : string {
        return this.currentState;
    }
    public on(transitionName : string, callback: Function) : void {
        this.transitionCallbacks[transitionName] = callback;
    }
    public onEnter(stateName : string, callback: Function) : void {
        this.enterCallbacks[stateName] = callback;
    }
    public onLeave(stateName : string, callback: Function) : void {
        this.leaveCallbacks[stateName] = callback;
    }
}
import Promise from 'bluebird';

import {StateMachine} from './StateMachine'


describe('StateMachine', () => {

    it('should change state', (done) => {

        let stateMachine = new StateMachine('created', [
            { name: 'load', from: 'created', to: 'loading'},
            { name: 'setReady', from: 'loading', to: 'ready'},
            { name: 'play', from: 'ready', to: 'playing'},
            { name: 'stop', from: 'playing', to: 'ended'},
            { name: 'cancel', from: '*', to: 'ended'}
        ]);


        stateMachine.trigger('load');

        stateMachine.on('setReady', () => Promise.delay(1000));

        stateMachine.onLeave('loading', () => {
            console.log('I am loaded');
        });

        stateMachine.onEnter('ended', () => {
            console.log('I am ended');
        });

        stateMachine.onEnter('ready', () => {
            console.log('I am ready');

            stateMachine.trigger('cancel');

            stateMachine.trigger('play');

            stateMachine.trigger('stop');
        });

        stateMachine.trigger('setReady');

        setTimeout(done, 3000);
    })
});
const Promise = require('bluebird');

// Transition { name, from, to }
// OnStateChanged = (transition, currentState, newState) {};

class StateMachine {
    constructor(intitialState, transitions, onStateChanged)
    {
        this.previousState = intitialState;
        this.currentState = intitialState;
        this.transitions = transitions;
        this.onStateChanged = onStateChanged;
        this.transitionCallbacks = {};
        this.enterCallbacks = {};
        this.leaveCallbacks = {};
    }
    changeState(transitionName, newState) {
        this.previousState = this.currentState;
        this.currentState = newState;
        this.onStateChanged(transitionName, this.previousState, this.currentState);

        let cb;
        cb = this.leaveCallbacks[this.previousState];
        if (cb) {
            setTimeout(cb, 0);
        }
        cb = this.enterCallbacks[this.currentState];
        if (cb) {
            setTimeout(cb, 0);
        }
    }
    findTransition(transitionName, doThrow) {
        let transitions = this.transitions.filter(t => t.name == transitionName);
        if (transitions.length < 1) {
            if (doThrow) {
                throw new Error(`transition '${transitionName}' does not exist`);
            }
            return null;
        }
        let transition = transitions.filter(t => t.from == '*' || t.from == this.currentState).pop();
        if (!transition) {
            if (doThrow) {
                throw new Error(`transition '${transitionName}' not possible from state '${this.currentState}'`);
            }
            return null;
        }
        return transition;
    }
    trigger(transitionName) {
        let self = this;
        let transition = this.findTransition(transitionName, true);
        let cb = this.transitionCallbacks[transitionName];
        setTimeout(() => {
            if (cb) {
                Promise.method(cb)({})
                    .then(() => self.changeState(transitionName, transition.to))
                    .catch(() => Promise.resolve());
            } else {
                self.changeState(transitionName, transition.to);
            }
        }, 0);
    }
    canTrigger(transitionName) {
        let transition = this.findTransition(transitionName, false);
        if (!transition) {
            return false;
        }
        return true;
    }
    getState() {
        return this.currentState;
    }
    on(transitionName, callback) {
        this.transitionCallbacks[transitionName] = callback;
    }
    onEnter(stateName, callback) {
        this.enterCallbacks[stateName] = callback;
    }
    onLeave(stateName, callback) {
        this.leaveCallbacks[stateName] = callback;
    }
}

module.exports = StateMachine;

 

const Promise = require('bluebird');

// Transition { name, from, to }
// OnStateChanged = (transition, currentState, newState) {};

class StateMachine {
    constructor(intitialState, transitions, onStateChanged) {
        this.previousState = intitialState;
        this.currentState = intitialState;
        this.transitions = transitions;
        this.onStateChanged = onStateChanged;
        this.transitionCallbacks = {};
        this.enterCallbacks = {};
        this.leaveCallbacks = {};
    }
    changeState(transitionName, newState) {
        this.previousState = this.currentState;
        this.currentState = newState;
        this.onStateChanged(transitionName, this.previousState, this.currentState);

        let cb;
        cb = this.leaveCallbacks[this.previousState];
        if (cb) {
            setTimeout(cb, 0);
        }
        cb = this.enterCallbacks[this.currentState];
        if (cb) {
            setTimeout(cb, 0);
        }
    }
    findTransition(transitionName, doThrow) {
        let transitions = this.transitions.filter(t => t.name == transitionName);
        if (transitions.length < 1) {
            if (doThrow) {
                throw new Error(`transition '${transitionName}' does not exist`);
            } else {
                console.log(`transition '${transitionName}' does not exist`);
            }
            return null;
        }
        let transition = transitions.filter(t => t.from == '*' || t.from == this.currentState).pop();
        if (!transition) {
            if(transitions[0] && transitions[0].to == this.currentState) {
                console.log(`already in state '${this.currentState}', skipping transition: '${transitionName}'`);
            } else if (doThrow) {
                throw new Error(`transition '${transitionName}' not possible from state '${this.currentState}'`);
            } else {
                console.log(`transition '${transitionName}' not possible from state '${this.currentState}'`);
            }
            return null;
        }
        return transition;
    }
    trigger(transitionName) {
        let self = this;
        let transition = this.findTransition(transitionName, false);
        let cb = this.transitionCallbacks[transitionName];
        if (transition) {
            setTimeout(() => {
                if (cb) {
                    Promise.method(cb)({})
                        .then(() => self.changeState(transitionName, transition.to))
                        .catch(() => Promise.resolve());
                } else {
                    self.changeState(transitionName, transition.to);
                }
            }, 0);
        }
    }
    canTrigger(transitionName) {
        let transition = this.findTransition(transitionName, false);
        if (!transition) {
            return false;
        }
        return true;
    }
    getState() {
        return this.currentState;
    }
    on(transitionName, callback) {
        this.transitionCallbacks[transitionName] = callback;
    }
    onEnter(stateName, callback) {
        this.enterCallbacks[stateName] = callback;
    }
    onLeave(stateName, callback) {
        this.leaveCallbacks[stateName] = callback;
    }
}

module.exports = StateMachine;
10520cookie-checkTypescript statemachine