Top Banner
React Native Munich Meetup May 2017 REDUX SAGA Nacho Martín @nacmartin
128

Redux saga: managing your side effects. Also: generators in es6

Jan 22, 2018

Download

Internet

Ignacio Martin
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Redux saga: managing your side effects. Also: generators in es6

React Native Munich Meetup May 2017

REDUX SAGA

Nacho Martín @nacmartin

Page 2: Redux saga: managing your side effects. Also: generators in es6

Nacho Martin

I write code at Limenius.

We build tailor-made projects, and provide consultancy and formation.

We are very happy with React and React Native.

Page 3: Redux saga: managing your side effects. Also: generators in es6

Roadmap: • Are Sagas indispensable? • Do they have a strong theoretical background? • ES6 Generators • Sagas

Page 4: Redux saga: managing your side effects. Also: generators in es6

Maybe you don’t need redux-saga

Page 5: Redux saga: managing your side effects. Also: generators in es6
Page 6: Redux saga: managing your side effects. Also: generators in es6
Page 7: Redux saga: managing your side effects. Also: generators in es6

What we needdispatch({type: ‘API_REQUEST’})

Reducer

Page 8: Redux saga: managing your side effects. Also: generators in es6

What we needdispatch({type: ‘API_REQUEST’})

Reducer

state = {…state, requesting : true }

Store

Page 9: Redux saga: managing your side effects. Also: generators in es6

What we needdispatch({type: ‘API_REQUEST’})

Reducer

Store

Middleware

Page 10: Redux saga: managing your side effects. Also: generators in es6

What we needdispatch({type: ‘API_REQUEST’})

Reducer

Store

Middleware

dispatch({type: ‘API_REQUEST_SUCCESS’, data})

Page 11: Redux saga: managing your side effects. Also: generators in es6

What we needdispatch({type: ‘API_REQUEST’})

Reducer

state = {…state, data : action.data }

Store

Middleware

dispatch({type: ‘API_REQUEST_SUCCESS’, data})

Page 12: Redux saga: managing your side effects. Also: generators in es6

What we needdispatch({type: ‘API_REQUEST’})

Reducer

state = {…state, showError: true }

Store

Middleware

dispatch({type: ‘API_REQUEST_ERROR’})

Page 13: Redux saga: managing your side effects. Also: generators in es6

What we needdispatch({type: ‘API_REQUEST’})

Reducer

state = {…state, showError: true }

Store

Middleware

dispatch({type: ‘API_REQUEST_ERROR’})

Side effects

Pure code

Page 14: Redux saga: managing your side effects. Also: generators in es6

Redux-thunk

function makeASandwichWithSecretSauce() { return function (dispatch) { dispatch({type: Constants.SANDWICH_REQUEST}) return fetch('https://www.google.com/search?q=secret+sauce') .then( sauce => dispatch({type: Constants.SANDWICH_SUCCESS, sauce}), error => dispatch({type: Constants.SANDWICH_ERROR, error}) ); };}

dispatch(makeASandwichWithSecretSauce())

Page 15: Redux saga: managing your side effects. Also: generators in es6

Redux-thunk

The lib has only 14 lines of codeIf you don’t need more, stick with it

But what if you do?

Page 16: Redux saga: managing your side effects. Also: generators in es6

If you need more

•Redux-saga •Redux-observable •… •Maybe your own in the future?

Page 17: Redux saga: managing your side effects. Also: generators in es6

Sagas: an abused word

Page 18: Redux saga: managing your side effects. Also: generators in es6

Originally

Page 19: Redux saga: managing your side effects. Also: generators in es6

Originally

Page 20: Redux saga: managing your side effects. Also: generators in es6

Originally

Page 21: Redux saga: managing your side effects. Also: generators in es6

Originally

Page 22: Redux saga: managing your side effects. Also: generators in es6

But nowadays

A long and complicated story with many details

e.g. “my neighbor told me the saga of his divorce again”

Page 23: Redux saga: managing your side effects. Also: generators in es6

Originally in C.S.

Héctor García-Molina

Page 24: Redux saga: managing your side effects. Also: generators in es6

Originally in C.S.

Héctor García-Molina

( )

Page 25: Redux saga: managing your side effects. Also: generators in es6

Originally in C.S.

Héctor García-Molina

( )

Page 26: Redux saga: managing your side effects. Also: generators in es6

Originally in C.S.

Héctor García-Molina

( )Multiple workflows, each providing

compensating actions for every step of the workflow where it can fail

Page 27: Redux saga: managing your side effects. Also: generators in es6

But nowadays

A process manager used to orchestrate complex operations.

Page 28: Redux saga: managing your side effects. Also: generators in es6

But nowadays

A process manager used to orchestrate complex operations.

A library to manage side-effects.

And in redux-saga:

Page 29: Redux saga: managing your side effects. Also: generators in es6

Generators function*

Page 30: Redux saga: managing your side effects. Also: generators in es6

First generator

function* myFirstGenerator() { yield "one" yield "two" yield "three"}

var it = myFirstGenerator()

console.log(it)

console.log(it.next())console.log(it.next())console.log(it.next())console.log(it.next())

Page 31: Redux saga: managing your side effects. Also: generators in es6

