Slide 1

Slide 1 text

Handling Async Actions with Redux Saga Jake Trent

Slide 2

Slide 2 text

What is this talk about? Assumes working knowledge of redux Fundamentals of redux‑saga redux‑saga in practice

Slide 3

Slide 3 text

What is redux? "Predictable state container" Flux variant Single state tree Pure function state reducers Popular ecosystem

Slide 4

Slide 4 text

What is a saga? 1987 Garcia‑Molina and Salem paper ‑ long‑lived db trans. CQRS ‑ coordinates and routes messages between contexts Distributed systems ‑ manage long‑running business process "Process manager"

Slide 5

Slide 5 text

What is redux‑saga? JS lib "Alternate side e ff ect model for redux apps" Responsible for "orchestrating complex/asynchronous ops"

Slide 6

Slide 6 text

What makes it work? Sagas are generator functions ‑‑ feel like action creators Sagas return e ff ects ‑‑ descriptors of instructions to exec later E ff ects captured and executed in saga redux middleware

Slide 7

Slide 7 text

What is a generator? Way to handle async code JS function that returns a generator object when called Starts "paused" Generator object's n e x t method resumes execution Contains y i e l d keyword Function body execution pauses on y i e l d y i e l d defines return value

Slide 8

Slide 8 text

Generator syntax f u n c t i o n * n a m e ( [ p a r a m [ , p a r a m [ , . . . p a r a m ] ] ] ) { s t a t e m e n t s }

Slide 9

Slide 9 text

Simple generator example f u n c t i o n * i d M a k e r ( ) { l e t i n d e x = 0 w h i l e ( i n d e x < 3 ) y i e l d i n d e x + + } v a r g e n = i d M a k e r ( ) c o n s o l e . l o g ( g e n . n e x t ( ) . v a l u e ) / / 0 c o n s o l e . l o g ( g e n . n e x t ( ) . v a l u e ) / / 1 c o n s o l e . l o g ( g e n . n e x t ( ) . v a l u e ) / / 2 c o n s o l e . l o g ( g e n . n e x t ( ) . v a l u e ) / / u n d e f i n e d

Slide 10

Slide 10 text

What does a generator object return? { v a l u e : 1 , d o n e : f a l s e }

Slide 11

Slide 11 text

Where can I use generators? Native in Node since v0.12 Browser via babel‑polyfill Other languages have this feature eg, C# "iterator methods"

Slide 12

Slide 12 text

What is an e ff ect? JS object Descriptor of instructions Instructions to be executed later

Slide 13

Slide 13 text

What are e ff ect helpers? redux‑saga ‑provided functions Format e ff ects for saga middleware

Slide 14

Slide 14 text

Some e ff ect helpers c a l l ‑ for calling async p u t ‑ for dispatching actions s e l e c t ‑ for querying state

Slide 15

Slide 15 text

E ff ect shape { " @ @ r e d u x ‐ s a g a / I O " : t r u e , " C A L L " : { " c o n t e x t " : n u l l , " a r g s " : [ " / a p i / m a s t e r m i n d / 7 7 6 d e 5 5 2 . . . / g u e s s " , { " d a t a " : { " g u e s s " : [ " r e d " , " r e d " , " y e l l o w " , " y e l l o w " ] } } ] } }

Slide 16

Slide 16 text

Another e ff ect example { " @ @ r e d u x ‐ s a g a / I O " : t r u e , " P U T " : { " c h a n n e l " : n u l l , " a c t i o n " : { " t y p e " : " g u e s s / C R E A T E _ S U C C E S S " , " g u e s s " : [ " r e d " , " r e d " , " y e l l o w " , " y e l l o w " ] , " f e e d b a c k " : { " k e y s " : { " b l a c k s " : 1 , " w h i t e s " : 0 } } , " a l e r t s " : [ ] } } }

Slide 17

Slide 17 text

How do I use redux‑saga in a project? Test and write sagas Attach sagas to redux store Associate an action to each saga Call actions as normal Dispatch actions to reducers as normal

Slide 18

Slide 18 text

Mastermind demo game

Slide 19

Slide 19 text

