Updated at: 2014-03-06

KrewAsync 設計メモ

KrewAsync っての作った

割といい感じに設計・実装できたのでメモ。

コード部分は 200 行くらいでピュア AS3 のクラス 1 個だし、 テストもドキュメントもしっかり書いたし。

やる前に調べた

要件

  • メソッドチェーンじゃなくていい。JSON 書いてやりたい
  • Deferred / Promise は初心者には若干とっつきにくいだろう。パッと見の分かりやすさ大事
  • 「どの部分も、JSON 書いてもいいし、クラスオブジェクトに置き換えてもいい」みたいな構造にしたかった

構造

こういうふうに使う

var async:KrewAsync = new KrewAsync( {asyncDef} );
async.go();

ここで、{asyncDef} の定義がキモになる。 「クラスオブジェクトも指定できる」が今回 must の要件。 ってことには、究極的にはこう?

{asyncDef} ::= <KrewAsync>

つまり、KrewAsync はコンストラクタの引数に KrewAsync 自体を受け取って、 その内容をコピーすることで自分自身をつくる。 超再帰的。かっちょいい。 じゃあその KrewAsync はどうつくるの?

そこで、中身を定義する Object を渡してもいいことにする

{asyncDef} ::= <KrewAsync> ||
{
    // 以下 3 つのうちいずれかを指定
    single  : function(async:KrewAsync):void {},
    serial  : [{asyncDef}, ... ],
    parallel: [{asyncDef}, ... ],

    error : function():void {}   // optional
    anyway: function():void {}   // optional
}
// Object だったら、この情報をもとに作った KrewAsync インスタンスに置き換える。
// これで最初の定義を満たす形になった

この JSON の中の serial と parallel の配列の要素にも、{asyncDef} が指定可能。 末端の子ノードは single に指定された Function を実行することにする。

ここで、Function を指定したときは以下に置換する決まりを作る。 これによって、{asyncDef} のところにただの Function を並べることが可能になる。

// {asyncDef} が Function だったら以下に置き換え
{
    single: Function
}

あと Array が余ってるので、これも置換ルールを設定しておく

// {asyncDef} が Array だったら以下に置き換え
{
    serial: Array
}

これで {asyncDef} の部分は、

  • KrewAsync そのもののインスタンス
  • 中身を記述する Object
  • 末端ノードのタスクを意味する Function
  • serial のショートカットになる Array

どれを指定してもよいという柔軟なインタフェースになった。 めでたし。

拡張

ループや分岐

KrewAsync はシンプルでよい。コード上でシーケンスが見やすいことにも価値がある。 ただし、全ての要件を満たすわけではない。足りないものは何か。

  • n 回ループが記述できない
  • 分岐が記述できない

これについては KrewStateMachine と組み合わせて書く、とかでいいかもしれない。

途中で追加

だがもうひとつ、

  • タスクの途中で、タスクを追加したくなることがある

という要件がある。 つまりタスクの数が途中で動的に変わる (async.go を呼ぶ時点で全てのタスクが確定できない)というケースだ。

途中で自身のシーケンスに新たなタスクを足し込める拡張版もつくろうかな…

  • まあでもそれは Async 内で Async を使うとかでもよいか

全部成功したハンドラ

  • 今 fail が発生したときに error が呼ばれるけど、 「全部 done が呼ばれた」ときのハンドラも結構ほしくなる
  • finally とは違う
  • タスクの最後に追加しとくとかで書けはするんだけどね