First generator

function* myFirstGenerator() { yield "one" yield "two" yield "three"}

var it = myFirstGenerator()

console.log(it)

console.log(it.next())console.log(it.next())console.log(it.next())console.log(it.next())

Page 32: Redux saga: managing your side effects. Also: generators in es6

First generator

function* myFirstGenerator() { yield "one" yield "two" yield "three"}

var it = myFirstGenerator()

console.log(it)

console.log(it.next())console.log(it.next())console.log(it.next())console.log(it.next())

{}

Page 33: Redux saga: managing your side effects. Also: generators in es6

First generator

function* myFirstGenerator() { yield "one" yield "two" yield "three"}

var it = myFirstGenerator()

console.log(it)

console.log(it.next())console.log(it.next())console.log(it.next())console.log(it.next())

Page 34: Redux saga: managing your side effects. Also: generators in es6

First generator

function* myFirstGenerator() { yield "one" yield "two" yield "three"}

var it = myFirstGenerator()

console.log(it)

console.log(it.next())console.log(it.next())console.log(it.next())console.log(it.next())

{ value: 'one', done: false }

Page 35: Redux saga: managing your side effects. Also: generators in es6

First generator

function* myFirstGenerator() { yield "one" yield "two" yield "three"}

var it = myFirstGenerator()

console.log(it)

console.log(it.next())console.log(it.next())console.log(it.next())console.log(it.next())

Page 36: Redux saga: managing your side effects. Also: generators in es6

First generator

function* myFirstGenerator() { yield "one" yield "two" yield "three"}

var it = myFirstGenerator()

console.log(it)

console.log(it.next())console.log(it.next())console.log(it.next())console.log(it.next())

{ value: 'two', done: false }

Page 37: Redux saga: managing your side effects. Also: generators in es6

First generator

function* myFirstGenerator() { yield "one" yield "two" yield "three"}

var it = myFirstGenerator()

console.log(it)

console.log(it.next())console.log(it.next())console.log(it.next())console.log(it.next())

Page 38: Redux saga: managing your side effects. Also: generators in es6

First generator

function* myFirstGenerator() { yield "one" yield "two" yield "three"}

var it = myFirstGenerator()

console.log(it)

console.log(it.next())console.log(it.next())console.log(it.next())console.log(it.next())

{ value: 'three', done: false }

Page 39: Redux saga: managing your side effects. Also: generators in es6

First generator

function* myFirstGenerator() { yield "one" yield "two" yield "three"}

var it = myFirstGenerator()

console.log(it)

console.log(it.next())console.log(it.next())console.log(it.next())console.log(it.next())

Page 40: Redux saga: managing your side effects. Also: generators in es6

First generator

function* myFirstGenerator() { yield "one" yield "two" yield "three"}

var it = myFirstGenerator()

console.log(it)

console.log(it.next())console.log(it.next())console.log(it.next())console.log(it.next())

{ value: undefined, done: true }

Page 41: Redux saga: managing your side effects. Also: generators in es6

function* sum() { var x = 0 x += (yield "1st " + x) x += (yield "2nd " + x) x += (yield "3rd " + x) x += (yield "4th " + x)}

Passing values to generators

var it = sum()

console.log(it.next(‘unused’))console.log(it.next(1))console.log(it.next(20))console.log(it.next(300))

Page 42: Redux saga: managing your side effects. Also: generators in es6

function* sum() { var x = 0 x += (yield "1st " + x) x += (yield "2nd " + x) x += (yield "3rd " + x) x += (yield "4th " + x)}

Passing values to generators

var it = sum()

console.log(it.next(‘unused’))console.log(it.next(1))console.log(it.next(20))console.log(it.next(300))

heaven of data

Page 43: Redux saga: managing your side effects. Also: generators in es6

function* sum() { var x = 0 x += (yield "1st " + x) x += (yield "2nd " + x) x += (yield "3rd " + x) x += (yield "4th " + x)}

Passing values to generators

var it = sum()

console.log(it.next(‘unused’))console.log(it.next(1))console.log(it.next(20))console.log(it.next(300))

Page 44: Redux saga: managing your side effects. Also: generators in es6

function* sum() { var x = 0 x += (yield "1st " + x) x += (yield "2nd " + x) x += (yield "3rd " + x) x += (yield "4th " + x)}

Passing values to generators

var it = sum()

console.log(it.next(‘unused’))console.log(it.next(1))console.log(it.next(20))console.log(it.next(300))

Page 45: Redux saga: managing your side effects. Also: generators in es6

function* sum() { var x = 0 x += (yield "1st " + x) x += (yield "2nd " + x) x += (yield "3rd " + x) x += (yield "4th " + x)}

Passing values to generators

var it = sum()

console.log(it.next(‘unused’))console.log(it.next(1))console.log(it.next(20))console.log(it.next(300))

