Upload
kaora-shibacaki
View
1.664
Download
0
Embed Size (px)
Citation preview
With Rxby crexista
W H O
• name: Kaoru Shibasaki
• Twitter: @crexista
W H O
• name: Kaoru Shibasaki
• Twitter: @crexista
• Work at:
With Rx
W H A T ’ S
W H A T ’ S
• ブラウザのPushステートを使った非同期遷移
• FlashとJSでのExternalInterfaceを使った非同期通信
• 大量のAPI通信と配信サーバ等とのコネクション管理
非同期処理の山
非同期処理をどうやってRxでさばいてたかを話します
A G E N D A
1. Why.(なぜ使ったのか)
2. Problem.(注意点は何か)
3. How.(解決策)
4. まとめ
W H Y
1. EventListenerではダメなのか?
2. MVVMフレームワークは?
3. その他
Reason 1. EventListenerではダメな理由
_result1 = null;
_apiLoader.addEventListener(onLoad);
_apiLoader.load(url);
function onLoad(event){
_result1 = event.target.data;
}
一般的なEventListener例その1
_result1 = null;
_result2 = null;
_apiLoader.addEventListener(onLoad);
_apiLoader.load(url);
function onLoad(event){
_result1 = event.target.data;
_apiLoader.removeEvent(onLoad);
_apiLoader.addEventListener(onNext);
_apiLoader.load(url);
}
function onNext(event) {
_result2 = event.target.data;
_apiLoader.removeEvent(onNext);
}
一般的なEventListener例その2
APIを順番に2つリクエストする場合
_result1 = null;
_result2 = null;
_result3 = null;
_apiLoader1.addEventListener(onLoad1);
_apiLoader2.addEventListener(onLoad2);
_apiLoader1.load(url);
_apiLoader2.load(url);
function onLoad1(event){
_result1 = event.target.data;
_apiLoader.removeEvent(onLoad);
_apiLoader.addEventListener(onNext);
_apiLoader.load(url);
if (_result2 != null) onLast();
}
function onLoad2(event) {
_result2 = event.target.data;
_apiLoader.removeEvent(onNext);
if (_result1 != null) onLast();
}
function onLast() {
//_result1と_result2を使った処理・・・
}
一般的なEventListener例その3
APIを同時に2つリクエストしてその2つの結果から新たに処理
状態が増える
コールバック関数が値を返さないので
テストがしづらい
var resultSignal =
APILoader.load().map(onLoad).flatMap(nextLoad);
resultSignal.subscribe(onNext, onComplete, onError);
Rxだと・・・
var resultSignal =
APILoader.load().map(onLoad).flatMap(nextLoad);
resultSignal.subscribe(onNext, onComplete, onError);
Rxだと・・・
別クラスのstaticメソッドに出せる
テストがしやすい
var mockResult = Observable.returnValue(obj);
mock(apiLoader.load).returnValue(mockResult);
var testResult = APILoader.load().map().flatMap();
assertThat(testResult, isEqual(/*期待値*/));
Mockへに切り替えてのテスト
Reason 2. MVVMを採用しない理由
M V V Mとは?
from wikipedia
コードで書くと?
コード側V I E W側
M V V Mとは?
<tag value = “{uvm.name}">
<button click = "{uvm.updateAge}">
class UserViewModel
{
[Bindable]
private var _age
function updateAge
{
_age ++;
}
}
M V V Mパターンの欠点
• 揮発性イベントの扱いが面倒
• ViewとVMの紐付けを動的に変更できない
• Viewロジックが複雑なものに向いてない
カスタマイズコストが高い
Reason 3. その他
Operatorが多い
• 例) マウスオーバーしてから数秒間マウスアウトしなかった場合APIをリクエストする
mouseOver
.merge(mouseOut)
.throttle(LIMIT_MSEC)
.filter(checkEventType).subscribe(apiRequest)
Rxでのイベントカスタマイズの例
P R O B L E M
P R O B L E M
1. 組み込み非同期処理との組み合わせ
2. PULL型からPUSH型のStreamへの変換時
3. SubscribeとError処理
組み込み系との相性
どういうこと?
var request = apiResult.flatMap(function(result)
{
var socket = new Socket();
var obs = Observable.fromEvent(“connect”, socket);
socket.connect();
return obs
})
.map(function(event):void
{
var socket = event.target;
//なんらかの処理});
var request = apiResult.flatMap(function(result)
{
var socket = new Socket();
var obs = Observable.fromEvent(“connect”, socket);
socket.connect();
return obs
})
.map(function(event):void
{
var socket = event.target;
//なんらかの処理});
組み込みクラス
var request = apiResult.flatMap(function(result)
{
var socket = new Socket();
var obs = Observable.fromEvent(“connect”, socket);
socket.connect();
return obs
})
.map(function(event):void
{
var socket = event.target;
//なんらかの処理});
組み込みクラス
ここでconnectしたsocketはrequestをdisposeしても消えない
var request = apiResult.flatMap(function(result)
{
var socket = new Socket();
var obs = Observable.fromEvent(“connect”, socket);
socket.connect();
return obs
})
.map(function(event):void
{
var socket = event.target;
//なんらかの処理});
組み込みクラス
組み込み系の非同期処理はそのまま使えない!
解決策
カスタムシグナルを作る
Observable.create
使い方
socketRequest = Observable.create(function(observer)
{
var socket = new Socket();
var func = function(event)
{
observer.onNext(event.target);
observer.onComplete();
};
socket.addEventListener(“connect”,func);
socket.connect();
return function()
{
socket.removeEventListener(func);
if (!socket.connected) socket.close();
}
});
socketRequest = Observable.create(function(observer)
{
var socket = new Socket();
var func = function(event)
{
observer.onNext(event.target);
observer.onComplete();
};
socket.addEventListener(“connect”,func);
socket.connect();
return function()
{
socket.removeEventListener(func);
if (!socket.connected) socket.close();
}
});
接続完了時のコールバック
socketRequest = Observable.create(function(observer)
{
var socket = new Socket();
var func = function(event)
{
observer.onNext(event.target);
observer.onComplete();
};
socket.addEventListener(“connect”,func);
socket.connect();
return function()
{
socket.removeEventListener(func);
if (!socket.connected) socket.close();
}
});
接続キャンセル時コールバック
var request = apiResult.flatMap(function(result)
{
return socketRequest;
})
.map(function(event):void
{
var socket = event.target;
//なんらかの処理});
P U L L型→ P U S H型
HOTとCOLDの話ではないです(近いけど)
• PULL型のStream
• 非同期でリクエストし、値が取れたら完了
• 例) APIリクエストとか
• PUSH型のStream
• 一度subscribeすると永続的に値が来るStream
• 例) UIとか、メールとか
P U L L型→ P U S H型
A P Iリクエストの結果をもってP U S H通知サーバに接続した例
apiResult.flatMap(function(result)
{
return socketRequest;
})
.flatMap(function(socket)
{
return socket.onData;
}).do(function(data)
{
//描画処理});
1. APIへのリクエスト結果
apiResult.flatMap(function(result)
{
return socketRequest;
})
.flatMap(function(socket)
{
return socket.onData;
}).do(function(data)
{
//描画処理});
2. Socketサーバへのリクエスト
apiResult.flatMap(function(result)
{
return socketRequest;
})
.flatMap(function(socket)
{
return socket.onData;
}).do(function(data)
{
//描画処理});
3. socketサーバからのPUSH通知
apiResult.flatMap(function(result)
{
return socketRequest;
})
.flatMap(function(socket)
{
return socket.onData;
}).do(function(data)
{
//描画処理});
apiResult.flatMap(function(result)
{
return socketRequest;
})
.flatMap(function(socket)
{
return socket.onData;
}).do(function(data)
{
//描画処理});
apiResult.flatMap(function(result)
{
return socketRequest;
})
.flatMap(function(socket)
{
return socket.onData;
}).do(function(data)
{
//描画処理});
apiResult.flatMap(function(result)
{
return socketRequest;
})
.flatMap(function(socket)
{
return socket.onData;
}).do(function(data)
{
//描画処理});
apiResult.flatMap(function(result)
{
return socketRequest;
})
.flatMap(function(socket)
{
return socket.onData;
}).do(function(data)
{
//描画処理});
apiResult.flatMap(function(result)
{
return socketRequest;
})
.flatMap(function(socket)
{
return socket.onData;
}).do(function(data)
{
//描画処理});
apiResult.flatMap(function(result)
{
return socketRequest;
})
.flatMap(function(socket)
{
return socket.onData;
}).do(function(data)
{
//描画処理});
赤字のonDataは上書きされたが
connectionがcloseされるわけではない
対処法
apiResult.flatMap(function(result)
{
return socketRequest;
})
.flatMap(function(socket)
{
return socket.onData.takeUntil(apiResult);
}).do(function(data)
{
//描画処理}); takeUntilを使って
apiResultが完了したらonDataを止める
SubscribeとError処理
1. SubscribeはaddEventListenerとだいたい同じ
2. Subscribeしまくる == eventListener書きまくり
3. eventの結果どこで何が起きるかわからなくなる!
Subscribeについて
悪い例
var request =
APILoader.load(urlA).subscribe(function(result){
APILoader.load(result).subscribe(function(){
//なんかの処理});
})ここの処理はキャンセルできない
回避策
var innerReq;
var request =
APILoader.load(urlA).subscribe(function(result){
innerReq =APILoader.load(result).subscribe(function(){
//なんかの処理});
})
if (innerReq) innerReq.dispose();request.dispose();
回避策
var innerReq;
var request =
APILoader.load(urlA).subscribe(function(result){
innerReq =APILoader.load(result).subscribe(function(){
//なんかの処理});
})
if (innerReq) innerReq.dispose();request.dispose();
できるといえばできるがテストが面倒に・・
良い例
APILoader.load(urlA).flatMap(function(result){
return APILoader.load(result);}).subscribe(function(){
//なんかの処理});
• 変な書き方をするとメモリリークの原因に
• というか、フラグが増えて管理が大変に
• (副作用を起こすものなので当たり前といえば当たり前だが)
Subscribeについて
• 変な書き方をするとメモリリークの原因に
• というか、フラグが増えて管理が大変に
• (副作用を起こすものなので当たり前といえば当たり前だが)
Subscribeについて
• Rxではエラーが起きるとsubscribeが切れる
• subscribeのonErrorを書き忘れるとハマル
• なのできちんとonErrorかcatchErrorしましょう
Errorについて
• Error処理忘れるな & Subscribeをあまり書くな
• なのできちんとonErrorかcatchErrorしましょう
SubscribeとErrorについて
でも人間なので忘れるよね
SubscribeとErrorについて
フレームワーク作りました
フレームワークの方針
• 薄くつくる
• (現状の)レイヤードアーキテクチャに合わせる
• 開発者はsubscribeを「基本」書かない
• Error処理を書き忘れても最終的にcatchするように
アーキテクチャ
映像の再生や切断といったアプリとしての挙動を決める層
アプリとしての挙動を決める際に必要となるビジネスロジック
A P P L I C A T I O NとU Iのつなぎ込みをす
る層
フレームワークの方針
• Presenter層でApplicationとUIのつなぎ込む
• 開発者はつなぎ込みだけフレームワークに従う
• あとは好きに書いてくれ
function start():Array
{
return [
[UI.onPlay.map(VideoModule.play), errorHandler],
[MessageModule.comment.map(UI.addComment)],
];
}
フレームワークでの実装例
UIとApplicationをつなぐSignal
function start():Array
{
return [
[UI.onPlay.map(VideoModule.play), errorHandler],
[MessageModule.comment.map(UI.addComment)],
];
}
フレームワークでの実装例
このSignalの監視中のエラーのハンドラー
function start():Array
{
return [
[UI.onPlay.map(VideoModule.play), errorHandler],
[MessageModule.comment.map(UI.addComment)],
];
}
フレームワークでの実装例
Error処理を変更する必要がない場合はそのまま
function run():Array
{
var arr = presenter.start();
for (var signals in arr)
{
var signal = signals[0];
var errorHandle = (signals.length==2)?
signals[1]:defaultHandle
signal.catchError(errorHandle)
.subscribe(onNext, onComplete, onError);
}
}
フレームワーク内部での処理
エラーのキャッチ漏れ防止機構
まとめ
まとめ
• 基本クラスを覚える
• Observable, Subject, Dispose, subscribeあたり
• Operatorとしては以下を覚えておくと楽
• map, flatMap, filter, zip, merge, do, return, take, takeUntil
まとめ
• Subscribeの扱いはルールを決めるべし
• SubscribeをなくせばSignalの流れを掴みやすくなる
まとめ
流れ
–
H T T P S : / / G I S T . G I T H U B . C O M / S T A L T Z / 8 6 8 E 7 E 9 B C 2 A 7 B 8 C
1 F 7 5 4
“Everything is a stream”