Slide 1

Slide 1 text

WHATWG Streams をためした 2016-10-22 Kyoto.js #11 bouzuya 1 / 43

Slide 2

Slide 2 text

ぼく 2 / 43

Slide 3

Slide 3 text

bouzuya 3 / 43

Slide 4

Slide 4 text

github.com/bouzuya hatena.ne.jp/bouzuya twitter.com/bouzuya 4 / 43

Slide 5

Slide 5 text

FaithCreates Inc. 神戸 (Kobe) Web + Android + iOS RALLY 誰でも簡単にスタンプラリー をつくれるサー ビス Co eeScript + AngularJS Ruby + Ruby on Rails 5 / 43

Slide 6

Slide 6 text

WHATWG Streams 6 / 43

Slide 7

Slide 7 text

調査: 認知度は? 7 / 43

Slide 8

Slide 8 text

そもそも Stream って何? 8 / 43

Slide 9

Slide 9 text

名前からすると流れるもの? 9 / 43

Slide 10

Slide 10 text

web プラットフォー ムの相当部分は、 ストリー ミングデー タ上に築かれている: すな わち, デー タは、 全部を記憶域内に読取ることなく, 増分的なやり方で、 作成され, 処 理され, 消費される。 ――WHATWG Streams 日本語訳 10 / 43

Slide 11

Slide 11 text

ぼくの中での Stream デー タ構造の一種 デー タをひとつではなく連続したいくつかのものとして扱える デー タを一度ではなく連続した何度かで処理できる デー タを流せる・ デー タが流れてくる 具体例 (JS) Node.js Stream gulp RxJS Observable / ES.next Observable 11 / 43

Slide 12

Slide 12 text

WHATWG Streams 策定の経緯 12 / 43

Slide 13

Slide 13 text

知らない。 興味ない。 想像: WHATWG Fetch が一括ドンだからでは…… 13 / 43

Slide 14

Slide 14 text

(5 min) 14 / 43

Slide 15

Slide 15 text

3 つの Stream クラス 1. ReadableStream 2. WritableStream 3. TransformStream 15 / 43

Slide 16

Slide 16 text

1. ReadableStream 読み取りのための Stream Source から読んだデー タを内部キュー にためる c l a s s R e a d a b l e S t r e a m { c o n s t r u c t o r ( u n d e r l y i n g S o u r c e : S o u r c e , o p t i o n s ? : R e a d a b l e S t r e a m O p t i o n s ) ; g e t l o c k e d ( ) : b o o l e a n ; c a n c e l ( r e a s o n : a n y ) : P r o m i s e < v o i d > ; g e t R e a d e r ( ) : R e a d e r ; p i p e T h r o u g h ( t r a n s f o r m : T r a n s f o r m S t r e a m , o p t i o n s ? : P i p e T o O p t i o n s ) : R e a d a b l e S t r e a m ; p i p e T o ( w r i t a b l e : W r i t a b l e S t r e a m , o p t i o n s ? : P i p e T o O p t i o n s ) : P r o m i s e < v o i d > ; t e e ( ) : [ R e a d a b l e S t r e a m , R e a d a b l e S t r e a m ] ; } 16 / 43

Slide 17

Slide 17 text

2. WritableStream 書き込みのための Stream Sink に書くデー タを内部キュー にためる c l a s s W r i t a b l e S t r e a m { c o n s t r u c t o r ( u n d e r l y i n g S i n k : S i n k , o p t i o n s ? : W r i t a b l e S t r e a m O p t i o n s ) ; g e t l o c k e d ( ) : b o o l e a n ; a b o r t ( r e a s o n : a n y ) : P r o m i s e < v o i d > ; g e t W r i t e r ( ) : W r i t e r ; } 17 / 43

Slide 18

Slide 18 text

3. TransformStream 変換のための Stream まだ仕様にない c l a s s T r a n s f o r m S t r e a m { c o n s t r u c t o r ( t r a n s f o r m e r : T r a n s f o r m e r ) ; g e t r e a d a b l e ( ) : R e a d a b l e S t r e a m ; g e t w r i t a b l e ( ) : W r i t a b l e S t r e a m ; } 18 / 43