{ value: '1st 0’, done: false }

Page 46: Redux saga: managing your side effects. Also: generators in es6

function* sum() { var x = 0 x += (yield "1st " + x) x += (yield "2nd " + x) x += (yield "3rd " + x) x += (yield "4th " + x)}

Passing values to generators

var it = sum()

console.log(it.next(‘unused’))console.log(it.next(1))console.log(it.next(20))console.log(it.next(300))

Page 47: Redux saga: managing your side effects. Also: generators in es6

function* sum() { var x = 0 x += (yield "1st " + x) x += (yield "2nd " + x) x += (yield "3rd " + x) x += (yield "4th " + x)}

Passing values to generators

var it = sum()

console.log(it.next(‘unused’))console.log(it.next(1))console.log(it.next(20))console.log(it.next(300))

x = 0 + 1

Page 48: Redux saga: managing your side effects. Also: generators in es6

function* sum() { var x = 0 x += (yield "1st " + x) x += (yield "2nd " + x) x += (yield "3rd " + x) x += (yield "4th " + x)}

Passing values to generators

var it = sum()

console.log(it.next(‘unused’))console.log(it.next(1))console.log(it.next(20))console.log(it.next(300))

Page 49: Redux saga: managing your side effects. Also: generators in es6

function* sum() { var x = 0 x += (yield "1st " + x) x += (yield "2nd " + x) x += (yield "3rd " + x) x += (yield "4th " + x)}

Passing values to generators

var it = sum()

console.log(it.next(‘unused’))console.log(it.next(1))console.log(it.next(20))console.log(it.next(300))

Page 50: Redux saga: managing your side effects. Also: generators in es6

function* sum() { var x = 0 x += (yield "1st " + x) x += (yield "2nd " + x) x += (yield "3rd " + x) x += (yield "4th " + x)}

Passing values to generators

var it = sum()

console.log(it.next(‘unused’))console.log(it.next(1))console.log(it.next(20))console.log(it.next(300))

{ value: '2nd 1’, done: false }

Page 51: Redux saga: managing your side effects. Also: generators in es6

function* sum() { var x = 0 x += (yield "1st " + x) x += (yield "2nd " + x) x += (yield "3rd " + x) x += (yield "4th " + x)}

Passing values to generators

var it = sum()

console.log(it.next(‘unused’))console.log(it.next(1))console.log(it.next(20))console.log(it.next(300))

Page 52: Redux saga: managing your side effects. Also: generators in es6

function* sum() { var x = 0 x += (yield "1st " + x) x += (yield "2nd " + x) x += (yield "3rd " + x) x += (yield "4th " + x)}

Passing values to generators

var it = sum()

console.log(it.next(‘unused’))console.log(it.next(1))console.log(it.next(20))console.log(it.next(300))

x = 1 + 20

Page 53: Redux saga: managing your side effects. Also: generators in es6

function* sum() { var x = 0 x += (yield "1st " + x) x += (yield "2nd " + x) x += (yield "3rd " + x) x += (yield "4th " + x)}

Passing values to generators

var it = sum()

console.log(it.next(‘unused’))console.log(it.next(1))console.log(it.next(20))console.log(it.next(300))

Page 54: Redux saga: managing your side effects. Also: generators in es6

function* sum() { var x = 0 x += (yield "1st " + x) x += (yield "2nd " + x) x += (yield "3rd " + x) x += (yield "4th " + x)}

Passing values to generators

var it = sum()

console.log(it.next(‘unused’))console.log(it.next(1))console.log(it.next(20))console.log(it.next(300))

Page 55: Redux saga: managing your side effects. Also: generators in es6

function* sum() { var x = 0 x += (yield "1st " + x) x += (yield "2nd " + x) x += (yield "3rd " + x) x += (yield "4th " + x)}

Passing values to generators

var it = sum()

console.log(it.next(‘unused’))console.log(it.next(1))console.log(it.next(20))console.log(it.next(300))

Page 56: Redux saga: managing your side effects. Also: generators in es6

function* sum() { var x = 0 x += (yield "1st " + x) x += (yield "2nd " + x) x += (yield "3rd " + x) x += (yield "4th " + x)}

Passing values to generators

var it = sum()

console.log(it.next(‘unused’))console.log(it.next(1))console.log(it.next(20))console.log(it.next(300))

{ value: '3rd 21’, done: false }

Page 57: Redux saga: managing your side effects. Also: generators in es6

function* sum() { var x = 0 x += (yield "1st " + x) x += (yield "2nd " + x) x += (yield "3rd " + x) x += (yield "4th " + x)}

Passing values to generators

var it = sum()

console.log(it.next(‘unused’))console.log(it.next(1))console.log(it.next(20))console.log(it.next(300))

Page 58: Redux saga: managing your side effects. Also: generators in es6

function* sum() { var x = 0 x += (yield "1st " + x) x += (yield "2nd " + x) x += (yield "3rd " + x) x += (yield "4th " + x)}

Passing values to generators

var it = sum()

console.log(it.next(‘unused’))console.log(it.next(1))console.log(it.next(20))console.log(it.next(300))

x = 21 + 300

Page 59: Redux saga: managing your side effects. Also: generators in es6

function* sum() { var x = 0 x += (yield "1st " + x) x += (yield "2nd " + x) x += (yield "3rd " + x) x += (yield "4th " + x)}

Passing values to generators

var it = sum()

console.log(it.next(‘unused’))console.log(it.next(1))console.log(it.next(20))console.log(it.next(300))

Page 60: Redux saga: managing your side effects. Also: generators in es6

function* sum() { var x = 0 x += (yield "1st " + x) x += (yield "2nd " + x) x += (yield "3rd " + x) x += (yield "4th " + x)}

Passing values to generators

var it = sum()

console.log(it.next(‘unused’))console.log(it.next(1))console.log(it.next(20))console.log(it.next(300))

Page 61: Redux saga: managing your side effects. Also: generators in es6

function* sum() { var x = 0 x += (yield "1st " + x) x += (yield "2nd " + x) x += (yield "3rd " + x) x += (yield "4th " + x)}

Passing values to generators

var it = sum()

console.log(it.next(‘unused’))console.log(it.next(1))console.log(it.next(20))console.log(it.next(300))

{ value: '4th 321’, done: false }

Page 62: Redux saga: managing your side effects. Also: generators in es6

function* sum() { var x = 0 while(true) { x += (yield x) }}

Similar, in a loop

var it = sum()

console.log(it.next(‘unused’))console.log(it.next(1))console.log(it.next(20))console.log(it.next(300))

Page 63: Redux saga: managing your side effects. Also: generators in es6

function* sum() { var x = 0 while(true) { x += (yield x) }}

Similar, in a loop

var it = sum()

console.log(it.next(‘unused’))console.log(it.next(1))console.log(it.next(20))console.log(it.next(300))

Page 64: Redux saga: managing your side effects. Also: generators in es6

function* sum() { var x = 0 while(true) { x += (yield x) }}

Similar, in a loop

var it = sum()

console.log(it.next(‘unused’))console.log(it.next(1))console.log(it.next(20))console.log(it.next(300))

{ value: 0, done: false }

Page 65: Redux saga: managing your side effects. Also: generators in es6

function* sum() { var x = 0 while(true) { x += (yield x) }}

Passing values to generators

var it = sum()

console.log(it.next(‘unused’))console.log(it.next(1))console.log(it.next(20))console.log(it.next(300))

{ value: 1, done: false }

Page 66: Redux saga: managing your side effects. Also: generators in es6

function* sum() { var x = 0 while(true) { x += (yield x) }}

Passing values to generators

var it = sum()

console.log(it.next(‘unused’))console.log(it.next(1))console.log(it.next(20))console.log(it.next(300))

{ value:21, done: false }

Page 67: Redux saga: managing your side effects. Also: generators in es6

function* sum() { var x = 0 while(true) { x += (yield x) }}

Passing values to generators

var it = sum()

console.log(it.next(‘unused’))console.log(it.next(1))console.log(it.next(20))console.log(it.next(300))

{ value:321, done: false }

Page 68: Redux saga: managing your side effects. Also: generators in es6

Making a iterable

for (let value of myIterable) { console.log(value); // [1, 2, 3]}

[...myIterable]; // [1, 2, 3]

var myIterable = {};myIterable[Symbol.iterator] = function* () { yield 1; yield 2; yield 3;};

Page 69: Redux saga: managing your side effects. Also: generators in es6

const fetchUser = () => new Promise( resolve => { setTimeout(() => resolve( { username: 'nacho', hash: ‘12345' } ), 4000) })

With async code (+ promises)

function* apiCalls(username, password) { var user = yield fetchUser(username) return user}

var it = apiCalls()

var promise = it.next().valueconsole.log(promise)

promise.then((result) => { console.log(result) var response = it.next(result) console.log(response)})

Page 70: Redux saga: managing your side effects. Also: generators in es6

const fetchUser = () => new Promise( resolve => { setTimeout(() => resolve( { username: 'nacho', hash: ‘12345' } ), 4000) })

With async code (+ promises)

function* apiCalls(username, password) { var user = yield fetchUser(username) return user}

var it = apiCalls()

var promise = it.next().valueconsole.log(promise)

promise.then((result) => { console.log(result) var response = it.next(result) console.log(response)})

Page 71: Redux saga: managing your side effects. Also: generators in es6

const fetchUser = () => new Promise( resolve => { setTimeout(() => resolve( { username: 'nacho', hash: ‘12345' } ), 4000) })

With async code (+ promises)

function* apiCalls(username, password) { var user = yield fetchUser(username) return user}

var it = apiCalls()

var promise = it.next().valueconsole.log(promise)

promise.then((result) => { console.log(result) var response = it.next(result) console.log(response)})

Promise { <pending> }

Page 72: Redux saga: managing your side effects. Also: generators in es6

With async code (+ promises) const fetchUser = () => new Promise( resolve => { setTimeout(() => resolve( { username: 'nacho', hash: ‘12345' } ), 4000) })

function* apiCalls(username, password) { var user = yield fetchUser(username) return user}

var it = apiCalls()

var promise = it.next().valueconsole.log(promise)

promise.then((result) => { console.log(result) var response = it.next(result) console.log(response)})

{ username: 'nacho', hash: '12345' }

Page 73: Redux saga: managing your side effects. Also: generators in es6

With async code (+ promises) const fetchUser = () => new Promise( resolve => { setTimeout(() => resolve( { username: 'nacho', hash: ‘12345' } ), 4000) })

function* apiCalls(username, password) { var user = yield fetchUser(username) return user}

var it = apiCalls()

var promise = it.next().valueconsole.log(promise)

promise.then((result) => { console.log(result) var response = it.next(result) console.log(response)})

{ value: { username: 'nacho', hash: '12345' }, done: true }

Page 74: Redux saga: managing your side effects. Also: generators in es6

With async code (+ promises)

function* apiCalls(username, password) { var user = yield fetchUser(username) var hash = yield someCrypto(password) if (user.hash == hash) { var hash = yield setSession(user.username) var posts = yield fetchPosts(user) } //...}

Page 75: Redux saga: managing your side effects. Also: generators in es6

With async code (+ promises)

function* apiCalls(username, password) { var user = yield fetchUser(username) var hash = yield someCrypto(password) if (user.hash == hash) { var hash = yield setSession(user.username) var posts = yield fetchPosts(user) } //...}

We are doing async as if it was sync Easy to understand, dependent on our project

Page 76: Redux saga: managing your side effects. Also: generators in es6

With async code (+ promises)

function* apiCalls(username, password) { var user = yield fetchUser(username) var hash = yield someCrypto(password) if (user.hash == hash) { var hash = yield setSession(user.username) var posts = yield fetchPosts(user) } //...}

We are doing async as if it was sync Easy to understand, dependent on our project

?

Page 77: Redux saga: managing your side effects. Also: generators in es6

With async code (+ promises)

function* apiCalls(username, password) { var user = yield fetchUser(username) var hash = yield someCrypto(password) if (user.hash == hash) { var hash = yield setSession(user.username) var posts = yield fetchPosts(user) } //...}

We are doing async as if it was sync Easy to understand, dependent on our project

?More complex code

but reusable between projects

Page 78: Redux saga: managing your side effects. Also: generators in es6

With async code (+ promises)

function* apiCalls(username, password) { var user = yield fetchUser(username) var hash = yield someCrypto(password) if (user.hash == hash) { var hash = yield setSession(user.username) var posts = yield fetchPosts(user) } //...}

We are doing async as if it was sync Easy to understand, dependent on our project

More complex code but reusable between projects

redux-saga (or other libs)

Page 79: Redux saga: managing your side effects. Also: generators in es6

Sagas

Page 80: Redux saga: managing your side effects. Also: generators in es6

Setup

import { createStore, applyMiddleware } from 'redux'import createSagaMiddleware from 'redux-saga'

// ...import { helloSaga } from './sagas'

const sagaMiddleware = createSagaMiddleware()const store = createStore( reducer, applyMiddleware(sagaMiddleware))sagaMiddleware.run(helloSaga)

Page 81: Redux saga: managing your side effects. Also: generators in es6

How to play a sound?

Imagine that we have a class SoundManager that can load sounds, do a set up, and has a method SoundManager.play(sound)

How could we use it in React?

Page 82: Redux saga: managing your side effects. Also: generators in es6

Naive solutionclass MoveButton extends Component { render() { return ( <Button onPress={playSound(soundManager, ‘buzz’)} /> ); }}

Page 83: Redux saga: managing your side effects. Also: generators in es6

Naive solutionclass MoveButton extends Component { render() { return ( <Button onPress={playSound(soundManager, ‘buzz’)} /> ); }}

But where does soundManager come from?

Page 84: Redux saga: managing your side effects. Also: generators in es6

Naive solutionclass MoveButton extends Component { render() { return ( <Button onPress={this.props.dispatch(playSound(‘buzz’))} /> ); }}

Page 85: Redux saga: managing your side effects. Also: generators in es6

Naive solutionclass MoveButton extends Component { render() { return ( <Button onPress={this.props.dispatch(playSound(‘buzz’))} /> ); }}

Dispatch an action and we’ll see. But the action creator doesn’t have access

to SoundManager :_(

Page 86: Redux saga: managing your side effects. Also: generators in es6

Naive solutionclass MoveButton extends Component { render() { return ( <Button onPress={playSound(this.props.soundManager, ‘buzz’)} /> ); }}

Passing it from its parent, and the parent of its parent with props?

Page 87: Redux saga: managing your side effects. Also: generators in es6

Naive solutionclass MoveButton extends Component { render() { return ( <Button onPress={playSound(this.props.soundManager, ‘buzz’)} /> ); }}

Passing it from its parent, and the parent of its parent with props?

Hairball ahead

Page 88: Redux saga: managing your side effects. Also: generators in es6

Naive solutionclass MoveButton extends Component { render() { return ( <Button onPress={playSound(this.props.soundManager, ‘buzz’)} /> ); }}

From redux connect: • But soundManager is not serializable. • Breaks time-travel, persist and rehydrate store…

Page 89: Redux saga: managing your side effects. Also: generators in es6

Naive solution

What if we want to play a sound when the opponent moves too and we receive her movements from a websocket?

Page 90: Redux saga: managing your side effects. Also: generators in es6

Naive solution

What if we want to play a sound when the opponent moves too and we receive her movements from a websocket?

class Game extends Component { componentDidMount() { this.props.dispatch(connectSocket(soundManager)) } //...}

Page 91: Redux saga: managing your side effects. Also: generators in es6

Naive solution

What if we want to play a sound when the opponent moves too and we receive her movements from a websocket?

class Game extends Component { componentDidMount() { this.props.dispatch(connectSocket(soundManager)) } //...}

What has to do connectSocket with soundManager? We are forced to do this because we don’t know anything

better :_(

Page 92: Redux saga: managing your side effects. Also: generators in es6

Using sagas

import { take } from 'redux-saga/effects'

export default function* rootSaga() { const action = yield take(Constants.PLAY_SOUND_REQUEST) console.log(action.sound)}

Page 93: Redux saga: managing your side effects. Also: generators in es6

Example: Play sound

import { take, call } from 'redux-saga/effects'

export default function* rootSaga(soundManager) { const action = yield take(Constants.PLAY_SOUND_REQUEST) soundManager.play(action.sound)}

Page 94: Redux saga: managing your side effects. Also: generators in es6

Example: Play sound

import { take, call } from 'redux-saga/effects'

export default function* rootSaga(soundManager) { const action = yield take(Constants.PLAY_SOUND_REQUEST) soundManager.play(action.sound)}

But we will need a mock to test it

Page 95: Redux saga: managing your side effects. Also: generators in es6

Example: Play sound (call)

import { take, call } from 'redux-saga/effects'

export default function* rootSaga(soundManager) { const action = yield take(Constants.PLAY_SOUND_REQUEST) yield call(soundManager.play, action.sound)}

Page 96: Redux saga: managing your side effects. Also: generators in es6

Example: Play sound (call)

import { take, call } from 'redux-saga/effects'

export default function* rootSaga(soundManager) { const action = yield take(Constants.PLAY_SOUND_REQUEST) yield call(soundManager.play, action.sound)}

{ CALL: { fn: soundManager.play, args: ['buzz'] }}

Call returns an object

Page 97: Redux saga: managing your side effects. Also: generators in es6

Declarative effects

Effects are declarative, so they are easier to test

export function* delayAndDo() { yield call(delay, 1000) yield call(doSomething)}

test('delayAndDo Saga test', (assert) => { const it = delayAndDo()

assert.deepEqual( it.next().value, call(delay, 1000), 'delayAndDo Saga must call delay(1000)' )

assert.deepEqual( it.next().value, call(doSomething), 'delayAndDo Saga must call doSomething' )

{ CALL: {fn: delay, args: [1000]}}

{ CALL: {fn: doSomething, args: []}}

Page 98: Redux saga: managing your side effects. Also: generators in es6

Example: Play sound

import { take, call } from 'redux-saga/effects'

export default function* rootSaga(soundManager) { const action = yield take(Constants.PLAY_SOUND_REQUEST) yield call(soundManager.play, action.sound)}

Page 99: Redux saga: managing your side effects. Also: generators in es6

Example: Play sound

import { take, call } from 'redux-saga/effects'

export default function* rootSaga(soundManager) { const action = yield take(Constants.PLAY_SOUND_REQUEST) yield call(soundManager.play, action.sound)}

Will take 1 action, play a sound, and terminate

Page 100: Redux saga: managing your side effects. Also: generators in es6

Example: Play sound

import { take, call } from 'redux-saga/effects'

export default function* rootSaga(soundManager) { while (true) { const action = yield take(Constants.PLAY_SOUND_REQUEST) yield call(soundManager.play, action.sound) }}

Page 101: Redux saga: managing your side effects. Also: generators in es6

Example: Play sound

import { take, call } from 'redux-saga/effects'

export default function* rootSaga(soundManager) { while (true) { const action = yield take(Constants.PLAY_SOUND_REQUEST) yield call(soundManager.play, action.sound) }}

Will take every action

Page 102: Redux saga: managing your side effects. Also: generators in es6

Example: Play sound (takeEvery)

import { takeEvery, call } from 'redux-saga/effects'

function* playSound(soundManager, action) { yield call(soundManager.play, action.sound)}

export default function* rootSaga(soundManager) { const action = yield takeEvery(Constants.PLAY_SOUND_REQUEST, playSound, soundManager)}

Page 103: Redux saga: managing your side effects. Also: generators in es6

Dispatching (put)

import { take, put } from 'redux-saga/effects'

function* watchLogin() { while (true) { const action = yield take('USER_LOGIN_SUCCESS') yield put({type: 'FETCH_NEW_MESSAGES'}) }}

Page 104: Redux saga: managing your side effects. Also: generators in es6

Dispatching (put)

import { take, put } from 'redux-saga/effects'

function* watchLogin() { while (true) { const action = yield take('USER_LOGIN_SUCCESS') yield put({type: 'FETCH_NEW_MESSAGES'}) }}

put dispatches a new action

Page 105: Redux saga: managing your side effects. Also: generators in es6

Ajax example

function makeASandwichWithSecretSauce() { return function (dispatch) { dispatch({type: Constants.SANDWICH_REQUEST}) return fetch('https://www.google.com/search?q=secret+sauce') .then( sauce => dispatch({type: Constants.SANDWICH_SUCCESS, sauce}), error => dispatch({type: Constants.SANDWICH_ERROR, error}) ); };}

dispatch(makeASandwichWithSecretSauce())

Page 106: Redux saga: managing your side effects. Also: generators in es6

Ajax example

function makeASandwichWithSecretSauce() { return function (dispatch) { dispatch({type: Constants.SANDWICH_REQUEST}) return fetch('https://www.google.com/search?q=secret+sauce') .then( sauce => dispatch({type: Constants.SANDWICH_SUCCESS, sauce}), error => dispatch({type: Constants.SANDWICH_ERROR, error}) ); };}

dispatch(makeASandwichWithSecretSauce())

Thunk

Page 107: Redux saga: managing your side effects. Also: generators in es6

Ajax example

import { takeEvery, put, call } from 'redux-saga/effects'

function* sandwichRequest() { yield put({type:Constants.SANDWICH_REQUEST_SHOW_LOADER}) try { const sauce = yield call(() => fetch('https://www.google.com/search?q=secret+sauce')) yield put {type: Constants.SANDWICH_SUCCESS, sauce} } catch (error) { yield put {type: Constants.SANDWICH_ERROR, error} }}

export default function* rootSaga() { const action = yield takeEvery(Constants.SANDWICH_REQUEST, sandwichRequest)}

dispatch({type:Constants.SANDWICH_REQUEST})

Page 108: Redux saga: managing your side effects. Also: generators in es6

Ajax example

import { takeEvery, put, call } from 'redux-saga/effects'

function* sandwichRequest() { yield put({type:Constants.SANDWICH_REQUEST_SHOW_LOADER}) try { const sauce = yield call(() => fetch('https://www.google.com/search?q=secret+sauce')) yield put {type: Constants.SANDWICH_SUCCESS, sauce} } catch (error) { yield put {type: Constants.SANDWICH_ERROR, error} }}

export default function* rootSaga() { const action = yield takeEvery(Constants.SANDWICH_REQUEST, sandwichRequest)}

dispatch({type:Constants.SANDWICH_REQUEST})

Saga

Page 109: Redux saga: managing your side effects. Also: generators in es6

takeLatest

import { takeLatest } from 'redux-saga/effects'

function* watchFetchData() { yield takeLatest('FETCH_REQUESTED', fetchData)}

Page 110: Redux saga: managing your side effects. Also: generators in es6

takeLatest

import { takeLatest } from 'redux-saga/effects'

function* watchFetchData() { yield takeLatest('FETCH_REQUESTED', fetchData)}

Ensure that only the last fetchData will be running

Page 111: Redux saga: managing your side effects. Also: generators in es6

Non-blocking (fork)

function* watchJoinGame(socket) { let gameSaga = null; while (true) { let action = yield take(Constants.JOIN_GAME) gameSaga = yield fork(game, socket, action.gameId) yield fork(watchLeaveGame, gameSaga) }}

function* game(gameChannel, response) { yield fork(listenToSocket, gameChannel, 'game:move', processMovements) yield fork(listenToSocket, gameChannel, 'game:end', processEndGame)

// More things that we want to do inside a game //...}

Page 112: Redux saga: managing your side effects. Also: generators in es6

Non-blocking (fork)

function* watchJoinGame(socket) { let gameSaga = null; while (true) { let action = yield take(Constants.JOIN_GAME) gameSaga = yield fork(game, socket, action.gameId) yield fork(watchLeaveGame, gameSaga) }}

function* game(gameChannel, response) { yield fork(listenToSocket, gameChannel, 'game:move', processMovements) yield fork(listenToSocket, gameChannel, 'game:end', processEndGame)

// More things that we want to do inside a game //...}

Fork will create a new task without blocking in the caller

Page 113: Redux saga: managing your side effects. Also: generators in es6

Cancellation (cancel)function* watchJoinGame(socket) { let gameSaga = null; while (true) { let action = yield take(Constants.JOIN_GAME) gameSaga = yield fork(game, socket, action.gameId) yield fork(watchLeaveGame, gameSaga) }}

Page 114: Redux saga: managing your side effects. Also: generators in es6

Cancellation (cancel)function* watchJoinGame(socket) { let gameSaga = null; while (true) { let action = yield take(Constants.JOIN_GAME) gameSaga = yield fork(game, socket, action.gameId) yield fork(watchLeaveGame, gameSaga) }}

function* watchLeaveGame(gameSaga) { while (true) { yield take(Constants.GAME_LEAVE) if (gameSaga) { yield cancel(gameSaga) } }}

Page 115: Redux saga: managing your side effects. Also: generators in es6

Cancellation (cancel)

function* gameSaga(socket, gameId) { try { const result = yield call(joinChannel, socket, 'game:'+gameId) // Call instead of fork so it blocks and we can cancel it yield call(gameSequence, result.channel, result.response) } catch (error) { console.log(error) } finally { if (yield cancelled()) { socket.channel('game:'+gameId, {}).leave() } }}

function* watchJoinGame(socket) { let gameSaga = null; while (true) { let action = yield take(Constants.JOIN_GAME) gameSaga = yield fork(game, socket, action.gameId) yield fork(watchLeaveGame, gameSaga) }}

function* watchLeaveGame(gameSaga) { while (true) { yield take(Constants.GAME_LEAVE) if (gameSaga) { yield cancel(gameSaga) } }}

Page 116: Redux saga: managing your side effects. Also: generators in es6

Cancellation (cancel)

function* gameSaga(socket, gameId) { try { const result = yield call(joinChannel, socket, 'game:'+gameId) // Call instead of fork so it blocks and we can cancel it yield call(gameSequence, result.channel, result.response) } catch (error) { console.log(error) } finally { if (yield cancelled()) { socket.channel('game:'+gameId, {}).leave() } }}

function* watchJoinGame(socket) { let gameSaga = null; while (true) { let action = yield take(Constants.JOIN_GAME) gameSaga = yield fork(game, socket, action.gameId) yield fork(watchLeaveGame, gameSaga) }}

function* watchLeaveGame(gameSaga) { while (true) { yield take(Constants.GAME_LEAVE) if (gameSaga) { yield cancel(gameSaga) } }}

Page 117: Redux saga: managing your side effects. Also: generators in es6

Non-blocking detached (spawn)

A tasks waits for all its forks to terminate Errors are bubbled up Cancelling a tasks cancels all its forks

Fork

Spawn

New tasks are detached Errors don’t bubble up We have to cancel them manually

Page 118: Redux saga: managing your side effects. Also: generators in es6

Implement takeEvery from take

function* takeEvery(pattern, saga, ...args) { const task = yield fork(function* () { while (true) { const action = yield take(pattern) yield fork(saga, ...args.concat(action)) } }) return task}

Page 119: Redux saga: managing your side effects. Also: generators in es6

Parallel (all)

export default function* rootSaga() { yield all([ playSounds(), watchJoinGame(),

//… ])}

Will block until all terminate

Page 120: Redux saga: managing your side effects. Also: generators in es6

Races (race)

import { race, take } from 'redux-saga/effects'

function* aiCalculate() { while (true) { ... }}

function* watchStartBackgroundTask() { while (true) { yield take('START_AI_COMPUTE_PLAYS') yield race({ task: call(aiCalculate), cancelAi: take('FORCE_MOVE_USER_IS_TIRED_OF_WAITING') }) }}

In race, if one tasks terminates, the others are cancelled

Page 121: Redux saga: managing your side effects. Also: generators in es6

Watch and fork pattern

https://medium.com/@pierremaoui/using-websockets-with-redux-sagas-a2bf26467cab

function* watchRequests() { while (true) { const { payload } = yield take('REQUEST') yield fork(handleRequest, payload) }}

Page 122: Redux saga: managing your side effects. Also: generators in es6

Sequentially, using channels

https://medium.com/@pierremaoui/using-websockets-with-redux-sagas-a2bf26467cab

import { take, actionChannel, call, ... } from ‘redux-saga/effects'

function* watchRequests() { const requestChan = yield actionChannel('REQUEST') while (true) { const { payload } = yield take(requestChan) yield call(handleRequest, payload) }}

Page 123: Redux saga: managing your side effects. Also: generators in es6

Sequentially, using channels

https://medium.com/@pierremaoui/using-websockets-with-redux-sagas-a2bf26467cab

import { take, actionChannel, call, ... } from ‘redux-saga/effects'

function* watchRequests() { const requestChan = yield actionChannel('REQUEST') while (true) { const { payload } = yield take(requestChan) yield call(handleRequest, payload) }}

Channels act as buffers, buffering actions while we block

Page 124: Redux saga: managing your side effects. Also: generators in es6

Connect + listen from socketimport { eventChannel, END } from 'redux-saga'

function websocketInitChannel() { return eventChannel( emitter => { const ws = new WebSocket()

ws.onmessage = e => { return emitter( { type: 'ACTION_TYPE', payload } ) } // unsubscribe function return () => { ws.close() emitter(END) } })}

export default function* websocketSagas() { const channel = yield call(websocketInitChannel) while (true) { const action = yield take(channel) yield put(action) }}

https://medium.com/@pierremaoui/using-websockets-with-redux-sagas-a2bf26467cab

eventChannel turns the ws connection into a channel

Page 125: Redux saga: managing your side effects. Also: generators in es6

Obtaining the state (select)

import { select, takeEvery } from 'redux-saga/effects'

function* watchAndLog() { yield takeEvery('*', function* logger(action) { const state = yield select()

console.log('action', action) console.log('state after', state) })}

select() gives us the state after the reducers have applied the action It is better that sagas don’t to rely on the state, but it is still possible

Page 126: Redux saga: managing your side effects. Also: generators in es6

Sequencing (delay)

import { put } from 'redux-saga/effects'import { delay } from 'redux-saga'

function* scoreSequence(payload) { yield put({ type: Constants.END_GAME }) yield call(delay, 2000) yield put({ type: Constants.SHOW_WINNER, rightAnswer: payload.winner }) yield call(delay, 2000) yield put({ type: Constants.GAME_UPDATE_SCORES, scores: payload.scores }) yield call(delay, 1000) yield put({ type: Constants.GAME_SHOW_PLAY_AGAIN })}

Page 127: Redux saga: managing your side effects. Also: generators in es6

Summary

•Take (takeEvery, takeLast) •Call •Put •Fork & Spawn •Cancel •Channels & EventChannels •All & race •Select •Delay

Page 128: Redux saga: managing your side effects. Also: generators in es6

Thanks! @nacmartin [email protected]

http://limenius.com