Simulating a Traffic Light with Bacon.js and state machine
Tags: javascript, baconjs, frp, statemachine
Published: 2024-07-01 21:15:38

Handling asynchronous events and managing state effectively are essential skills. Functional Reactive Programming (FRP) provides a powerful paradigm that simplifies these tasks by treating events as continuous streams of data.

### Understanding the Traffic Light State Machine

Let's start by defining the states and transitions of our traffic light using a simple state machine approach:

```javascript
const Bacon = require('baconjs');

// Define traffic light states and transitions
const trafficLightStateMachine = {
  initialState: 'Green',
  transitions: {
    'Green': { nextState: 'Yellow', duration: 3000 },
    'Yellow': { nextState: 'Red', duration: 1000 },
    'Red': { nextState: 'Green', duration: 2000 }
  }
};
```

Here, the traffic light begins at `'Green'`, transitions to `'Yellow'` after 3 seconds, then to `'Red'` after 1 second, and finally back to `'Green'` after another 2 seconds.

### Simulating Traffic Light Events

To simulate the traffic light's behavior, we'll create a stream of events using Bacon.js:

```javascript
const simulateTrafficLight = Bacon.fromArray([
  { type: 'state', value: 'Green' },
  { type: 'timeout' },
  { type: 'state', value: 'Yellow' },
  { type: 'timeout' },
  { type: 'state', value: 'Red' },
  { type: 'timeout' },
  { type: 'state', value: 'Green' },
  { type: 'timeout' }
]);
```

This `simulateTrafficLight` stream alternates between emitting state change events (`'state'`) and timeout events (`'timeout'`), mimicking the traffic light's transitions in a controlled manner.

### Implementing the State Machine with `withStateMachine`

The heart of our simulation lies in using `withStateMachine` provided by Bacon.js. It allows us to model the traffic light's behavior based on the defined state machine:

```javascript
simulateTrafficLight
  .withStateMachine(trafficLightStateMachine.initialState, function(state, event) {
    if (event.type === 'state') {
      // Emit the current state and schedule the next transition
      return [state, [{ type: 'timeout', delay: trafficLightStateMachine.transitions[state].duration }]];
    } else if (event.type === 'timeout') {
      // Transition to the next state
      const nextState = trafficLightStateMachine.transitions[state].nextState;
      return [nextState, [{ type: 'state', value: nextState }]];
    } else {
      // Pass through unknown events
      return [state, [event]];
    }
  })
  .log();
```

In this setup:
- When a `'state'` event is encountered, it emits the current state and schedules the next transition after the specified duration.
- When a `'timeout'` event occurs, it transitions to the next state as defined in the state machine.
- Any other events are passed through without changes.

### Visualizing the Traffic Light Simulation

By logging the output of our state machine, we can observe the sequence of state changes and timeouts as they occur based on our predefined simulation:

```javascript
simulateTrafficLight
  .withStateMachine(/*...*/)
  .log();
```


Functional Reactive Programming with Bacon.js offers a straightforward yet powerful approach to managing state and handling events in JavaScript. By using FRP principles, you can build applications that are not only responsive but also easier to maintain and extend over time.

Mastering these concepts opens up possibilities for creating more interactive and dynamic web experiences. Whether you're building a traffic light simulator or handling real-time data updates, Bacon.js and FRP provide a solid foundation for modern JavaScript development.

In conclusion, diving into FRP with Bacon.js can elevate your JavaScript skills and empower you to tackle complex event-driven scenarios with confidence.