86
With Rx by crexista

歌舞伎座Tech Rx会

Embed Size (px)

Citation preview

Page 1: 歌舞伎座Tech Rx会

With Rxby crexista

Page 2: 歌舞伎座Tech Rx会

W H O

• name: Kaoru Shibasaki

• Twitter: @crexista

Page 3: 歌舞伎座Tech Rx会

W H O

• name: Kaoru Shibasaki

• Twitter: @crexista

• Work at:

Page 4: 歌舞伎座Tech Rx会

With Rx

Page 5: 歌舞伎座Tech Rx会

W H A T ’ S

Page 6: 歌舞伎座Tech Rx会

W H A T ’ S

• ブラウザのPushステートを使った非同期遷移

• FlashとJSでのExternalInterfaceを使った非同期通信

• 大量のAPI通信と配信サーバ等とのコネクション管理

Page 7: 歌舞伎座Tech Rx会

非同期処理の山

Page 8: 歌舞伎座Tech Rx会

非同期処理をどうやってRxでさばいてたかを話します

Page 9: 歌舞伎座Tech Rx会

A G E N D A

1. Why.(なぜ使ったのか)

2. Problem.(注意点は何か)

3. How.(解決策)

4. まとめ

Page 10: 歌舞伎座Tech Rx会

W H Y

1. EventListenerではダメなのか?

2. MVVMフレームワークは?

3. その他

Page 11: 歌舞伎座Tech Rx会

Reason 1. EventListenerではダメな理由

Page 12: 歌舞伎座Tech Rx会

_result1 = null;

_apiLoader.addEventListener(onLoad);

_apiLoader.load(url);

function onLoad(event){

_result1 = event.target.data;

}

一般的なEventListener例その1

Page 13: 歌舞伎座Tech Rx会

_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つリクエストする場合

Page 14: 歌舞伎座Tech Rx会

_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つの結果から新たに処理

Page 15: 歌舞伎座Tech Rx会

状態が増える

コールバック関数が値を返さないので

Page 16: 歌舞伎座Tech Rx会

テストがしづらい

Page 17: 歌舞伎座Tech Rx会

var resultSignal =

APILoader.load().map(onLoad).flatMap(nextLoad);

resultSignal.subscribe(onNext, onComplete, onError);

Rxだと・・・

Page 18: 歌舞伎座Tech Rx会

var resultSignal =

APILoader.load().map(onLoad).flatMap(nextLoad);

resultSignal.subscribe(onNext, onComplete, onError);

Rxだと・・・

別クラスのstaticメソッドに出せる

Page 19: 歌舞伎座Tech Rx会

テストがしやすい

Page 20: 歌舞伎座Tech Rx会

var mockResult = Observable.returnValue(obj);

mock(apiLoader.load).returnValue(mockResult);

var testResult = APILoader.load().map().flatMap();

assertThat(testResult, isEqual(/*期待値*/));

Mockへに切り替えてのテスト

Page 21: 歌舞伎座Tech Rx会

Reason 2. MVVMを採用しない理由

Page 22: 歌舞伎座Tech Rx会

M V V Mとは?

from wikipedia

Page 23: 歌舞伎座Tech Rx会

コードで書くと?

Page 24: 歌舞伎座Tech Rx会

コード側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 ++;

}

}

Page 25: 歌舞伎座Tech Rx会

M V V Mパターンの欠点

• 揮発性イベントの扱いが面倒

• ViewとVMの紐付けを動的に変更できない

• Viewロジックが複雑なものに向いてない

Page 26: 歌舞伎座Tech Rx会

カスタマイズコストが高い

Page 27: 歌舞伎座Tech Rx会

Reason 3. その他

Page 28: 歌舞伎座Tech Rx会

Operatorが多い

Page 29: 歌舞伎座Tech Rx会

• 例) マウスオーバーしてから数秒間マウスアウトしなかった場合APIをリクエストする

mouseOver

.merge(mouseOut)

.throttle(LIMIT_MSEC)

.filter(checkEventType).subscribe(apiRequest)

Rxでのイベントカスタマイズの例

Page 30: 歌舞伎座Tech Rx会

P R O B L E M

Page 31: 歌舞伎座Tech Rx会

P R O B L E M

1. 組み込み非同期処理との組み合わせ

2. PULL型からPUSH型のStreamへの変換時

3. SubscribeとError処理

Page 32: 歌舞伎座Tech Rx会

組み込み系との相性

Page 33: 歌舞伎座Tech Rx会

どういうこと?

Page 34: 歌舞伎座Tech Rx会

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;

//なんらかの処理});

Page 35: 歌舞伎座Tech Rx会

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;

//なんらかの処理});

