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

Flow.js

  • Upload
    uupaa

  • View
    12.219

  • Download
    0

Embed Size (px)

DESCRIPTION

非同期プログラミングを驚きのシンプルさに ver 1.0.1 and more. http://uupaa.hatenablog.com/entry/2013/03/12/185555 http://uupaa.hatenablog.com/entry/2013/03/14/131556

Citation preview

Page 1: Flow.js

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

Version 1.0.1

Page 2: Flow.js

JavaScript には 非同期プログラミングが

つきものです

Page 3: Flow.js

XHR, onload,

setTimeout, postMessage,

addEventListener, DOMContentLoaded

沢山

Page 4: Flow.js

非同期プログラミングを支援するイディオムには、 Deferred/Promises, async/await

などがありますが、

Page 5: Flow.js

今回紹介する Flow.js も、 非同期プログラミングを 上手く扱う方法の1つです

Page 6: Flow.js

Flow.js の 元となったニーズとウォンツ

•  複数の非同期処理の完了を待ちたい •  ダウンロードの完了を待ちつつアニメーションしたい •  同期/非同期処理がちょっと離れた場所で混在しているが、 一方はカウンター、一方はコールバックの連鎖などで待機方法がバラバラ これらを画一的な仕組で待てないか

•  いくつかの非同期処理をグルーピングし、それらの終了を待ちたい事がよくあるが、毎回同じようなコードを書いている気がする

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

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

•  デバッグのしやすさも大事

•  どの非同期処理で止まっているのか原因を素早く特定したい

Page 7: Flow.js

Flow.js の使い方

Page 8: Flow.js

導入 npm または github から flow.js を取得してください。 flow.js はコード単体で利用可能です。 $ npm install flow_js

var Flow = require("flow_js").Flow;または、$ git clone [email protected]:uupaa/flow.js.git$ cd flow.js Browser:

<script src="flow.js"></script>

Page 9: Flow.js

基本的な考え方 •  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 }

Page 10: Flow.js

基本的な使い方 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);

Page 11: Flow.js

同期/非同期処理の成功で pass() 同期/非同期処理の成功でpass()を実行します。 pass()をwaits回数分呼び出すとFlowが待機成功となり、callback(null)を呼び出してFlowも終了します。

var flow = new Flow(1, callback);flow.pass(); // 処理成功 -> 待機成功 -> callback(null);

Page 12: Flow.js

同期/非同期処理の失敗で miss() 同期/非同期処理の失敗でmiss()を実行します。 miss()を呼び出すとFlowが待機失敗となり、 callback(Errorオブジェクト) を呼び出してFlowも終了します。

var flow = new Flow(1, callback);flow.miss(); // 処理失敗 -> 待機失敗 -> callback(Error);

Page 13: Flow.js

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");

Page 14: Flow.js

非同期プログラミングを柔軟に、もっと便利にする使い方

Page 15: Flow.js

失敗可能な回数を設定する 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"), [])

Page 16: Flow.js

待機処理数を継ぎ足す extend(waits)で動的に待機処理数の継ぎ足しが可能です。 waits には追加する処理数を指定します。 最初は1つしかなかった非同期処理が、内部で次々に非同期処理を呼び込むようなケースで利用します。

var flow = new Flow(1, callback);flow.extend(1); flow.pass(); // 処理成功 -> waits=2 なので待機する flow.pass(); // 処理成功 -> 待機成功 -> callback(null, [])

Page 17: Flow.js

Flowを強制終了する exit()でFlowを強制終了することができます。 待機失敗で終了し、callback(Error("exit")) が渡されます

var flow = new Flow(2, callback);flow.exit(); // 強制終了 -> 待機失敗 // -> callback(Error("exit"), [])

Page 18: Flow.js

Junction - 合流する callbackに、別のFlowのインスタンスを指定することで、 Junction(合流)を作ることができます。 flow1とflow2のargsはjunctionのargsに引き継がれます。

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]}

flow2

flow1 junction callback

Page 19: Flow.js

Fork - 分岐する flows として分岐先を { name: 関数/Flow, ... } で指定し、fork(name)で、待機終了後の分岐先を指定できます。 一番上の項目がデフォルトの分岐先になります。

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, []) を呼出

flow fork("fork1")(default)

fork("fork2")

Page 20: Flow.js

例外発生時のエスカレーション 例外のハンドリングと対処はユーザ側で行い、 miss()を呼び出してください。

var flow = new Flow(2, callback);function someMethod() { try { // throw new TypeError("BAD_CASE"); flow.pass(); } catch (err) { flow.miss(); }}

Page 21: Flow.js

待機中の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); // ダンプ後に内部情報をクリアします

Page 22: Flow.js

例: 非同期処理のネスト

非同期に完了する処理をまとめた2つのグループ([A,B], [C,D])と、さらにそれら2つの完了を待ち合わせる合流処理の例です。

Page 23: Flow.js

非同期に完了する2つのグループと、 それらを待ち合わせるJunctionによる

非同期プログラミング

非同期処理グループ2

非同期処理グループ1

処理A 処理B

処理C 処理D

junction

finishedCallback

JavaScript

Page 24: Flow.js

// 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(); } } A(), B(), C(), D();}

JavaScript

Page 25: Flow.js

group2 = new Flow(2, junction)

group1 = new Flow(2, junction)

非同期に完了する2つのFlowと、 それらを待ち合わせるFlow(junction)による

非同期プログラミング

処理A gorup1.pass()

処理B gorup1.pass()

処理C gorup2.pass()

処理D gorup2.pass()

junction = new Flow(2)

finishedCallback

Flow.js

Page 26: Flow.js

// Rewritten by Flow.jsfunction 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();}

Flow.js

Page 27: Flow.js

jQuery.when

jQuery.when

非同期に完了する2つのPromiseと それらを待ち合わせるPromiseによる

非同期プログラミング

処理A dfd.promise()

処理B dfd.promise()

処理C dfd.promise()

処理D dfd.promise()

jQuery.when

finishedCallback

jQuery.Deferred

Page 28: Flow.js

// Rewritten by jQuery.Deferredfunction 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) ).done(function() { finishedCallback() });}

jQuery.Deferred

Page 29: Flow.js

Flow.js のまとめ •  Flow.js で書くとコードが短くなる

•  速度劣化やメモリ増加も無視できる範囲です

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

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

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

•  ググれる名前がある事が重要なので、名前をつけた •  Flow.js の原型は、数年前に実装済みだったりします

Page 30: Flow.js

気に入ったら 使ってみてください。

Page 31: Flow.js

ただし…

Page 32: Flow.js

TypeScript Ver1.0では、 C#由来の async/awaitが 実装されるらしいので…

Page 33: Flow.js

Flow.js は、それまでの つなぎのようなものかなと…

Page 34: 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