Slide 19

Slide 19 text

Pipe chain (1) 3 種の Stream を pipe する (= pipe chain をつくる) RS -(pipe)-> WS RS -(pipe)-> TS -(pipe)-> ... -(pipe)-> WS RS -(pipe)-> WS + RS -(pipe)-> WS pipe chain を chunk が流れる chunk は RS / WS が読み書きするデー タの単位 19 / 43

Slide 20

Slide 20 text

c o n s t d a t a = [ 1 , 2 , 3 ] ; n e w R e a d a b l e S t r e a m ( { p u l l ( c o n t r o l l e r ) { c o n s t c h u n k = d a t a . s h i f t ( ) ; c o n t r o l l e r . e n q u e u e ( c h u n k ) ; / / 1 , 2 , 3 } } ) . p i p e T h r o u g h ( n e w T r a n s f o r m S t r e a m ( { t r a n s f o r m ( c h u n k , c o n t r o l l e r ) { c o n t r o l l e r . e n q u e u e ( c h u n k * 2 ) ; } } ) ) . p i p e T o ( n e w W r i t a b l e S t r e a m ( { w r i t e ( c h u n k ) { c o n s o l e . l o g ( c h u n k ) ; / / 2 , 4 , 6 } } ) ) ; 20 / 43

Slide 21

Slide 21 text

Source と Sink 1. Underlying Source 2. Underlying Sink 21 / 43

Slide 22

Slide 22 text

1. Source RS に wrap されるデー タの発生源 Push / Pull の違いを吸収する Push Source 例: TCP ソケット Pull Source 例: ファイルハンドル Backpressure を考慮する ( 後述) i n t e r f a c e S o u r c e { t y p e ? : ' b y t e s ' | u n d e f i n e d ; s t a r t ? ( c o n t r o l l e r : R e a d a b l e S t r e a m C o n t r o l l e r ) : P r o m i s e < v o i d > | u n d e f i n e d ; p u l l ? ( c o n t r o l l e r : R e a d a b l e S t r e a m C o n t r o l l e r ) : P r o m i s e < v o i d > | u n d e f i n e d ; c a n c e l ? ( r e a s o n : a n y ) : P r o m i s e < v o i d > | u n d e f i n e d ; } 22 / 43

Slide 23

Slide 23 text

2. Sink WS に wrap されるデー タの行き先 Backpressure を考慮する ( 後述) i n t e r f a c e S i n k { s t a r t ? ( c o n t r o l l e r : W r i t a b l e S t r e a m C o n t r o l l e r ) : P r o m i s e < v o i d > | u n d e f i n e d ; w r i t e ? ( c h u n k : a n y ) : P r o m i s e < v o i d > | u n d e f i n e d ; c l o s e ? ( ) : P r o m i s e < v o i d > | u n d e f i n e d ; a b o r t ? ( r e a s o n : a n y ) : P r o m i s e < v o i d > | u n d e f i n e d ; } 23 / 43

Slide 24

Slide 24 text

なぜ Source と Sink が重要か? 使用時に RS / WS を継承しない constructor に Source / Sink を指定する n e w R e a d a b l e S t r e a m ( s o u r c e , o p t i o n s ) n e w W r i t a b l e S t r e a m ( s i n k , o p t i o n s ) Source / Sink はクラスとして提供されない Source / Sink のインタフェー スを実装する 24 / 43

Slide 25

Slide 25 text