Test a saga (1/2) i m p o r t { c a l l , p u t } f r o m ' r e d u x ‐ s a g a / e f f e c t s ' i m p o r t t e s t f r o m ' a v a ' i m p o r t * a s a c t i o n s f r o m ' . . / a c t i o n s ' i m p o r t * a s a p i f r o m ' . . / a p i ' i m p o r t * a s s u b j e c t f r o m ' . . / s a g a s ' c o n s t { d e s e r i a l i z e E r r o r , d e s . . } = a p i . c r e a t e t e s t ( ' # c r e a t e h a n d l e s a r e q u e s t s u c c e s s ' , t = > { c o n s t r e s = { s t a t u s : 2 0 0 , d a t a : { d a t a : { s o m e : ' r e s p o n s e ' } } } c o n s t g e n = s u b j e c t . c r e a t e ( ) t . d e e p E q u a l ( g e n . n e x t ( ) . v a l u e , c a l l ( r e q u e s t , f o r m a t U r l ( ) ) ) t . d e e p E q u a l ( g e n . n e x t ( r e s ) . v a l u e , p u t ( a c t i o n s . c r e a t e S u c c e s s ( d e s e r i a l i z e S u c c e s s ( r e s ) ) ) ) t . t r u t h y ( g e n . n e x t ( ) . d o n e ) } )

Slide 20

Slide 20 text

Test a saga (2/2) t e s t ( ' # c r e a t e h a n d l e s a r e q u e s t e r r o r ' , t = > { c o n s t r e s = { s t a t u s : 5 0 0 , d a t a : { e r r o r s : [ { s o m e : ' e r r o r ' } ] } } c o n s t g e n = s u b j e c t . c r e a t e ( ) t . d e e p E q u a l ( g e n . n e x t ( ) . v a l u e , c a l l ( r e q u e s t , f o r m a t U r l ( ) ) ) t . d e e p E q u a l ( g e n . t h r o w ( r e s ) . v a l u e , p u t ( a c t i o n s . c r e a t e E r r o r ( d e s e r i a l i z e E r r o r ( r e s ) ) ) ) t . t r u t h y ( g e n . n e x t ( ) . d o n e ) } )

Slide 21

Slide 21 text

Write a saga i m p o r t { c a l l , p u t } f r o m ' r e d u x ‐ s a g a / e f f e c t s ' i m p o r t * a s a c t i o n s f r o m ' . / a c t i o n s ' i m p o r t * a s a p i f r o m ' . / a p i ' e x p o r t f u n c t i o n * c r e a t e ( ) { c o n s t { d e s e r i a l i z e E r r o r , d e s . . . } = a p i . c r e a t e t r y { c o n s t r e s = y i e l d c a l l ( r e q u e s t , f o r m a t U r l ( ) ) y i e l d p u t ( a c t i o n s . c r e a t e S u c c e s s ( d e s e r i a l i z e S u c c e s s ( r e s ) ) ) } c a t c h ( r e s ) { i f ( r e s i n s t a n c e o f E r r o r ) t h r o w r e s y i e l d p u t ( a c t i o n s . c r e a t e E r r o r ( d e s e r i a l i z e E r r o r ( r e s ) ) ) } }

Slide 22

Slide 22 text

Attach saga to redux store i m p o r t { a p p l y M i d d l e w a r e , c o m b i n e R e d u c e r s , c r e a t e S t o r e } f r o m ' r e d u x ' i m p o r t c r e a t e S a g a M i d d l e w a r e f r o m ' r e d u x ‐ s a g a ' i m p o r t * a s r e d u c e r s f r o m ' . / r e d u c e r s ' i m p o r t s a g a s f r o m ' . / s a g a s ' c o n s t s a g a M i d d l e w a r e = c r e a t e S a g a M i d d l e w a r e ( ) / / ! c o n s t c r e a t e S t o r e W i t h M i d d l e w a r e = a p p l y M i d d l e w a r e ( s a g a M i d d l e w a r e / / ! ) ( c r e a t e S t o r e ) c o n s t r o o t R e d u c e r = c o m b i n e R e d u c e r s ( r e d u c e r s ) c o n s t s t o r e = c r e a t e S t o r e W i t h M i d d l e w a r e ( r o o t R e d u c e r ) s a g a M i d d l e w a r e . r u n ( s a g a s ) / / !