組み込みクラス

Page 36: 歌舞伎座Tech Rx会

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しても消えない

Page 37: 歌舞伎座Tech Rx会

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;

//なんらかの処理});

組み込みクラス

組み込み系の非同期処理はそのまま使えない!

Page 38: 歌舞伎座Tech Rx会

解決策

Page 39: 歌舞伎座Tech Rx会

カスタムシグナルを作る

Page 40: 歌舞伎座Tech Rx会

Observable.create

Page 41: 歌舞伎座Tech Rx会

使い方

Page 42: 歌舞伎座Tech Rx会

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

}

});

Page 43: 歌舞伎座Tech Rx会

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

}

});

接続完了時のコールバック

Page 44: 歌舞伎座Tech Rx会

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

}

});

接続キャンセル時コールバック

Page 45: 歌舞伎座Tech Rx会

var request = apiResult.flatMap(function(result)

{

return socketRequest;

})

.map(function(event):void

{

var socket = event.target;

//なんらかの処理});

Page 46: 歌舞伎座Tech Rx会

P U L L型→ P U S H型

Page 47: 歌舞伎座Tech Rx会

HOTとCOLDの話ではないです(近いけど)

Page 48: 歌舞伎座Tech Rx会

• PULL型のStream

• 非同期でリクエストし、値が取れたら完了

• 例) APIリクエストとか

• PUSH型のStream

• 一度subscribeすると永続的に値が来るStream

• 例) UIとか、メールとか

P U L L型→ P U S H型

Page 49: 歌舞伎座Tech Rx会

A P Iリクエストの結果をもってP U S H通知サーバに接続した例

Page 50: 歌舞伎座Tech Rx会

apiResult.flatMap(function(result)

{

return socketRequest;

})

