machina.js – Finite State Machines in JavaScript

On March 12, 2012, in JavaScript, by Jim Cowart

I’ve been working on a helper framework recently, with the goal of providing a flexible means to write finite state machines (FSMs) in JavaScript.  I’ve borrowed some ideas from the world of Erlang/OTP (the gen_fsm behaviour is wonderful!), and the result of my efforts thus far is machina.js.

First – What is an FSM?

A finite state machine is an architectural model which can exist in only one of a finite number of states at a given time – and thus it responds differently to the same input depending on the state in which it is currently in.  A simple example is a traffic light.  The light responds to traffic differently depending on its state (red vs green) – obviously telling it to halt if the current state is ‘red’, and to proceed if the state is ‘green’.  This is a powerful pattern – capable of modeling and solving any number of problems at any layer of an application – and client-side web applications are no exception.  Consider some of the possible scenarios an FSM could address:

  • Application Initialization:  You might have a series of states in which your web application can exist prior to when the user should be allowed to interact with the interface.  During this time you might be pre-loading external templates, retrieving external data, rendering DOM elements dynamically, etc.
  • Offline vs Online: If your web application needs to support the ability to work offline, then you could use an FSM to abstract how (and where) data is stored given the two possible states.
  • “Grouped” Changes to UI: It’s quite possible that the UI in your web app changes configuration (i.e. – several views swapped in & out) based on where the user has navigated.  An FSM can abstract the “UI configuration” states, allowing you to simply say (via pseudo code) “transition into the ‘Customer Module’ state”, where the FSM will handle the smooth transition away from the current UI (which may involve hiding elements, undelegating events, animated transitions, etc.) and into the selected UI (which may involve showing new elements, retrieving external templates/data, transitions, etc.).
  • View Lifecycle: If you’re using a framework like Backbone.js (or any other) which has the concept of a view object, an FSM can wrap/manage the view’s lifecycle concerns – responding differently to messages based on one of several possible states (like “not-instantiated”, “not-rendered”, “shown”, “hidden”, etc.)

It’s certainly possible to (mostly) manage the above concerns using deferreds – but after having spent time in codebases liberally sprinkled with deferreds (many times, several layers deep of nested deferreds), I find that they very quickly can become difficult to test, difficult to debug and (most importantly), they lock together a series of steps which I may want to allow to only partially run.  For example, consider the “Application Initialization” scenario above.  If part of the initialization process is retrieving external data, I may want to allow the user to ‘refresh’ that data without re-triggering the entire init process again.  While this can easily be done with a second deferred (or set of them), if I’ve abstracted the problem with an FSM, I only have to transition back to the state where the app is retrieving data and allow it to process from there.  And I love being able to take advantage of the times that SOLID and DRY exist together in peace (when they make a nice deodorant….rooting out code-smells).

Erlang/OTP Influence on machina.js

I’ve had the good fortune to spend a decent amount of time over the last year learning about the mind-melting world of Erlang.  The OTP library for Erlang provides several “behaviours” (somewhat like a ‘template’ for creating a process that follows certain API conventions) – one of which is the Gen_Fsm behaviour.  There are two things that I think make Erlang’s FSM behaviour super powerful:  first, the current state is the name of the function that will get invoked, and second – Erlang’s pattern matching makes the way a state handles different messages amazingly concise, flexible and extensible.  Let’s look at a quick snippet.

If we have an Erlang FSM which has “initializing”, “loading” and “loaded” states, then we might see something like the following snippet:

initializing({load_customer, CustomerId}, State) ->
    NewState = State#state{ customer_id = CustomerId },
    {next_state, loading, NewState}.

