Flow.js
A very simple way to wait for
  asynchronous processes.

                     Version 1.0.1
JavaScript には
非同期プログラミングが
   つきものです
XHR,
      onload,
    setTimeout,
   postMessage,
 addEventListener,
DOMContentLoaded
                 沢山
非同期プログラミングを
支援するイディオムには、
 Deferred/Promises,
    async/await
  などがありますが、
今回紹介する Flow.js も、
 非同期プログラミングを
上手く扱う方法の1つです
Flow.js の
   元となったニーズとウォンツ
•  複数の非同期処理の完了を待ちたい
   •  ダウンロードの完了を待ちつつアニメーションしたい
   •  同期/非同期処理がちょっと離れた場所で混在しているが、
      一方はカウンター、一方はコールバックの連鎖などで待機方法がバラバラ
      これらを画一的な仕組で待てないか
   •  いくつかの非同期処理をグルーピングし、それらの終了を待ちたい事がよ
      くあるが、毎回同じようなコードを書いている気がする

•  シンプルな実装がほしい
   •  Deferred/Promiss を
      JavaScriptに詳しくない方に説明するのは骨が折れる

•  運用で困らないようにしたい
   •  特定の環境に依存したり、頻繁に更新される
      重厚なライブラリには依存できない(したくない)

•  デバッグのしやすさも大事
   •  どの非同期処理で止まっているのか原因を素早く特定したい
Flow.js の使い方
導入
npm または github から flow.js を取得してください。
flow.js はコード単体で利用可能です。
$ npm install flow_js	
	
  	var Flow = require("flow_js").Flow;	
	
または、	
	
$ git clone git@github.com:uupaa/flow.js.git	
$ cd flow.js
	
Browser:	
  	<script src="flow.js"></script>
基本的な考え方
•  Flowではユーザ側の同期/非同期処理を処理と呼びます
   Flowは処理が同期か非同期かを区別していません
•  Flowは処理が終わるまで待機し、待機終了でcallbackを呼びます
•  処理成功でpass()を、失敗でmiss()を呼んでください
•  miss()を呼ぶと待機失敗で終了します。このデフォルトの動作を
   変更する場合は、missable()で失敗可能な回数を指定します
•  waits には待機する処理の数を指定します、
   pass()の呼出回数がwaits以上になると、待機成功で終了します

var waits = 3;	
var flow = new Flow(waits, callback);	
function callback(err, args) { ... }	
function userFunction(flow) { ...; flow.pass(); return }
基本的な使い方
new Flow(waits, callback)でインスタンスを作成します。
waits に待機する処理の数を、
callback には待機終了で呼び出す関数を指定します。

callback(err, args)のerrには待機成功でnullが、
待機失敗でErrorオブジェクトが渡されます。
argsはpass(value)やmiss(value)で指定したvalueの配列です。

// 2つ処理が終わるまで待機を行い、待機終了でcallbackを呼ぶ	
var flow = new Flow(2, callback);	
function callback(err, args) { ... }	
setTimeout(function() { flow.pass(); }, Math.random() * 1000); 	
setTimeout(function() { flow.pass(); }, Math.random() * 1000);
同期/非同期処理の成功で pass()
同期/非同期処理の成功でpass()を実行します。
pass()をwaits回数分呼び出すとFlowが待機成功となり、
callback(null)を呼び出してFlowも終了します。




var flow = new Flow(1, callback);	
flow.pass(); // 処理成功 -> 待機成功 -> callback(null);
同期/非同期処理の失敗で miss()
同期/非同期処理の失敗でmiss()を実行します。
miss()を呼び出すとFlowが待機失敗となり、
callback(Errorオブジェクト) を呼び出してFlowも終了します。




var flow = new Flow(1, callback);	
flow.miss(); // 処理失敗 -> 待機失敗 -> callback(Error);
passとmissに値を渡す
pass(value) や pass(value, key) と値を指定すると
callbackの第二引数 args から参照する事ができます。
miss(value), miss(value, key) も同様です。

key を指定すると args.key で value を検索できます。
key を指定することで、 pass(value) の実行順を意識せずに
値を受け渡すことができます。

var flow = new Flow(3, function(err, args) { // [1, "value"]	
     console.log(args["key"]); // -> "value"	
});	
flow.pass(); // value を指定しない	
flow.pass(1);	
flow.pass("value", "key");
非同期プログラミングを柔軟に、
もっと便利にする使い方
失敗可能な回数を設定する
3回中1回成功すれば良い(2回まで失敗可能)といった、
確率的な待機を行う場合に、missable(count) を実行します。
count には失敗しても良い回数を指定します。
missableで指定した回数以上 miss() を呼ぶと待機失敗となります。

