Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Handling Async Actions with Redux Saga

Handling Async Actions with Redux Saga

An alternate method of orchestrating complex or async actions in redux. A method where side effects are described instead of executed w/in your source.

Jake Trent

July 14, 2016
Tweet

More Decks by Jake Trent

Other Decks in Programming

Transcript

  1. What is this talk about? Assumes working knowledge of redux

    Fundamentals of redux‑saga redux‑saga in practice
  2. What is redux? "Predictable state container" Flux variant Single state

    tree Pure function state reducers Popular ecosystem
  3. 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"
  4. What is redux‑saga? JS lib "Alternate side e ff ect

    model for redux apps" Responsible for "orchestrating complex/asynchronous ops"
  5. 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
  6. 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
  7. 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 }
  8. 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
  9. What does a generator object return? { v a l

    u e : 1 , d o n e : f a l s e }
  10. Where can I use generators? Native in Node since v0.12

    Browser via babel‑polyfill Other languages have this feature eg, C# "iterator methods"
  11. What is an e ff ect? JS object Descriptor of

    instructions Instructions to be executed later
  12. 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
  13. 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 " ] } } ] } }
  14. 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 " : [ ] } } }
  15. 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
  16. 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 ) } )
  17. 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 ) } )
  18. 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 ) ) ) } }
  19. 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 ) / / !
  20. 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 ) , . . . ] }
  21. 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 } }
  22. 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 ' ) )
  23. 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 }
  24. 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 } }
  25. 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
  26. 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 ) ) ) } } }
  27. 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 ) ) ) } ) } )
  28. 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 } ] ) ) ) } ) } )
  29. 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
  30. 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