loading({succeeded, Customer}, #state{customer_id = CustomerId} = State) ->
    event_module:notify("Successfully Loaded Customer ~p ~n", [ CustomerId ]),
    NewState = State#state{ customer = Customer },
    {next_state, loaded, NewState}.

loaded({add_order, Order}, #state{customer = Customer}) ->
    order_module:process_order(Customer, Order).

The above code shows three Erlang functions, each representing one possible way to handle a message in a given state. The “initializing” function above will only be invoked if the current state of the FSM is “initializing”, and if the first argument is a tuple which has the atom “load_customer” as the first part. There could actually be any number of “initializing’ function overloads, each handling a different (for example) tuple in the first argument – this is one small example of the beautiful world of pattern matching in Erlang. Note that at the end of the “initializing” function, we’re returning a tuple that tells the FSM to transition to the “loading” state, passing the “NewState” record along with it.

The ‘loading’ function example above would only get invoked if the FSM was in the “loading” state and if the first argument was a tuple, with the atom “succeeded” as the first part of the tuple. You probably get the idea from here. The concept of the current state value also being the name of the function to invoke to handle a message/event, and the ability to “match” the right function to invoke based on the value of arguments passed – these are two key ideas I’ve borrowed from Erlang/OTP in my attempt to write a decent JavaScript FSM framework. Oh that JavaScript could support pattern matching on method signatures!

UPDATE – Elijah Manor suggested I go ahead and include a brief JavaScript version of how the above erlang functions would look if converted to use machina.js (I was worried about post length, but if you’ve made it this far, thanks!):

var fsm = new machina.Fsm({
    states: {
        initializing: {
            // other handlers for the "initializing" state would go here
            load_customer: function(customerId) {
                this.customer_id = customerId;
                this.transition("loading");
            }
        },
        loading: {
            // other handlers for the "loading" state would go here
            succeeded: function(customer) {
                this.channel.publish("Successfully Loaded Customer " + customer.id);
                this.customer = customer;
                this.transition("loaded");
            }
        },
        loaded: {
            // other handlers for the "loaded" state would go here
            add_order: function(order) {
                orderProcessor.processOrder(this.customer, order);
            }
        }
    }
});

So How Did I Do It?

Machina.js organizes an FSM’s states into objects, where each state is an object literal which contains functions that are named to represent the kind of “message” or “event” they handle.   The state object – while obviously not a function – parallels the Erlang/OTP FSM in that only methods that live on that object will get invoked if the FSM’s current state value (which is a string) matches the member name that object is assigned to. The name(s) of functions that exist on a given state’s object parallel how Erlang pattern-matches to find the correct function to invoke by matching the function signature against the arguments actually passed.

For example, let’s say we have a machina.js-based FSM which is in the “ready” state.  If it receives a message telling it to handle “customer.getData” – or if someone directly invokes “handle(“customer.getData” /*, other args */) –  then it will look for “this.states.ready” (i.e.- do we have handlers for the “ready” state), and then ‘this.states.ready.[”customer.getData”]’.  If the handler is found, it invokes that function, passing in the arguments provided to the “handle” call (i.e. ‘this.states.ready[”customer.getData”](/* other args */).  A machina FSM can also have a “catch-all” handler (indicated by a “*”), which will handle any messages where a named handler doesn’t match the message/event type.  If the FSM can’t find a named handler or a catch-all handler, the message/event is simply ignored.

Moving Beyond the Basics

I’ve bounced a lot of my ideas for machina.js off of Alex Robson (who is a far better Erlang developer than I am, and is helping me “dog-food” machina in other projects).  Alex inspired additions to machina like the “deferUntilTransition” and “deferUntilNextHandler” calls.  Calling “deferUntilTransition()” inside a state’s handler will queue the current handler’s arguments up to be replayed after the FSM transitions into a new state.  This adds a lot of power to the FSM, as it enables you to not worry about when it receives messages, since you can defer them until the FSM transitions into a state that can appropriately handle the message.  The “deferUntilTransition” call takes an optional “stateName” argument, which will cause it to wait until the FSM has transitioned into a specific state and not just the ‘next’ state.  The “deferUntilNextHandler” call is similar, except it only waits until the next handler is invoked before it tries to replay the message, and does not wait for a state transition.

Another thing I intended for machina.js from the start was message bus integration.  Machina supports two JavaScript message bus providers out of the box: postal.js and amplify.js.  It’s worth noting that since postal.js supports exchanges and wildcard bindings, you get a little more power in integrating machina with postal, but either library allows you to communicate easily with the FSM via messaging.  (Good de-coupling, FTW!)  You aren’t  required to interact with machina.js via messaging, though, so feel free to use the API directly for both invoking state handlers (via the “handle” call) and subscribing to events (via “on” and “off”).  Also – if you have a favorite pub/sub library already, you can write a provider for it to integrate with machina.js – just look at the postal and amplify providers for reference.

“machina.utils” provides more extensions points, enabling you to alter default configuration options for how FSMs are created, and changing what your message “payloads” look like if you’re using machina in conjunction with a message bus provider. 

Parting Thoughts

I’ve been pleasantly surprised to hear of other JavaScript developers discussing the use of FSMs.  It’s strengthening my conviction that they are often a superior abstraction for solving certain probems than deferreds.  If this sounds interesting, then go check out machina.js.  There are two runnable examples included in the repository, so download, explore and have fun!  And then come back here and share your thoughts….