missable を設定した場合は、待機成功の条件が式1から式2になります
式1: pass()実行数 >= waits 成立で待機成功で終了
式2: pass()実行数 + miss()実行数 >= waits 成立で待機成功で終了

var flow = new Flow(3, callback).missable(2);	
flow.miss(); // 処理失敗 -> missable=2 なので許容する
flow.miss(); // 処理失敗 -> missable=2 なので許容する
flow.miss(); // 処理失敗 -> missable=2 なので待機失敗	
             //        -> callback(Error("fail"), [])
待機処理数を継ぎ足す
extend(waits)で動的に待機処理数の継ぎ足しが可能です。
waits には追加する処理数を指定します。

最初は1つしかなかった非同期処理が、内部で次々に非同期処
理を呼び込むようなケースで利用します。




var flow = new Flow(1, callback);	
flow.extend(1);
flow.pass(); // 処理成功 -> waits=2 なので待機する
flow.pass(); // 処理成功 -> 待機成功 -> callback(null, [])
Flowを強制終了する
exit()でFlowを強制終了することができます。
待機失敗で終了し、callback(Error("exit")) が渡されます




var flow = new Flow(2, callback);	
flow.exit(); // 強制終了 -> 待機失敗	
             //        -> callback(Error("exit"), [])
Junction - 合流する
callbackに、別のFlowのインスタンスを指定することで、
Junction(合流)を作ることができます。
flow1とflow2のargsはjunctionのargsに引き継がれます。

      flow1
                            junction           callback
      flow2
var junction = new Flow(2, callback);	
var flow1 = new Flow(2, junction).pass(1).pass(2);	
var flow2 = new Flow(2, junction).pass(3).pass(4);	
function callback(err, args) { // [ [1,2], [3,4] ]	
   var values = Array.prototype.concat.apply([], args).sort();	
   console.log(values); // [1,2,3,4]	
}
Fork - 分岐する
flows として分岐先を { name: 関数/Flow, ... } で指定し、
fork(name)で、待機終了後の分岐先を指定できます。
一番上の項目がデフォルトの分岐先になります。

         flow             fork("fork1")(default)

                         fork("fork2")
var flows = { "fork1": function(err, args) { ... },	
              "fork2": function(err, args) { ... } };	
var flow = new Flow(1, flows);	
	
flow.fork("fork2"); // fork先を"fork2"に切り替え	
flow.pass(); // -> 処理成功 -> 待機成功 -> fork2(null, []) を呼出
例外発生時のエスカレーション
例外のハンドリングと対処はユーザ側で行い、
miss()を呼び出してください。

var flow = new Flow(2, callback);	
	
function someMethod() {	
   try {	
      // throw new TypeError("BAD_CASE");	
      flow.pass(); 	
   } catch (err) {	
      flow.miss();	
   }	
}
待機中のFlowをダンプする
new Flow(,, tag) を指定すると、
Flow.dump()で待機中のFlowの一覧がダンプ可能になります。

不具合で、いつまでも終了しないFlowの調査に役立ちます。

tagはできるだけユニークな名前をつけるようにしてください。
同じtagを再利用すると、以前の結果を上書きしてしまいます。
var flow1 = new Flow(2, callback, "flowA");	
var flow2 = new Flow(2, callback, "flowB");	
Flow.dump();	
{ "flowA": { waits: 2, pass: 0, state: "progress", ... } },	
{ "flowB": { waits: 2, pass: 0, state: "progress", ... } }	
Flow.dump(true); // ダンプ後に内部情報をクリアします
非同期に完了する処理をまとめた2つのグループ([A,B], [C,D])と、
さらにそれら2つの完了を待ち合わせる合流処理の例です。
	

例: 非同期処理のネスト
非同期に完了する2つのグループと、
それらを待ち合わせるJunctionによる
    非同期プログラミング



 非同期処理グループ1

処理A      処理B
                    junction


                finishedCallback


 非同期処理グループ2


                       ript
処理C      処理D

               Ja vaSc