Slide 23

Slide 23 text

Associate an action to each saga i m p o r t { t a k e E v e r y } f r o m ' r e d u x ‐ s a g a ' i m p o r t { f o r k } f r o m ' r e d u x ‐ s a g a / e f f e c t s ' i m p o r t * a s g a m e A c t i o n s f r o m ' . . / . . / g a m e / a c t i o n s ' i m p o r t * a s g a m e S a g a s f r o m ' . . / . . / g a m e / s a g a s ' e x p o r t d e f a u l t f u n c t i o n * r o o t ( ) { y i e l d * [ f o r k ( t a k e E v e r y , g a m e A c t i o n s . T Y P E S . C R E A T E , g a m e S a g a s . c r e a t e ) , . . . ] }

Slide 24

Slide 24 text

Write actions as normal i m p o r t t y p e s f r o m ' r e d u x ‐ t y p e s ' e x p o r t c o n s t T Y P E S = t y p e s ( ' g a m e ' , ' C R E A T E ' , ' C R E A T E _ S U C C E S S ' , ' C R E A T E _ E R R O R ' ) e x p o r t f u n c t i o n c r e a t e ( ) { r e t u r n { t y p e : T Y P E S . C R E A T E } } e x p o r t f u n c t i o n c r e a t e S u c c e s s ( g a m e ) { r e t u r n { t y p e : T Y P E S . C R E A T E _ S U C C E S S , g a m e } } e x p o r t f u n c t i o n c r e a t e E r r o r ( e r r o r s ) { r e t u r n { t y p e : T Y P E S . C R E A T E _ E R R O R , e r r o r s } }

Slide 25

Slide 25 text

Call actions as normal i m p o r t { P r o v i d e r } f r o m ' r e a c t ‐ r e d u x ' i m p o r t R e a c t f r o m ' r e a c t ' i m p o r t { r e n d e r } f r o m ' r e a c t ‐ d o m ' i m p o r t G a m e f r o m ' . / g a m e ' i m p o r t * a s g a m e A c t i o n s f r o m ' . / g a m e / a c t i o n s ' i m p o r t s t o r e f r o m ' . / c o m m o n / s t o r e ' s t o r e . d i s p a t c h ( g a m e A c t i o n s . c r e a t e ( ) ) / / ! r e n d e r ( < P r o v i d e r s t o r e = { s t o r e } > < G a m e / > < / P r o v i d e r > , d o c u m e n t . g e t E l e m e n t B y I d ( ' a p p ' ) )

Slide 26

Slide 26 text

Dispatch actions to reducers as normal i m p o r t { T Y P E S } f r o m ' . . / g a m e / a c t i o n s ' e x p o r t c o n s t i n i t i a l S t a t e = { i d : n u l l } f u n c t i o n c r e a t e S u c c e s s ( s t a t e , a c t i o n ) { r e t u r n { . . . s t a t e , i d : a c t i o n . g a m e . i d } } e x p o r t d e f a u l t f u n c t i o n r e d u c e ( s t a t e = i n i t i a l S t a t e , a c t i o n = { } c o n s t h a n d l e r s = { [ T Y P E S . C R E A T E _ S U C C E S S ] : c r e a t e S u c c e s s } r e t u r n h a n d l e r s [ a c t i o n . t y p e ] ? h a n d l e r s [ a c t i o n . t y p e ] ( s t a t e , a c t i o n ) : s t a t e }

Slide 27

Slide 27 text

Compared to redux‑thunk?

Slide 28

Slide 28 text

redux‑thunk syntax f u n c t i o n a c t i o n N a m e ( [ . . . a r g s ] ) { r e t u r n f u n c t i o n t h e T h u n k ( d i s p a t c h , g e t S t a t e ) { s t a t e m e n t s } }

Slide 29

Slide 29 text

redux‑thunk's idea Similarly, execute action later When has access to store's dispatch function and state

Slide 30

Slide 30 text