.flatMap(function(socket)

{

return socket.onData;

}).do(function(data)

{

//描画処理});

1. APIへのリクエスト結果

Page 51: 歌舞伎座Tech Rx会

apiResult.flatMap(function(result)

{

return socketRequest;

})

.flatMap(function(socket)

{

return socket.onData;

}).do(function(data)

{

//描画処理});

2. Socketサーバへのリクエスト

Page 52: 歌舞伎座Tech Rx会

apiResult.flatMap(function(result)

{

return socketRequest;

})

.flatMap(function(socket)

{

return socket.onData;

}).do(function(data)

{

//描画処理});

3. socketサーバからのPUSH通知

Page 53: 歌舞伎座Tech Rx会

apiResult.flatMap(function(result)

{

return socketRequest;

})

.flatMap(function(socket)

{

return socket.onData;

}).do(function(data)

{

//描画処理});

Page 54: 歌舞伎座Tech Rx会

apiResult.flatMap(function(result)

{

return socketRequest;

})

.flatMap(function(socket)

{

return socket.onData;

}).do(function(data)

{

//描画処理});

Page 55: 歌舞伎座Tech Rx会

apiResult.flatMap(function(result)

{

return socketRequest;

})

.flatMap(function(socket)

{

return socket.onData;

}).do(function(data)

{

//描画処理});

Page 56: 歌舞伎座Tech Rx会

apiResult.flatMap(function(result)

{

return socketRequest;

})

.flatMap(function(socket)

{

return socket.onData;

}).do(function(data)

{

//描画処理});

Page 57: 歌舞伎座Tech Rx会

apiResult.flatMap(function(result)

{

return socketRequest;

})

.flatMap(function(socket)

{

return socket.onData;

}).do(function(data)

{

//描画処理});

Page 58: 歌舞伎座Tech Rx会

apiResult.flatMap(function(result)

{

return socketRequest;

})

.flatMap(function(socket)

{

return socket.onData;

}).do(function(data)

{

//描画処理});

Page 59: 歌舞伎座Tech Rx会

apiResult.flatMap(function(result)

{

return socketRequest;

})

.flatMap(function(socket)

{

return socket.onData;

}).do(function(data)

{

//描画処理});

赤字のonDataは上書きされたが

connectionがcloseされるわけではない

Page 60: 歌舞伎座Tech Rx会

対処法

Page 61: 歌舞伎座Tech Rx会

apiResult.flatMap(function(result)

{

return socketRequest;

})

.flatMap(function(socket)

{

return socket.onData.takeUntil(apiResult);

}).do(function(data)

{

//描画処理}); takeUntilを使って

apiResultが完了したらonDataを止める

Page 62: 歌舞伎座Tech Rx会

SubscribeとError処理

Page 63: 歌舞伎座Tech Rx会

1. SubscribeはaddEventListenerとだいたい同じ

2. Subscribeしまくる == eventListener書きまくり

3. eventの結果どこで何が起きるかわからなくなる!

Subscribeについて

Page 64: 歌舞伎座Tech Rx会

悪い例

var request =

APILoader.load(urlA).subscribe(function(result){

APILoader.load(result).subscribe(function(){

//なんかの処理});

})ここの処理はキャンセルできない

Page 65: 歌舞伎座Tech Rx会

回避策

var innerReq;

var request =

APILoader.load(urlA).subscribe(function(result){

innerReq =APILoader.load(result).subscribe(function(){

//なんかの処理});

})

if (innerReq) innerReq.dispose();request.dispose();

Page 66: 歌舞伎座Tech Rx会

回避策

var innerReq;

var request =

APILoader.load(urlA).subscribe(function(result){

innerReq =APILoader.load(result).subscribe(function(){

//なんかの処理});

})

if (innerReq) innerReq.dispose();request.dispose();

できるといえばできるがテストが面倒に・・

Page 67: 歌舞伎座Tech Rx会

良い例

APILoader.load(urlA).flatMap(function(result){

return APILoader.load(result);}).subscribe(function(){

//なんかの処理});

Page 68: 歌舞伎座Tech Rx会

• 変な書き方をするとメモリリークの原因に

• というか、フラグが増えて管理が大変に

• (副作用を起こすものなので当たり前といえば当たり前だが)

Subscribeについて

Page 69: 歌舞伎座Tech Rx会

• 変な書き方をするとメモリリークの原因に

• というか、フラグが増えて管理が大変に

• (副作用を起こすものなので当たり前といえば当たり前だが)

Subscribeについて

Page 70: 歌舞伎座Tech Rx会

• Rxではエラーが起きるとsubscribeが切れる

• subscribeのonErrorを書き忘れるとハマル

• なのできちんとonErrorかcatchErrorしましょう

Errorについて

Page 71: 歌舞伎座Tech Rx会

• Error処理忘れるな & Subscribeをあまり書くな

• なのできちんとonErrorかcatchErrorしましょう

SubscribeとErrorについて

Page 72: 歌舞伎座Tech Rx会

でも人間なので忘れるよね

SubscribeとErrorについて

Page 73: 歌舞伎座Tech Rx会

フレームワーク作りました

Page 74: 歌舞伎座Tech Rx会

フレームワークの方針

• 薄くつくる

• (現状の)レイヤードアーキテクチャに合わせる

• 開発者はsubscribeを「基本」書かない

• Error処理を書き忘れても最終的にcatchするように

Page 75: 歌舞伎座Tech Rx会

アーキテクチャ

Page 76: 歌舞伎座Tech Rx会

映像の再生や切断といったアプリとしての挙動を決める層

アプリとしての挙動を決める際に必要となるビジネスロジック

A P P L I C A T I O NとU Iのつなぎ込みをす

る層

Page 77: 歌舞伎座Tech Rx会

フレームワークの方針

• Presenter層でApplicationとUIのつなぎ込む

• 開発者はつなぎ込みだけフレームワークに従う

• あとは好きに書いてくれ

Page 78: 歌舞伎座Tech Rx会

function start():Array

{

return [

[UI.onPlay.map(VideoModule.play), errorHandler],

[MessageModule.comment.map(UI.addComment)],

];

}

フレームワークでの実装例

UIとApplicationをつなぐSignal

Page 79: 歌舞伎座Tech Rx会

function start():Array

{

return [

[UI.onPlay.map(VideoModule.play), errorHandler],

[MessageModule.comment.map(UI.addComment)],

];

}

フレームワークでの実装例

このSignalの監視中のエラーのハンドラー

Page 80: 歌舞伎座Tech Rx会

function start():Array

{

return [

[UI.onPlay.map(VideoModule.play), errorHandler],

[MessageModule.comment.map(UI.addComment)],

];

}

フレームワークでの実装例

Error処理を変更する必要がない場合はそのまま

Page 81: 歌舞伎座Tech Rx会

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

}

}

フレームワーク内部での処理

エラーのキャッチ漏れ防止機構

Page 82: 歌舞伎座Tech Rx会

まとめ

Page 83: 歌舞伎座Tech Rx会

まとめ

• 基本クラスを覚える

• Observable, Subject, Dispose, subscribeあたり

• Operatorとしては以下を覚えておくと楽

• map, flatMap, filter, zip, merge, do, return, take, takeUntil

Page 84: 歌舞伎座Tech Rx会

まとめ

• Subscribeの扱いはルールを決めるべし

• SubscribeをなくせばSignalの流れを掴みやすくなる

Page 85: 歌舞伎座Tech Rx会

まとめ

流れ

Page 86: 歌舞伎座Tech Rx会

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”