// Waiting for the completion of the asynchronous processes.	
function waitForAsyncProcesses(finishedCallback) {	
   // Remaining count of the asynchronous processes.	
   var waits1 = [A, B].length; // 2	
   var waits2 = [C, D].length; // 2	
   var waits3 = 2;	
	
   function A() { setTimeout(function() { done_group1(); }, 10); }	
   function B() { setTimeout(function() { done_group1(); }, 100); }	
   function C() { setTimeout(function() { done_group2(); }, 20); }	
   function D() { setTimeout(function() { done_group2(); }, 200); }	
	
   function done_group1() {	
      if (--waits1 <= 0) { junction(); }	
   }        	
   function done_group2() {	
      if (--waits2 <= 0) { junction(); }	
   }	
   function junction() {	
      if (--waits3 <= 0) { finishedCallback(); }	
   }	
                                                       vaSc ript
   A(), B(), C(), D();	                            Ja
}
非同期に完了する2つのFlowと、
それらを待ち合わせるFlow(junction)による
     非同期プログラミング



 group1 = new Flow(2, junction)
    処理A              処理B
gorup1.pass()    gorup1.pass()    junction = new Flow(2)


                                    finishedCallback


 group2 = new Flow(2, junction)
    処理C              処理D
gorup2.pass()    gorup2.pass()
                                      Flo w.js
// Rewritten by Flow.js	
function waitForAsyncProcesses(finishedCallback) {	
   // Create the flow instances, and build a combination   of flow.	
   var junction = new Flow(2, finishedCallback);	
   var group1   = new Flow([A, B].length, junction);	
   var group2   = new Flow([C, D].length, junction);	
	
   function A() { setTimeout(function() { group1.pass();   },   10); }	
   function B() { setTimeout(function() { group1.pass();   },   100); }	
   function C() { setTimeout(function() { group2.pass();   },   20); }	
   function D() { setTimeout(function() { group2.pass();   },   200); }	
	
   A(), B(), C(), D();	
}	
	




                                                       Flo w.js
非同期に完了する2つのPromiseと
  それらを待ち合わせるPromiseによる
      非同期プログラミング


         jQuery.when
    処理A             処理B
dfd.promise()   dfd.promise()       jQuery.when


                                finishedCallback


         jQuery.when
    処理C             処理D
dfd.promise()   dfd.promise()               erred
                                jQue ry.Def
// Rewritten by jQuery.Deferred	
function waitForAsyncProcesses(finishedCallback) {	
   var promises1 = [A(), B()];	
   var promises2 = [C(), D()];	
   function A() { var dfd = jQuery.Deferred();	
                  setTimeout(function() { dfd.resolve(); }, 10);	
                  return dfd.promise(); }	
   function B() { var dfd = jQuery.Deferred();	
                  setTimeout(function() { dfd.resolve(); }, 100);	
                  return dfd.promise(); }	
   function C() { var dfd = jQuery.Deferred();	
                  setTimeout(function() { dfd.resolve(); }, 20);	
                  return dfd.promise(); }	
   function D() { var dfd = jQuery.Deferred();	
                  setTimeout(function() { dfd.resolve(); }, 200);	
                  return dfd.promise(); }	
	
   jQuery.when(	
     jQuery.when.apply(null, promises1),	
     jQuery.when.apply(null, promises2)	
                                                                   red
   ).done(function() { finishedCallback() });	             y.Defer
}	                                                   jQuer
Flow.js のまとめ
•  Flow.js で書くとコードが短くなる
    •  速度劣化やメモリ増加も無視できる範囲です

•  シンプルに同期/非同期プログラミングができる
    •  非同期処理のグルーピングやネストもできます

•  ブラウザでもNode.jsでも動く
    •  Timer I/O 非依存(setTimeout 未使用)
    •  大抵の場所で使えます


•  主要なメソッドは pass, miss の2つ
    •  易,枯,速,軽,小( flow.min.js は僅か1.2KBです)

•  ググれる名前がある事が重要なので、名前をつけた
    •  Flow.js の原型は、数年前に実装済みだったりします
気に入ったら
使ってみてください。
ただし…
TypeScript Ver1.0では、
C#由来の async/awaitが
実装されるらしいので…
Flow.js は、それまでの
つなぎのようなものかなと…
Link
•  Flow.js
      •  https://github.com/uupaa/flow.js



•  TypeScript loadmap
      •  http://www.slideshare.net/chack411/typescript-rev2-any-browser-
         any-host-any-os-open-source


•  Promises/A idiom
      •  http://wiki.commonjs.org/wiki/Promises/A



•  await/async idiom
      •  http://msdn.microsoft.com/en-us/library/vstudio/hh191443.aspx

Flow.js