javascriptでFSM(ステートマシン)を手軽に実装できるライブラリがないか探していたところ、javascript-state-machineというのが見つかりました。
本記事作成の2015/10/26時点でStar数2440となかなか人気のようです。
環境依存の要素は特にないので、nodeでもブラウザでも普通に動きます。
githubのREADMEを見ながらcodepenで動くサンプルを実装してみました(coffeeで書いてます)。
初期化
fsm = StateMachine.create({
initial: 'green',
events: [
{ name: 'warn', from: 'green', to: 'yellow' },
{ name: 'panic', from: 'yellow', to: 'red' },
{ name: 'calm', from: 'red', to: 'yellow' },
{ name: 'clear', from: 'yellow', to: 'green' }
]});
createメソッドで初期化します。initialに初期状態を書いて、eventsプロパティにイベント名、遷移前状態名、遷移後状態名のリストをどんどん書いていきます。分かりやすいですね。
状態遷移
console.log("initial state: #{fsm.current}")
fsm.warn()
console.log("warn executed, now state changed to: #{fsm.current}")
fsm.panic()
console.log("panic executed, now state changed to: #{fsm.current}")
###
initial state: green
warn executed, now state changed to: yellow
panic executed, now state changed to: red
###
状態遷移してみましょう。まずは初期状態では当然green。
初期化でgreenからはwarnメソッドによって遷移できると書いてあるので、warn()するとstateはyellowに。同じようにpanic()を実行するとredになっています。
エラーハンドリング
console.log("lets try to call invalid warn event from #{fsm.current}!")
try
fsm.warn()
catch error
console.log("it raises error by default with following message: #{error}")
###
lets try to call invalid warn event from red!
it raises error by default with following message: event warn inappropriate in current state red
###
では不正な状態遷移をすると何が起こるか見てみましょう。現状stateはredなので、warnは呼べないはずです。ここで無理やり呼んでみます。すると、デフォルトの挙動ではerrorをraiseして実行が止まるようになっています。このサンプルではここで止まっては嫌なのでtry-catchでメッセージをconsole.logしています。
このエラーハンドラは初期化もしくは後から付け加えることができます。
console.log("we can register customized error handler")
fsm.error = ()->
console.log("this is customized error function! (does not raise error)")
fsm.warn()
###
we can register customized error handler
this is customized error function! (does not raise error)
###
このように、errorメソッドを上書きすると、そもそもerrorをraiseすることもありません。
ちなみにfsmオブジェクトに後付けでメソッドを追加する方法はドキュメントには記載されていないので、そのうちdeprecatedになるかもしれません。
コールバック
fsm.onbeforecalm = -> console.log("A. going to call calm()!")
fsm.onaftercalm = -> console.log("B. after calm() executed!")
fsm.onenteryellow = -> console.log("C. entering yellow state!")
fsm.onleavered = -> console.log("D. leaving red!")
fsm.onbeforeevent = -> console.log("E. on before event!")
fsm.onafterevent = -> console.log("F. on after event!")
fsm.onleavestate = -> console.log("G. on leave state!")
fsm.onenterstate = -> console.log("H. on enter state!")
fsm.calm()
###
A. going to call calm()!
E. on before event!
D. leaving red!
G. on leave state!
C. entering yellow state!
H. on enter state!
B. after calm() executed!
F. on after event!
###
状態遷移の際にいろんな条件でコールバックを呼べるようです。遷移やイベント発行の前後に色んな条件で挟めます。例えばonleaveredはredから状態遷移をする時に呼ばれ、onleavestateはどの状態でも遷移する時に呼ばれます。
呼ばれる順番もきになったので試しに全部設定してみました。こういう順番で呼ばれるみたいです。
コールバックの設定はドキュメントにあるので後付は公式にOKっぽい。
まとめ
jsでFSMを管理できるライブラリは他にもMachinaなどがあるようですが、設定が簡潔に書いてあって直感的に分かるjavascript-state-machineが一番好きだなーと思ったのでこちらを使うことにしました。