Compared to redux‑thunk? thunk saga action returns function e ff ects access to state & dispatch ditto, via e ff ect helpers dispatch multiple times ditto, via e ff ect helpers invoked later, in middleware* later, in middleware test stub promises assert e ff ects *except for in tests

Slide 31

Slide 31 text

Action using redux‑thunk and Promises i m p o r t * a s a p i f r o m ' . / a p i ' e x p o r t f u n c t i o n c r e a t e ( ) { r e t u r n a s y n c d i s p a t c h = > { c o n s t { d e s e r i a l i z e E r r o r , d e s . . . } = a p i . c r e a t e t r y { c o n s t r e s = a w a i t r e q u e s t ( f o r m a t U r l ( ) ) d i s p a t c h ( c r e a t e S u c c e s s ( d e s e r i a l i z e S u c c e s s ( r e s ) ) ) } c a t c h ( r e s ) { i f ( r e s i n s t a n c e o f E r r o r ) t h r o w r e s d i s p a t c h ( c r e a t e E r r o r ( d e s e r i a l i z e E r r o r ( r e s ) ) ) } } }

Slide 32

Slide 32 text

Action test using Promises (1/2) i m p o r t t d f r o m ' t e s t d o u b l e ' / / ! i m p o r t t e s t f r o m ' a v a ' i m p o r t * a s a p i f r o m ' . . / a p i ' i m p o r t * a s s u b j e c t f r o m ' . . / a c t i o n s ' t e s t ( ' # c r e a t e r e q u e s t s g a m e a n d h a n d l e s s u c c e s s ' , t = > { c o n s t d i s p a t c h = t d . f u n c t i o n ( ' d i s p a t c h ' ) / / ! c o n s t g a m e = { s o m e : ' g a m e ' } c o n s t r e s = { d a t a : { d a t a : [ g a m e ] } } / / ! c o n s t r e q P r o m i s e = n e w P r o m i s e ( r e s o l v e = > r e s o l v e ( r e s ) ) / / ! t d . r e p l a c e ( a p i . c r e a t e , ' r e q u e s t ' , ( ) = > r e q P r o m i s e ) / / ! s u b j e c t . c r e a t e ( ) ( d i s p a t c h ) r e t u r n r e q P r o m i s e . t h e n ( _ = > { / / ! t d . v e r i f y ( d i s p a t c h ( s u b j e c t . c r e a t e S u c c e s s ( g a m e ) ) ) } ) } )

Slide 33

Slide 33 text

Action test using Promises (2/2) t e s t ( ' # c r e a t e r e q u e s t s g a m e a n d h a n d l e s e r r o r ' , t = > { c o n s t d i s p a t c h = t d . f u n c t i o n ( ' d i s p a t c h ' ) c o n s t e r r o r s = [ { s o m e : ' e r r o r s ' } ] c o n s t s t a t u s = 4 0 0 c o n s t r e s = { s t a t u s , d a t a : { e r r o r s } } c o n s t r e q P r o m i s e = n e w P r o m i s e ( ( _ , r e j e c t ) = > r e j e c t ( r e s ) ) t d . r e p l a c e ( a p i . c r e a t e , ' r e q u e s t ' , ( ) = > r e q P r o m i s e ) s u b j e c t . c r e a t e ( ) ( d i s p a t c h ) r e t u r n r e q P r o m i s e . c a t c h ( _ = > { t d . v e r i f y ( d i s p a t c h ( s u b j e c t . c r e a t e E r r o r ( [ { . . . e r r o r s [ 0 ] , s t a t u s } ] ) ) ) } ) } )

Slide 34

Slide 34 text

Testing Promises feels worse More stubs required ‑ setup and maintenance pain Less inclined to focus on action module ‑ "the stub game" Implicit testing/coupling to other modules has crept in Setting up fake Promises Async in test flow

Slide 35

Slide 35 text

Review: re‑create a saga for mastermind

Slide 36

Slide 36 text

I like redux‑saga Complex/async action flows More pleasant testing experience Feels like more like a pure function Generators are less familiar than functions Fun to try a new method of organization

Slide 37

Slide 37 text

Thank you! jaketrent.com @jaketrent mastermind‑saga.herokuapp.com