c o n s t d a t a = [ 1 , 2 , 3 ] ; n e w R e a d a b l e S t r e a m ( { / / * * S o u r c e * * p u l l ( c o n t r o l l e r ) { c o n s t c h u n k = d a t a . s h i f t ( ) ; c o n t r o l l e r . e n q u e u e ( c h u n k ) ; / / 1 , 2 , 3 } } ) . p i p e T h r o u g h ( n e w T r a n s f o r m S t r e a m ( { t r a n s f o r m ( c h u n k , c o n t r o l l e r ) { c o n t r o l l e r . e n q u e u e ( c h u n k * 2 ) ; } } ) ) . p i p e T o ( n e w W r i t a b l e S t r e a m ( { / / * * S i n k * * w r i t e ( c h u n k ) { c o n s o l e . l o g ( c h u n k ) ; / / 2 , 4 , 6 } } ) ) ; 25 / 43

Slide 26

Slide 26 text

Pipe chain (2) 1. ( Underlying Source ) 2. ReadableStream 3. [ TransformStream ] 4. WritableStream 5. ( Underlying Sink ) 26 / 43

Slide 27

Slide 27 text

(10 min) 27 / 43

Slide 28

Slide 28 text

WHATWG Streams の特徴 ロック バックプレッシャー 28 / 43

Slide 29

Slide 29 text

ロック 29 / 43

Slide 30

Slide 30 text

ReadableStream の読み取り操作 p i p e T o ( w r i t a b l e : W S ) : P r o m i s e < v o i d > …… WS に pipe p i p e T h r o u g h ( t r a n s f o r m : T S ) : R S …… TS に pipe t e e ( ) : [ R S , R S ] …… RS を分岐 g e t R e a d e r ( ) : R e a d e r …… 上 3 つの内部でも呼ばれる低レベルな操作 30 / 43

Slide 31

Slide 31 text

ロックとは? 制約: ひとつの RS は同時にひとつの Reader しか持てない g e t R e a d e r ( ) : R e a d e r は Reader を確保 (= ロック) する Reader が開放されるまで別の Reader を確保できない 名前に反した破壊的な操作 → すべての読み取り操作は破壊的な操作 g e t l o c k e d ( ) : b o o l e a n でロックの有無を確認できる Reader は RS の内部キュー を dequeue する WritableStream の g e t W r i t e r ( ) も同様 Node.js Stream / RxJS Observable とは大きく違う 比較は https://speakerdeck.com/jxck/stream-between-nodejs-and-whatwg 31 / 43

Slide 32

Slide 32 text

バックプレッシャー 32 / 43

Slide 33

Slide 33 text

バックプレッシャー とは? 流量制御 例: 書き込み中なのにどんどん読み込まれると詰まる Promise で待たせる Source や Sink のメソッドの完了を伝える QueuingStrategy で待たせる queue size で Source の p u l l ( ) を待たせる 33 / 43

Slide 34

Slide 34 text

Promise で待たせる l e t i n d e x = 0 ; n e w R e a d a b l e S t r e a m ( { p u l l ( c o n t r o l l e r ) { i n d e x + = 1 ; c o n t r o l l e r . e n q u e u e ( i n d e x ) ; / / p u l l ( ) は P r o m i s e の解決を待って ( 5 0 + m s 待って) 呼ばれる r e t u r n n e w P r o m i s e ( ( o k ) = > s e t T i m e o u t ( o k , 5 0 ) ) ; } } ) . p i p e T o ( n e w W r i t a b l e S t r e a m ( { w r i t e ( c h u n k ) { c o n s o l e . l o g ( c h u n k ) ; / / w r i t e ( ) は P r o m i s e の解決を待って ( 1 0 0 + m s 待って) 呼ばれる r e t u r n n e w P r o m i s e ( ( o k ) = > s e t T i m e o u t ( o k , 1 0 0 ) ) ; } } ) ) ; 34 / 43

Slide 35

Slide 35 text

