Flow.js

13,881 views

Published on

非同期プログラミングを驚きのシンプルさに ver 1.0.1

and more.
http://uupaa.hatenablog.com/entry/2013/03/12/185555
http://uupaa.hatenablog.com/entry/2013/03/14/131556

Published in: Technology

Flow.js

  1. 1. Flow.jsA very simple way to wait for asynchronous processes. Version 1.0.1
  2. 2. JavaScript には非同期プログラミングが つきものです
  3. 3. XHR, onload, setTimeout, postMessage, addEventListener,DOMContentLoaded 沢山
  4. 4. 非同期プログラミングを支援するイディオムには、 Deferred/Promises, async/await などがありますが、
  5. 5. 今回紹介する Flow.js も、 非同期プログラミングを上手く扱う方法の1つです
  6. 6. Flow.js の 元となったニーズとウォンツ•  複数の非同期処理の完了を待ちたい •  ダウンロードの完了を待ちつつアニメーションしたい •  同期/非同期処理がちょっと離れた場所で混在しているが、 一方はカウンター、一方はコールバックの連鎖などで待機方法がバラバラ これらを画一的な仕組で待てないか •  いくつかの非同期処理をグルーピングし、それらの終了を待ちたい事がよ くあるが、毎回同じようなコードを書いている気がする•  シンプルな実装がほしい •  Deferred/Promiss を JavaScriptに詳しくない方に説明するのは骨が折れる•  運用で困らないようにしたい •  特定の環境に依存したり、頻繁に更新される 重厚なライブラリには依存できない(したくない)•  デバッグのしやすさも大事 •  どの非同期処理で止まっているのか原因を素早く特定したい
  7. 7. Flow.js の使い方
  8. 8. 導入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>
  9. 9. 基本的な考え方•  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 }
  10. 10. 基本的な使い方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);
  11. 11. 同期/非同期処理の成功で pass()同期/非同期処理の成功でpass()を実行します。pass()をwaits回数分呼び出すとFlowが待機成功となり、callback(null)を呼び出してFlowも終了します。var flow = new Flow(1, callback); flow.pass(); // 処理成功 -> 待機成功 -> callback(null);
  12. 12. 同期/非同期処理の失敗で miss()同期/非同期処理の失敗でmiss()を実行します。miss()を呼び出すとFlowが待機失敗となり、callback(Errorオブジェクト) を呼び出してFlowも終了します。var flow = new Flow(1, callback); flow.miss(); // 処理失敗 -> 待機失敗 -> callback(Error);
  13. 13. 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");
  14. 14. 非同期プログラミングを柔軟に、もっと便利にする使い方
  15. 15. 失敗可能な回数を設定する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"), [])
  16. 16. 待機処理数を継ぎ足すextend(waits)で動的に待機処理数の継ぎ足しが可能です。waits には追加する処理数を指定します。最初は1つしかなかった非同期処理が、内部で次々に非同期処理を呼び込むようなケースで利用します。var flow = new Flow(1, callback); flow.extend(1);flow.pass(); // 処理成功 -> waits=2 なので待機するflow.pass(); // 処理成功 -> 待機成功 -> callback(null, [])
  17. 17. Flowを強制終了するexit()でFlowを強制終了することができます。待機失敗で終了し、callback(Error("exit")) が渡されますvar flow = new Flow(2, callback); flow.exit(); // 強制終了 -> 待機失敗 // -> callback(Error("exit"), [])
  18. 18. Junction - 合流するcallbackに、別のFlowのインスタンスを指定することで、Junction(合流)を作ることができます。flow1とflow2のargsはjunctionのargsに引き継がれます。 flow1 junction callback flow2var 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] }
  19. 19. 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, []) を呼出
  20. 20. 例外発生時のエスカレーション例外のハンドリングと対処はユーザ側で行い、miss()を呼び出してください。var flow = new Flow(2, callback); function someMethod() { try { // throw new TypeError("BAD_CASE"); flow.pass(); } catch (err) { flow.miss(); } }
  21. 21. 待機中の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); // ダンプ後に内部情報をクリアします
  22. 22. 非同期に完了する処理をまとめた2つのグループ([A,B], [C,D])と、さらにそれら2つの完了を待ち合わせる合流処理の例です。 例: 非同期処理のネスト
  23. 23. 非同期に完了する2つのグループと、それらを待ち合わせるJunctionによる 非同期プログラミング 非同期処理グループ1処理A 処理B junction finishedCallback 非同期処理グループ2 ript処理C 処理D Ja vaSc
  24. 24. // 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}
  25. 25. 非同期に完了する2つのFlowと、それらを待ち合わせるFlow(junction)による 非同期プログラミング group1 = new Flow(2, junction) 処理A 処理Bgorup1.pass() gorup1.pass() junction = new Flow(2) finishedCallback group2 = new Flow(2, junction) 処理C 処理Dgorup2.pass() gorup2.pass() Flo w.js
  26. 26. // 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
  27. 27. 非同期に完了する2つのPromiseと それらを待ち合わせるPromiseによる 非同期プログラミング jQuery.when 処理A 処理Bdfd.promise() dfd.promise() jQuery.when finishedCallback jQuery.when 処理C 処理Ddfd.promise() dfd.promise() erred jQue ry.Def
  28. 28. // 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
  29. 29. Flow.js のまとめ•  Flow.js で書くとコードが短くなる •  速度劣化やメモリ増加も無視できる範囲です•  シンプルに同期/非同期プログラミングができる •  非同期処理のグルーピングやネストもできます•  ブラウザでもNode.jsでも動く •  Timer I/O 非依存(setTimeout 未使用) •  大抵の場所で使えます•  主要なメソッドは pass, miss の2つ •  易,枯,速,軽,小( flow.min.js は僅か1.2KBです)•  ググれる名前がある事が重要なので、名前をつけた •  Flow.js の原型は、数年前に実装済みだったりします
  30. 30. 気に入ったら使ってみてください。
  31. 31. ただし…
  32. 32. TypeScript Ver1.0では、C#由来の async/awaitが実装されるらしいので…
  33. 33. Flow.js は、それまでのつなぎのようなものかなと…
  34. 34. 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

×