Updated at: 2014-03-06
KrewAsync 設計メモ
KrewAsync っての作った
割といい感じに設計・実装できたのでメモ。
コード部分は 200 行くらいでピュア AS3 のクラス 1 個だし、 テストもドキュメントもしっかり書いたし。
やる前に調べた
- JSDeferred をはじめ、JS / AS3 の Deferred 系の既存ライブラリ調べた
- 参考リンク
要件
- メソッドチェーンじゃなくていい。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 とは違う
- タスクの最後に追加しとくとかで書けはするんだけどね