QueuingStrategy で待たせる RS / WS の内部キュー をどうためるかの戦略 上限を超えると Source の p u l l ( ) が呼ばれなくなる ※ Source は無視して enqueue し続けることもできる。 c l a s s C o u n t Q u e u i n g S t r a t e g y { c o n s t r u c t o r ( o p t i o n s : { h i g h W a t e r M a r k : n u m b e r ; } ) ; g e t h i g h W a t e r M a r k ( ) : n u m b e r ; / / q u e u e s i z e の限界。 s i z e ( _ c h u n k : a n y ) : n u m b e r ; / / c h u n k s i z e の計算。 このクラスでは常に 1 。 } c l a s s B y t e L e n g t h Q u e u i n g S t r a t e g y { c o n s t r u c t o r ( o p t i o n s : { h i g h W a t e r M a r k : n u m b e r ; } ) ; g e t h i g h W a t e r M a r k ( ) : n u m b e r ; s i z e ( c h u n k : { b y t e L e n g t h : n u m b e r ; } ) : n u m b e r ; } 35 / 43

Slide 36

Slide 36 text

/ / S o u r c e が q u e u e の s i z e を気にしながら e n q u e u e する例 n e w R e a d a b l e S t r e a m ( { s t a r t ( c o n t r o l l e r ) { s e t T i m e o u t ( ( ) = > { l e t i n d e x = 0 ; w h i l e ( c o n t r o l l e r . d e s i r e d S i z e > 0 ) { / / q u e u e に余裕がある限り e n q u e u e c o n t r o l l e r . e n q u e u e ( i n d e x + + ) ; } c o n t r o l l e r . c l o s e ( ) ; } ) ; } } , n e w C o u n t Q u e u i n g S t r a t e g y ( { h i g h W a t e r M a r k : 1 0 } ) ) / / q u e u e は 1 0 個まで . p i p e T o ( n e w W r i t a b l e S t r e a m ( . . . ) ) ; 36 / 43

Slide 37

Slide 37 text

(15 min) 37 / 43

Slide 38

Slide 38 text

ためしてみた 38 / 43

Slide 39

Slide 39 text

で、 何に使う? あくまで Stream はデー タ構造 何に使うかは自由 ぼくは View + ViewModel / Model 間の data ow に使ってみたい Flux + React からの unidirectional data ow なアレ 時間もないので↓ を実装した Cycle.js の adapter / run と TodoMVC ( 一部) 39 / 43

Slide 40

Slide 40 text

結果 bouzuya/cycle-whatwg-streams-example 40 / 43

Slide 41

Slide 41 text

わかったこと まず実装が NPM にない つくった→bouzuya/whatwg-streams-b そして helper (RxJS でいう operator) がない map / lter / reduce (scan) / merge など、 なにもない つくった→ bouzuya/whatwg-streams-fns 意外と快適 なんとかなる Model は RxJS と比べれば simple …… な気がする コアな操作って少ない (= いろいろ足りない) pipeThrough の構造が好き 効率悪そう bind operator がほしくなりにくい TypeScript の型推論だと…… 41 / 43

Slide 42

Slide 42 text

おまけ / / p i p e T h r o u g h の実装 p i p e T h r o u g h ( { r e a d a b l e , w r i t a b l e } , o p t i o n s ) { t h i s . p i p e T o ( w r i t a b l e , o p t i o n s ) ; r e t u r n r e a d a b l e ; } / / b o u z u y a / w h a t w g - s t r e a m s - f n s の例 i m p o r t { f r o m } f r o m ' w h a t w g - s t r e a m s - f n s / f r o m ' ; i m p o r t { m a p } f r o m ' w h a t w g - s t r e a m s - f n s / m a p ' ; i m p o r t { f i l t e r } f r o m ' w h a t w g - s t r e a m s - f n s / f i l t e r ' ; f r o m ( [ 1 , 2 , 3 , 4 , 5 ] ) . p i p e T h r o u g h ( m a p ( ( i : n u m b e r ) = > i * 2 ) ) . p i p e T h r o u g h ( f i l t e r ( ( i : n u m b e r ) = > i = = = 3 ) ) . p i p e T o ( n e w W r i t a b l e S t r e a m ( { w r i t e ( c ) { c o n s o l e . l o g ( c ) ; } } ) ) ; 42 / 43

Slide 43

Slide 43 text

おしまい 43 / 43