WHATWG Streams をためした - bouzuya / whatwg-streams-kyotojs-11

9890843c055488de704d85ef1ae1b893?s=47 bouzuya
October 22, 2016

WHATWG Streams をためした - bouzuya / whatwg-streams-kyotojs-11

『WHATWG Streams をためした』
2016-10-22 Kyoto.js #11 bouzuya
http://kyotojs.connpass.com/event/39462/

Links:
- HTML version : http://bouzuya.net/slides/2016-10-22/
- bouzuya : http://bouzuya.net/
- github.com/bouzuya : https://github.com/bouzuya
- hatena.ne.jp/bouzuya : http://hatena.ne.jp/bouzuya
- twitter.com/bouzuya : https://twitter.com/bouzuya
- FaithCreates Inc. : http://www.faithcreates.co.jp/
- RALLY : https://rallyapp.jp/
- Streams Standard 日本語訳 : https://triple-underscore.github.io/Streams-ja.html
- https://speakerdeck.com/jxck/stream-between-nodejs-and-whatwg : https://speakerdeck.com/jxck/stream-between-nodejs-and-whatwg
- bouzuya/cycle-whatwg-streams-example : https://github.com/bouzuya/cycle-whatwg-streams-example
- bouzuya/whatwg-streams-b : https://github.com/bouzuya/whatwg-streams-b
- bouzuya/whatwg-streams-fns : https://github.com/bouzuya/whatwg-streams-fns

9890843c055488de704d85ef1ae1b893?s=128

bouzuya

October 22, 2016
Tweet

Transcript

  1. 5.

    FaithCreates Inc. 神戸 (Kobe) Web + Android + iOS RALLY

    誰でも簡単にスタンプラリー をつくれるサー ビス Co eeScript + AngularJS Ruby + Ruby on Rails 5 / 43
  2. 10.

    web プラットフォー ムの相当部分は、 ストリー ミングデー タ上に築かれている: すな わち, デー タは、

    全部を記憶域内に読取ることなく, 増分的なやり方で、 作成され, 処 理され, 消費される。 ――WHATWG Streams 日本語訳 10 / 43
  3. 16.

    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
  4. 17.

    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
  5. 18.

    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
  6. 19.

    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
  7. 20.

    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
  8. 22.

    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
  9. 23.

    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
  10. 24.

    なぜ 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
  11. 25.

    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
  12. 26.

    Pipe chain (2) 1. ( Underlying Source ) 2. ReadableStream

    3. [ TransformStream ] 4. WritableStream 5. ( Underlying Sink ) 26 / 43
  13. 30.

    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
  14. 31.

    ロックとは? 制約: ひとつの 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
  15. 33.

    バックプレッシャー とは? 流量制御 例: 書き込み中なのにどんどん読み込まれると詰まる Promise で待たせる Source や Sink

    のメソッドの完了を伝える QueuingStrategy で待たせる queue size で Source の p u l l ( ) を待たせる 33 / 43
  16. 34.

    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
  17. 35.

    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
  18. 36.

    / / 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
  19. 39.

    で、 何に使う? あくまで Stream はデー タ構造 何に使うかは自由 ぼくは View +

    ViewModel / Model 間の data ow に使ってみたい Flux + React からの unidirectional data ow なアレ 時間もないので↓ を実装した Cycle.js の adapter / run と TodoMVC ( 一部) 39 / 43
  20. 41.

    わかったこと まず実装が 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
  21. 42.

    おまけ / / 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