Sling Academy
Home/JavaScript/Modeling State Machines Manually for Predictable Flow in JavaScript

Modeling State Machines Manually for Predictable Flow in JavaScript

Last updated: December 12, 2024

State machines provide a structured way to represent different states and the transitions between them in any application. They allow for predictable behaviors, make applications easier to debug, and ensure robust handling of state transitions. In this article, we will walk through the steps to model state machines manually in JavaScript, exploring the necessary components to create predictable flows.

Understanding State Machines

A state machine is an abstract mathematical model of computation used to design algorithms and understanding complex systems. It consists of:

  • States: Distinct modes in which the system can exist.
  • Transitions: The rules which allow transitioning from one state to another.
  • Events: Inputs or actions that trigger transitions.
  • Initial State: The state from which the machine begins its execution.

Creating a Basic State Machine

Let’s start by creating a basic state machine. We'll model a simple traffic light system that transitions between three states: RED, GREEN, and YELLOW.

class StateMachine {
  constructor(initialState, transitions) {
    this.currentState = initialState;
    this.transitions = transitions;
  }

  transition(event) {
    const { currentState, transitions } = this;
    const stateTransitions = transitions[currentState];
    if (stateTransitions && stateTransitions[event]) {
      this.currentState = stateTransitions[event];
      console.log(`Transitioned to ${this.currentState}`);
    } else {
      throw new Error(`Invalid transition: ${event} from ${currentState}`);
    }
  }
}

const trafficLightStates = {
  RED: { change: 'GREEN' },
  GREEN: { change: 'YELLOW' },
  YELLOW: { change: 'RED' },
};

const trafficLight = new StateMachine('RED', trafficLightStates);

trafficLight.transition('change'); // GREEN
trafficLight.transition('change'); // YELLOW
trafficLight.transition('change'); // RED

Enhancing the State Machine

While this state machine can manage states and transitions, it can be further enhanced to perform actions during state transitions. This means adding an action that should perform some logic or calculations when the machine enters a new state.

class EnhancedStateMachine extends StateMachine {
  constructor(initialState, transitions, actions) {
    super(initialState, transitions);
    this.actions = actions;
  }

  transition(event) {
    super.transition(event);
    const action = this.actions[this.currentState];
    if (action) {
      action();
    }
  }
}

const trafficLightActions = {
  GREEN: () => console.log("Cars can go!"),
  YELLOW: () => console.log("Cars should slow down."),
  RED: () => console.log("Cars must stop!"),
};

const enhancedTrafficLight = new EnhancedStateMachine('RED', trafficLightStates, trafficLightActions);

enhancedTrafficLight.transition('change'); // GREEN and "Cars can go!"
enhancedTrafficLight.transition('change'); // YELLOW and "Cars should slow down."
enhancedTrafficLight.transition('change'); // RED and "Cars must stop!"

In the above example, we've extended the original StateMachine class to EnhancedStateMachine that now includes actions that print messages to the console upon entering a state.

Advanced Features

Our state machine can be extended to incorporate additional features such as:

  • Async Actions: Handling actions that require asynchronous operations, such as fetching data.
  • Guard Conditions: Conditions that must be fulfilled before a transition can occur.
  • Extended States: Handling complex state configurations with additional data.

Here’s an example of incorporating asynchronous operations:

class AsyncStateMachine extends EnhancedStateMachine {
  async transitionAsync(event) {
    try {
      await this.transition(event);
    } catch (error) {
      console.error(error);
    }
  }
}

async function simulateLateNightTraffic() {
  const asyncTrafficLight = new AsyncStateMachine('RED', trafficLightStates, trafficLightActions);

  await asyncTrafficLight.transitionAsync('change'); // GREEN
  await asyncTrafficLight.transitionAsync('change'); // YELLOW
  await asyncTrafficLight.transitionAsync('change'); // RED
}

simulateLateNightTraffic();

By manually modeling state machines in JavaScript, you gain fine-grained control over the state logic, enhancing the predictability and reliability of the application. This fundamental understanding and manual setup serve as a basis that can be extended to more complex use cases.

Next Article: Reducing Complexity by Splitting Large Conditions into Smaller Functions in JavaScript

Previous Article: Refining Control Flow by Separating Business Logic from Presentation in JavaScript

Series: Mastering Control Flow in JavaScript

JavaScript

You May Also Like

  • Handle Zoom and Scroll with the Visual Viewport API in JavaScript
  • Improve Security Posture Using JavaScript Trusted Types
  • Allow Seamless Device Switching Using JavaScript Remote Playback
  • Update Content Proactively with the JavaScript Push API
  • Simplify Tooltip and Dropdown Creation via JavaScript Popover API
  • Improve User Experience Through Performance Metrics in JavaScript
  • Coordinate Workers Using Channel Messaging in JavaScript
  • Exchange Data Between Iframes Using Channel Messaging in JavaScript
  • Manipulating Time Zones in JavaScript Without Libraries
  • Solving Simple Algebraic Equations Using JavaScript Math Functions
  • Emulating Traditional OOP Constructs with JavaScript Classes
  • Smoothing Out User Flows: Focus Management Techniques in JavaScript
  • Creating Dynamic Timers and Counters with JavaScript
  • Implement Old-School Data Fetching Using JavaScript XMLHttpRequest
  • Load Dynamic Content Without Reloading via XMLHttpRequest in JavaScript
  • Manage Error Handling and Timeouts Using XMLHttpRequest in JavaScript
  • Handle XML and JSON Responses via JavaScript XMLHttpRequest
  • Make AJAX Requests with XMLHttpRequest in JavaScript
  • Customize Subtitle Styling Using JavaScript WebVTT Integration