SlideShare a Scribd company logo
1 of 128
Download to read offline
September 2017
Redux Saga,

the Viking way to manage side effects
Nacho Martín
Thanks to our sponsors!
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.
Roadmap:
• Are Sagas indispensable?
• Is there a theoretical background?
• ES6 Generators
• Sagas
Maybe you don’t need redux-saga
What we need
dispatch({type:‘API_REQUEST’})
Reducer
What we need
dispatch({type:‘API_REQUEST’})
Reducer
state = {…state, requesting : true }
Store
What we need
dispatch({type:‘API_REQUEST’})
Reducer
Store
Middleware
⚙
What we need
dispatch({type:‘API_REQUEST’})
Reducer
Store
Middleware
dispatch({type:‘API_REQUEST_SUCCESS’, data})
⚙
What we need
dispatch({type:‘API_REQUEST’})
Reducer
state = {…state, data : action.data }
Store
Middleware
dispatch({type:‘API_REQUEST_SUCCESS’, data})
⚙
What we need
dispatch({type:‘API_REQUEST’})
Reducer
state = {…state, showError: true }
Store
Middleware
dispatch({type:‘API_REQUEST_ERROR’})
⚙
What we need
dispatch({type:‘API_REQUEST’})
Reducer
state = {…state, showError: true }
Store
Middleware
dispatch({type:‘API_REQUEST_ERROR’})
⚙
Side effects
Pure code
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())
Redux-thunk
The lib has only 14 lines of code
If you don’t need more, stick with it
But what if you do?
If you need more
•Redux-saga
•Redux-observable
•…
•Maybe your own in the future?
Sagas: an abused word
Originally
Originally
Originally
Originally
But nowadays
A long and complicated story with many details
e.g. “my neighbor told me the saga of his divorce again”
Originally in C.S.
Héctor García-Molina
Originally in C.S.
Héctor García-Molina
( )
Originally in C.S.
Héctor García-Molina
( )
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
But nowadays
A process manager used to
orchestrate complex operations.
But nowadays
A process manager used to
orchestrate complex operations.
A library to manage side-effects.
And in redux-saga:
Generators
function*
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())
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())
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())
{}
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())
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 }
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())
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 }
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())
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 }
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())
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 }
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))
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
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))
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))
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 }
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))
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
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))
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))
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 }
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))
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
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))
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))
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))
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 }
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))
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
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))
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))
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 }
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))
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))
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 }
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: 1, done: false }
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:21, done: false }
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:321, done: false }
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().value
console.log(promise)
promise.then((result) => {
console.log(result)
var response = it.next(result)
console.log(response)
})
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().value
console.log(promise)
promise.then((result) => {
console.log(result)
var response = it.next(result)
console.log(response)
})
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().value
console.log(promise)
promise.then((result) => {
console.log(result)
var response = it.next(result)
console.log(response)
})
Promise { <pending> }
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().value
console.log(promise)
promise.then((result) => {
console.log(result)
var response = it.next(result)
console.log(response)
})
{ username: 'nacho', hash: '12345' }
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().value
console.log(promise)
promise.then((result) => {
console.log(result)
var response = it.next(result)
console.log(response)
})
{ value: { username: 'nacho', hash: '12345' }, done: true }
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)
}
//...
}
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
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
?
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)
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
Sagas
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)
Simple problem: 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?
Naive solution
class MoveButton extends Component {
render() {
return (
<Button
onPress={() => soundManager.play( ‘buzz’)}
/>
)
}
}
Naive solution
class MoveButton extends Component {
render() {
return (
<Button
onPress={() => soundManager.play( ‘buzz’)}
/>
)
}
}
But where does soundManager come from?
Naive solution
class MoveButton extends Component {
render() {
return (
<Button
onPress={() => dispatch(playSound( ‘buzz’))}
/>
)
}
}
Naive solution
class MoveButton extends Component {
render() {
return (
<Button
onPress={() => dispatch(playSound( ‘buzz’))}
/>
)
}
}
Dispatch an action and we’ll see.
But the action creator doesn’t have access
to SoundManager :_(
Naive solution
class MoveButton extends Component {
render() {
return (
<Button
onPress={() => this.props.soundManager(‘buzz’)}
/>
)
}
}
Passing it from its parent, and the parent of its parent with props?
Naive solution
class MoveButton extends Component {
render() {
return (
<Button
onPress={() => this.props.soundManager(‘buzz’)}
/>
)
}
}
Passing it from its parent, and the parent of its parent with props?
Hairball ahead
Naive solution
class 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…
Naive solution
class 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…
Then what, maybe use context?
Naive solution
What if we want to play a sound when the opponent moves too
and we receive her movements from a websocket?
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))
}
//...
}
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 :_(
Using sagas
import { take } from 'redux-saga/effects'
export default function* rootSaga() {
const action = yield take(Constants.PLAY_SOUND_REQUEST)
console.log(action.sound)
}
Using sagas
import { take } from 'redux-saga/effects'
export default function* rootSaga(soundManager) {
const action = yield take(Constants.PLAY_SOUND_REQUEST)
soundManager.play(action.sound)
}
Using sagas
import { take } from 'redux-saga/effects'
export default function* rootSaga(soundManager) {
const action = yield take(Constants.PLAY_SOUND_REQUEST)
soundManager.play(action.sound)
}
Ok, but we need a mock to test it
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)
}
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
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: []}}
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)
}
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
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)
}
}
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
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)
}
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'})
}
}
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
Ajax example
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())
Ajax example
import { takeEvery, put, call } from 'redux-saga/effects'
export default function* rootSaga() {
const action = yield takeEvery(Constants.SANDWICH_REQUEST, sandwichRequest)
}
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}
}
}
dispatch({type:Constants.SANDWICH_REQUEST})
Saga
takeLatest
import { takeLatest } from 'redux-saga/effects'
function* watchFetchData() {
yield takeLatest('FETCH_REQUESTED', fetchData)
}
takeLatest
import { takeLatest } from 'redux-saga/effects'
function* watchFetchData() {
yield takeLatest('FETCH_REQUESTED', fetchData)
}
Ensure that only the last fetchData will be running
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(socket, response) {
yield fork(listenToSocket, socket, 'game:move', processMovements)
yield fork(listenToSocket, socket, 'game:end', processEndGame)
// More things that we want to do inside a game
//...
}
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(socket, response) {
yield fork(listenToSocket, socket, 'game:move', processMovements)
yield fork(listenToSocket, socket, 'game:end', processEndGame)
// More things that we want to do inside a game
//...
}
Fork will create a new task without blocking in the caller
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)
}
}
Cancellation (cancel)
function* game(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)
}
}
Cancellation (cancel)
function* game(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) }
}
}
Cancellation (cancel)
function* game(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) }
}
}
Cancellation (cancel)
function* game(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) }
}
}
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
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
}
export default function* rootSaga() {
yield all([
playSounds(),
watchJoinGame(),
//…
])
}
Parallel (all)
Will block until all terminate
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
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)
}
}
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)
}
}
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 in call
Connect + listen from socket
function websocketInitChannel() {
return eventChannel( emitter => {
const ws = new WebSocket()
ws.onmessage = msg => {
return emitter( { type:‘WS_EVENT’, msg } )
}
// unsubscribe function
return () => {
ws.close()
emitter(END)
}
})
}
https://medium.com/@pierremaoui/using-websockets-with-redux-sagas-a2bf26467cab
eventChannel turns the ws connection into a channel
export default function* websocketSagas() {
const channel = yield call(websocketInitChannel)
while (true) {
const action = yield take(channel)
yield put(action)
}
}
import { eventChannel, END } from 'redux-saga'
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
Summary
•Take (takeEvery, takeLast)
•Call
•Put
•Fork & Spawn
•Cancel
•Channels & EventChannels
•All & race
•Select
Takeaways
• Don’t use sagas until you need to orchestrate
complex side effects, but prepare for that moment.
• Forget about the sagas paper to use redux-saga.
• Generators = friends. Generators + promises = love.
• Think how to model your problem with the effects
of the lib, or combinations of them.
Thanks! @nacmartin
nacho@limenius.com

More Related Content

What's hot

Introduction to ReactJS
Introduction to ReactJSIntroduction to ReactJS
Introduction to ReactJSKnoldus Inc.
 
An introduction to React.js
An introduction to React.jsAn introduction to React.js
An introduction to React.jsEmanuele DelBono
 
ReactJS presentation
ReactJS presentationReactJS presentation
ReactJS presentationThanh Tuong
 
React event
React eventReact event
React eventDucat
 
Introduction to React
Introduction to ReactIntroduction to React
Introduction to ReactRob Quick
 
React JS: A Secret Preview
React JS: A Secret PreviewReact JS: A Secret Preview
React JS: A Secret Previewvaluebound
 
Jetpack Compose - Android’s modern toolkit for building native UI
Jetpack Compose - Android’s modern toolkit for building native UIJetpack Compose - Android’s modern toolkit for building native UI
Jetpack Compose - Android’s modern toolkit for building native UIGilang Ramadhan
 
React js - The Core Concepts
React js - The Core ConceptsReact js - The Core Concepts
React js - The Core ConceptsDivyang Bhambhani
 
React js programming concept
React js programming conceptReact js programming concept
React js programming conceptTariqul islam
 
Intro to Asynchronous Javascript
Intro to Asynchronous JavascriptIntro to Asynchronous Javascript
Intro to Asynchronous JavascriptGarrett Welson
 
Clean code and Code Smells
Clean code and Code SmellsClean code and Code Smells
Clean code and Code SmellsMario Sangiorgio
 

What's hot (20)

React hooks
React hooksReact hooks
React hooks
 
React js
React jsReact js
React js
 
Its time to React.js
Its time to React.jsIts time to React.js
Its time to React.js
 
React and redux
React and reduxReact and redux
React and redux
 
Introduction to ReactJS
Introduction to ReactJSIntroduction to ReactJS
Introduction to ReactJS
 
Redux toolkit
Redux toolkitRedux toolkit
Redux toolkit
 
An introduction to React.js
An introduction to React.jsAn introduction to React.js
An introduction to React.js
 
React hooks
React hooksReact hooks
React hooks
 
Intro to React
Intro to ReactIntro to React
Intro to React
 
ReactJS presentation
ReactJS presentationReactJS presentation
ReactJS presentation
 
React Hooks
React HooksReact Hooks
React Hooks
 
React event
React eventReact event
React event
 
Introduction to React
Introduction to ReactIntroduction to React
Introduction to React
 
React JS: A Secret Preview
React JS: A Secret PreviewReact JS: A Secret Preview
React JS: A Secret Preview
 
An Introduction to Redux
An Introduction to ReduxAn Introduction to Redux
An Introduction to Redux
 
Jetpack Compose - Android’s modern toolkit for building native UI
Jetpack Compose - Android’s modern toolkit for building native UIJetpack Compose - Android’s modern toolkit for building native UI
Jetpack Compose - Android’s modern toolkit for building native UI
 
React js - The Core Concepts
React js - The Core ConceptsReact js - The Core Concepts
React js - The Core Concepts
 
React js programming concept
React js programming conceptReact js programming concept
React js programming concept
 
Intro to Asynchronous Javascript
Intro to Asynchronous JavascriptIntro to Asynchronous Javascript
Intro to Asynchronous Javascript
 
Clean code and Code Smells
Clean code and Code SmellsClean code and Code Smells
Clean code and Code Smells
 

Similar to Redux Sagas - React Alicante

Redux saga: managing your side effects. Also: generators in es6
Redux saga: managing your side effects. Also: generators in es6Redux saga: managing your side effects. Also: generators in es6
Redux saga: managing your side effects. Also: generators in es6Ignacio Martín
 
Think Async: Asynchronous Patterns in NodeJS
Think Async: Asynchronous Patterns in NodeJSThink Async: Asynchronous Patterns in NodeJS
Think Async: Asynchronous Patterns in NodeJSAdam L Barrett
 
EcmaScript unchained
EcmaScript unchainedEcmaScript unchained
EcmaScript unchainedEduard Tomàs
 
Andrii Orlov "Generators Flexibility in Modern Code"
Andrii Orlov "Generators Flexibility in Modern Code"Andrii Orlov "Generators Flexibility in Modern Code"
Andrii Orlov "Generators Flexibility in Modern Code"LogeekNightUkraine
 
Monadologie
MonadologieMonadologie
Monadologieleague
 
Swift 함수 커링 사용하기
Swift 함수 커링 사용하기Swift 함수 커링 사용하기
Swift 함수 커링 사용하기진성 오
 
JavaScript ∩ WebAssembly
JavaScript ∩ WebAssemblyJavaScript ∩ WebAssembly
JavaScript ∩ WebAssemblyTadeu Zagallo
 
Hitchhiker's Guide to Functional Programming
Hitchhiker's Guide to Functional ProgrammingHitchhiker's Guide to Functional Programming
Hitchhiker's Guide to Functional ProgrammingSergey Shishkin
 
オープンデータを使ったモバイルアプリ開発(応用編)
オープンデータを使ったモバイルアプリ開発(応用編)オープンデータを使ったモバイルアプリ開発(応用編)
オープンデータを使ったモバイルアプリ開発(応用編)Takayuki Goto
 
Gearmam, from the_worker's_perspective copy
Gearmam, from the_worker's_perspective copyGearmam, from the_worker's_perspective copy
Gearmam, from the_worker's_perspective copyBrian Aker
 
Gearmam, from the_worker's_perspective copy
Gearmam, from the_worker's_perspective copyGearmam, from the_worker's_perspective copy
Gearmam, from the_worker's_perspective copyBrian Aker
 
TDC218SP | Trilha Kotlin - DSLs in a Kotlin Way
TDC218SP | Trilha Kotlin - DSLs in a Kotlin WayTDC218SP | Trilha Kotlin - DSLs in a Kotlin Way
TDC218SP | Trilha Kotlin - DSLs in a Kotlin Waytdc-globalcode
 
JS Fest 2019. Anjana Vakil. Serverless Bebop
JS Fest 2019. Anjana Vakil. Serverless BebopJS Fest 2019. Anjana Vakil. Serverless Bebop
JS Fest 2019. Anjana Vakil. Serverless BebopJSFestUA
 
Imugi: Compiler made with Python
Imugi: Compiler made with PythonImugi: Compiler made with Python
Imugi: Compiler made with PythonHan Lee
 
JDD 2016 - Pawel Byszewski - Kotlin, why?
JDD 2016 - Pawel Byszewski - Kotlin, why?JDD 2016 - Pawel Byszewski - Kotlin, why?
JDD 2016 - Pawel Byszewski - Kotlin, why?PROIDEA
 
Compose Async with RxJS
Compose Async with RxJSCompose Async with RxJS
Compose Async with RxJSKyung Yeol Kim
 

Similar to Redux Sagas - React Alicante (20)

Redux saga: managing your side effects. Also: generators in es6
Redux saga: managing your side effects. Also: generators in es6Redux saga: managing your side effects. Also: generators in es6
Redux saga: managing your side effects. Also: generators in es6
 
Think Async: Asynchronous Patterns in NodeJS
Think Async: Asynchronous Patterns in NodeJSThink Async: Asynchronous Patterns in NodeJS
Think Async: Asynchronous Patterns in NodeJS
 
EcmaScript unchained
EcmaScript unchainedEcmaScript unchained
EcmaScript unchained
 
Andrii Orlov "Generators Flexibility in Modern Code"
Andrii Orlov "Generators Flexibility in Modern Code"Andrii Orlov "Generators Flexibility in Modern Code"
Andrii Orlov "Generators Flexibility in Modern Code"
 
Angular2 rxjs
Angular2 rxjsAngular2 rxjs
Angular2 rxjs
 
Monadologie
MonadologieMonadologie
Monadologie
 
ES6(ES2015) is beautiful
ES6(ES2015) is beautifulES6(ES2015) is beautiful
ES6(ES2015) is beautiful
 
Swift 함수 커링 사용하기
Swift 함수 커링 사용하기Swift 함수 커링 사용하기
Swift 함수 커링 사용하기
 
JavaScript ∩ WebAssembly
JavaScript ∩ WebAssemblyJavaScript ∩ WebAssembly
JavaScript ∩ WebAssembly
 
Hitchhiker's Guide to Functional Programming
Hitchhiker's Guide to Functional ProgrammingHitchhiker's Guide to Functional Programming
Hitchhiker's Guide to Functional Programming
 
Javascript
JavascriptJavascript
Javascript
 
オープンデータを使ったモバイルアプリ開発(応用編)
オープンデータを使ったモバイルアプリ開発(応用編)オープンデータを使ったモバイルアプリ開発(応用編)
オープンデータを使ったモバイルアプリ開発(応用編)
 
Gearmam, from the_worker's_perspective copy
Gearmam, from the_worker's_perspective copyGearmam, from the_worker's_perspective copy
Gearmam, from the_worker's_perspective copy
 
Gearmam, from the_worker's_perspective copy
Gearmam, from the_worker's_perspective copyGearmam, from the_worker's_perspective copy
Gearmam, from the_worker's_perspective copy
 
TDC218SP | Trilha Kotlin - DSLs in a Kotlin Way
TDC218SP | Trilha Kotlin - DSLs in a Kotlin WayTDC218SP | Trilha Kotlin - DSLs in a Kotlin Way
TDC218SP | Trilha Kotlin - DSLs in a Kotlin Way
 
ES6 Overview
ES6 OverviewES6 Overview
ES6 Overview
 
JS Fest 2019. Anjana Vakil. Serverless Bebop
JS Fest 2019. Anjana Vakil. Serverless BebopJS Fest 2019. Anjana Vakil. Serverless Bebop
JS Fest 2019. Anjana Vakil. Serverless Bebop
 
Imugi: Compiler made with Python
Imugi: Compiler made with PythonImugi: Compiler made with Python
Imugi: Compiler made with Python
 
JDD 2016 - Pawel Byszewski - Kotlin, why?
JDD 2016 - Pawel Byszewski - Kotlin, why?JDD 2016 - Pawel Byszewski - Kotlin, why?
JDD 2016 - Pawel Byszewski - Kotlin, why?
 
Compose Async with RxJS
Compose Async with RxJSCompose Async with RxJS
Compose Async with RxJS
 

More from Ignacio Martín

Elixir/OTP for PHP developers
Elixir/OTP for PHP developersElixir/OTP for PHP developers
Elixir/OTP for PHP developersIgnacio Martín
 
Introduction to React Native Workshop
Introduction to React Native WorkshopIntroduction to React Native Workshop
Introduction to React Native WorkshopIgnacio Martín
 
Server side rendering with React and Symfony
Server side rendering with React and SymfonyServer side rendering with React and Symfony
Server side rendering with React and SymfonyIgnacio Martín
 
Symfony 4 Workshop - Limenius
Symfony 4 Workshop - LimeniusSymfony 4 Workshop - Limenius
Symfony 4 Workshop - LimeniusIgnacio Martín
 
Server Side Rendering of JavaScript in PHP
Server Side Rendering of JavaScript in PHPServer Side Rendering of JavaScript in PHP
Server Side Rendering of JavaScript in PHPIgnacio Martín
 
Extending Redux in the Server Side
Extending Redux in the Server SideExtending Redux in the Server Side
Extending Redux in the Server SideIgnacio Martín
 
React Native Workshop - React Alicante
React Native Workshop - React AlicanteReact Native Workshop - React Alicante
React Native Workshop - React AlicanteIgnacio Martín
 
Asegurando APIs en Symfony con JWT
Asegurando APIs en Symfony con JWTAsegurando APIs en Symfony con JWT
Asegurando APIs en Symfony con JWTIgnacio Martín
 
Integrating React.js with PHP projects
Integrating React.js with PHP projectsIntegrating React.js with PHP projects
Integrating React.js with PHP projectsIgnacio Martín
 
Keeping the frontend under control with Symfony and Webpack
Keeping the frontend under control with Symfony and WebpackKeeping the frontend under control with Symfony and Webpack
Keeping the frontend under control with Symfony and WebpackIgnacio Martín
 
Integrando React.js en aplicaciones Symfony (deSymfony 2016)
Integrando React.js en aplicaciones Symfony (deSymfony 2016)Integrando React.js en aplicaciones Symfony (deSymfony 2016)
Integrando React.js en aplicaciones Symfony (deSymfony 2016)Ignacio Martín
 
Adding Realtime to your Projects
Adding Realtime to your ProjectsAdding Realtime to your Projects
Adding Realtime to your ProjectsIgnacio Martín
 
Symfony & Javascript. Combining the best of two worlds
Symfony & Javascript. Combining the best of two worldsSymfony & Javascript. Combining the best of two worlds
Symfony & Javascript. Combining the best of two worldsIgnacio Martín
 

More from Ignacio Martín (17)

Elixir/OTP for PHP developers
Elixir/OTP for PHP developersElixir/OTP for PHP developers
Elixir/OTP for PHP developers
 
Introduction to React Native Workshop
Introduction to React Native WorkshopIntroduction to React Native Workshop
Introduction to React Native Workshop
 
Server side rendering with React and Symfony
Server side rendering with React and SymfonyServer side rendering with React and Symfony
Server side rendering with React and Symfony
 
Symfony 4 Workshop - Limenius
Symfony 4 Workshop - LimeniusSymfony 4 Workshop - Limenius
Symfony 4 Workshop - Limenius
 
Server Side Rendering of JavaScript in PHP
Server Side Rendering of JavaScript in PHPServer Side Rendering of JavaScript in PHP
Server Side Rendering of JavaScript in PHP
 
Extending Redux in the Server Side
Extending Redux in the Server SideExtending Redux in the Server Side
Extending Redux in the Server Side
 
React Native Workshop - React Alicante
React Native Workshop - React AlicanteReact Native Workshop - React Alicante
React Native Workshop - React Alicante
 
Asegurando APIs en Symfony con JWT
Asegurando APIs en Symfony con JWTAsegurando APIs en Symfony con JWT
Asegurando APIs en Symfony con JWT
 
Integrating React.js with PHP projects
Integrating React.js with PHP projectsIntegrating React.js with PHP projects
Integrating React.js with PHP projects
 
Introduction to Redux
Introduction to ReduxIntroduction to Redux
Introduction to Redux
 
Keeping the frontend under control with Symfony and Webpack
Keeping the frontend under control with Symfony and WebpackKeeping the frontend under control with Symfony and Webpack
Keeping the frontend under control with Symfony and Webpack
 
Integrando React.js en aplicaciones Symfony (deSymfony 2016)
Integrando React.js en aplicaciones Symfony (deSymfony 2016)Integrando React.js en aplicaciones Symfony (deSymfony 2016)
Integrando React.js en aplicaciones Symfony (deSymfony 2016)
 
Adding Realtime to your Projects
Adding Realtime to your ProjectsAdding Realtime to your Projects
Adding Realtime to your Projects
 
Symfony & Javascript. Combining the best of two worlds
Symfony & Javascript. Combining the best of two worldsSymfony & Javascript. Combining the best of two worlds
Symfony & Javascript. Combining the best of two worlds
 
Symfony 2 CMF
Symfony 2 CMFSymfony 2 CMF
Symfony 2 CMF
 
Doctrine2 sf2Vigo
Doctrine2 sf2VigoDoctrine2 sf2Vigo
Doctrine2 sf2Vigo
 
Presentacion git
Presentacion gitPresentacion git
Presentacion git
 

Recently uploaded

Amazon Bedrock in Action - presentation of the Bedrock's capabilities
Amazon Bedrock in Action - presentation of the Bedrock's capabilitiesAmazon Bedrock in Action - presentation of the Bedrock's capabilities
Amazon Bedrock in Action - presentation of the Bedrock's capabilitiesKrzysztofKkol1
 
Revolutionizing the Digital Transformation Office - Leveraging OnePlan’s AI a...
Revolutionizing the Digital Transformation Office - Leveraging OnePlan’s AI a...Revolutionizing the Digital Transformation Office - Leveraging OnePlan’s AI a...
Revolutionizing the Digital Transformation Office - Leveraging OnePlan’s AI a...OnePlan Solutions
 
Strategies for using alternative queries to mitigate zero results
Strategies for using alternative queries to mitigate zero resultsStrategies for using alternative queries to mitigate zero results
Strategies for using alternative queries to mitigate zero resultsJean Silva
 
Ronisha Informatics Private Limited Catalogue
Ronisha Informatics Private Limited CatalogueRonisha Informatics Private Limited Catalogue
Ronisha Informatics Private Limited Catalogueitservices996
 
Patterns for automating API delivery. API conference
Patterns for automating API delivery. API conferencePatterns for automating API delivery. API conference
Patterns for automating API delivery. API conferencessuser9e7c64
 
2024 DevNexus Patterns for Resiliency: Shuffle shards
2024 DevNexus Patterns for Resiliency: Shuffle shards2024 DevNexus Patterns for Resiliency: Shuffle shards
2024 DevNexus Patterns for Resiliency: Shuffle shardsChristopher Curtin
 
UI5ers live - Custom Controls wrapping 3rd-party libs.pptx
UI5ers live - Custom Controls wrapping 3rd-party libs.pptxUI5ers live - Custom Controls wrapping 3rd-party libs.pptx
UI5ers live - Custom Controls wrapping 3rd-party libs.pptxAndreas Kunz
 
Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...
Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...
Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...confluent
 
OpenChain AI Study Group - Europe and Asia Recap - 2024-04-11 - Full Recording
OpenChain AI Study Group - Europe and Asia Recap - 2024-04-11 - Full RecordingOpenChain AI Study Group - Europe and Asia Recap - 2024-04-11 - Full Recording
OpenChain AI Study Group - Europe and Asia Recap - 2024-04-11 - Full RecordingShane Coughlan
 
Leveraging AI for Mobile App Testing on Real Devices | Applitools + Kobiton
Leveraging AI for Mobile App Testing on Real Devices | Applitools + KobitonLeveraging AI for Mobile App Testing on Real Devices | Applitools + Kobiton
Leveraging AI for Mobile App Testing on Real Devices | Applitools + KobitonApplitools
 
Machine Learning Software Engineering Patterns and Their Engineering
Machine Learning Software Engineering Patterns and Their EngineeringMachine Learning Software Engineering Patterns and Their Engineering
Machine Learning Software Engineering Patterns and Their EngineeringHironori Washizaki
 
What is Advanced Excel and what are some best practices for designing and cre...
What is Advanced Excel and what are some best practices for designing and cre...What is Advanced Excel and what are some best practices for designing and cre...
What is Advanced Excel and what are some best practices for designing and cre...Technogeeks
 
Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...
Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...
Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...Natan Silnitsky
 
Understanding Flamingo - DeepMind's VLM Architecture
Understanding Flamingo - DeepMind's VLM ArchitectureUnderstanding Flamingo - DeepMind's VLM Architecture
Understanding Flamingo - DeepMind's VLM Architecturerahul_net
 
SensoDat: Simulation-based Sensor Dataset of Self-driving Cars
SensoDat: Simulation-based Sensor Dataset of Self-driving CarsSensoDat: Simulation-based Sensor Dataset of Self-driving Cars
SensoDat: Simulation-based Sensor Dataset of Self-driving CarsChristian Birchler
 
Odoo 14 - eLearning Module In Odoo 14 Enterprise
Odoo 14 - eLearning Module In Odoo 14 EnterpriseOdoo 14 - eLearning Module In Odoo 14 Enterprise
Odoo 14 - eLearning Module In Odoo 14 Enterprisepreethippts
 
SoftTeco - Software Development Company Profile
SoftTeco - Software Development Company ProfileSoftTeco - Software Development Company Profile
SoftTeco - Software Development Company Profileakrivarotava
 
Introduction to Firebase Workshop Slides
Introduction to Firebase Workshop SlidesIntroduction to Firebase Workshop Slides
Introduction to Firebase Workshop Slidesvaideheekore1
 
SpotFlow: Tracking Method Calls and States at Runtime
SpotFlow: Tracking Method Calls and States at RuntimeSpotFlow: Tracking Method Calls and States at Runtime
SpotFlow: Tracking Method Calls and States at Runtimeandrehoraa
 
How to submit a standout Adobe Champion Application
How to submit a standout Adobe Champion ApplicationHow to submit a standout Adobe Champion Application
How to submit a standout Adobe Champion ApplicationBradBedford3
 

Recently uploaded (20)

Amazon Bedrock in Action - presentation of the Bedrock's capabilities
Amazon Bedrock in Action - presentation of the Bedrock's capabilitiesAmazon Bedrock in Action - presentation of the Bedrock's capabilities
Amazon Bedrock in Action - presentation of the Bedrock's capabilities
 
Revolutionizing the Digital Transformation Office - Leveraging OnePlan’s AI a...
Revolutionizing the Digital Transformation Office - Leveraging OnePlan’s AI a...Revolutionizing the Digital Transformation Office - Leveraging OnePlan’s AI a...
Revolutionizing the Digital Transformation Office - Leveraging OnePlan’s AI a...
 
Strategies for using alternative queries to mitigate zero results
Strategies for using alternative queries to mitigate zero resultsStrategies for using alternative queries to mitigate zero results
Strategies for using alternative queries to mitigate zero results
 
Ronisha Informatics Private Limited Catalogue
Ronisha Informatics Private Limited CatalogueRonisha Informatics Private Limited Catalogue
Ronisha Informatics Private Limited Catalogue
 
Patterns for automating API delivery. API conference
Patterns for automating API delivery. API conferencePatterns for automating API delivery. API conference
Patterns for automating API delivery. API conference
 
2024 DevNexus Patterns for Resiliency: Shuffle shards
2024 DevNexus Patterns for Resiliency: Shuffle shards2024 DevNexus Patterns for Resiliency: Shuffle shards
2024 DevNexus Patterns for Resiliency: Shuffle shards
 
UI5ers live - Custom Controls wrapping 3rd-party libs.pptx
UI5ers live - Custom Controls wrapping 3rd-party libs.pptxUI5ers live - Custom Controls wrapping 3rd-party libs.pptx
UI5ers live - Custom Controls wrapping 3rd-party libs.pptx
 
Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...
Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...
Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...
 
OpenChain AI Study Group - Europe and Asia Recap - 2024-04-11 - Full Recording
OpenChain AI Study Group - Europe and Asia Recap - 2024-04-11 - Full RecordingOpenChain AI Study Group - Europe and Asia Recap - 2024-04-11 - Full Recording
OpenChain AI Study Group - Europe and Asia Recap - 2024-04-11 - Full Recording
 
Leveraging AI for Mobile App Testing on Real Devices | Applitools + Kobiton
Leveraging AI for Mobile App Testing on Real Devices | Applitools + KobitonLeveraging AI for Mobile App Testing on Real Devices | Applitools + Kobiton
Leveraging AI for Mobile App Testing on Real Devices | Applitools + Kobiton
 
Machine Learning Software Engineering Patterns and Their Engineering
Machine Learning Software Engineering Patterns and Their EngineeringMachine Learning Software Engineering Patterns and Their Engineering
Machine Learning Software Engineering Patterns and Their Engineering
 
What is Advanced Excel and what are some best practices for designing and cre...
What is Advanced Excel and what are some best practices for designing and cre...What is Advanced Excel and what are some best practices for designing and cre...
What is Advanced Excel and what are some best practices for designing and cre...
 
Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...
Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...
Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...
 
Understanding Flamingo - DeepMind's VLM Architecture
Understanding Flamingo - DeepMind's VLM ArchitectureUnderstanding Flamingo - DeepMind's VLM Architecture
Understanding Flamingo - DeepMind's VLM Architecture
 
SensoDat: Simulation-based Sensor Dataset of Self-driving Cars
SensoDat: Simulation-based Sensor Dataset of Self-driving CarsSensoDat: Simulation-based Sensor Dataset of Self-driving Cars
SensoDat: Simulation-based Sensor Dataset of Self-driving Cars
 
Odoo 14 - eLearning Module In Odoo 14 Enterprise
Odoo 14 - eLearning Module In Odoo 14 EnterpriseOdoo 14 - eLearning Module In Odoo 14 Enterprise
Odoo 14 - eLearning Module In Odoo 14 Enterprise
 
SoftTeco - Software Development Company Profile
SoftTeco - Software Development Company ProfileSoftTeco - Software Development Company Profile
SoftTeco - Software Development Company Profile
 
Introduction to Firebase Workshop Slides
Introduction to Firebase Workshop SlidesIntroduction to Firebase Workshop Slides
Introduction to Firebase Workshop Slides
 
SpotFlow: Tracking Method Calls and States at Runtime
SpotFlow: Tracking Method Calls and States at RuntimeSpotFlow: Tracking Method Calls and States at Runtime
SpotFlow: Tracking Method Calls and States at Runtime
 
How to submit a standout Adobe Champion Application
How to submit a standout Adobe Champion ApplicationHow to submit a standout Adobe Champion Application
How to submit a standout Adobe Champion Application
 

Redux Sagas - React Alicante

  • 1. September 2017 Redux Saga, the Viking way to manage side effects Nacho Martín
  • 2. Thanks to our sponsors!
  • 3. 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.
  • 4. Roadmap: • Are Sagas indispensable? • Is there a theoretical background? • ES6 Generators • Sagas
  • 5. Maybe you don’t need redux-saga
  • 6.
  • 7.
  • 9. What we need dispatch({type:‘API_REQUEST’}) Reducer state = {…state, requesting : true } Store
  • 12. What we need dispatch({type:‘API_REQUEST’}) Reducer state = {…state, data : action.data } Store Middleware dispatch({type:‘API_REQUEST_SUCCESS’, data}) ⚙
  • 13. What we need dispatch({type:‘API_REQUEST’}) Reducer state = {…state, showError: true } Store Middleware dispatch({type:‘API_REQUEST_ERROR’}) ⚙
  • 14. What we need dispatch({type:‘API_REQUEST’}) Reducer state = {…state, showError: true } Store Middleware dispatch({type:‘API_REQUEST_ERROR’}) ⚙ Side effects Pure code
  • 15. 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())
  • 16. Redux-thunk The lib has only 14 lines of code If you don’t need more, stick with it But what if you do?
  • 17. If you need more •Redux-saga •Redux-observable •… •Maybe your own in the future?
  • 23. But nowadays A long and complicated story with many details e.g. “my neighbor told me the saga of his divorce again”
  • 24. Originally in C.S. Héctor García-Molina
  • 25. Originally in C.S. Héctor García-Molina ( )
  • 26. Originally in C.S. Héctor García-Molina ( )
  • 27. 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
  • 28. But nowadays A process manager used to orchestrate complex operations.
  • 29. But nowadays A process manager used to orchestrate complex operations. A library to manage side-effects. And in redux-saga:
  • 31. 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())
  • 32. 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())
  • 33. 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()) {}
  • 34. 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())
  • 35. 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 }
  • 36. 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())
  • 37. 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 }
  • 38. 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())
  • 39. 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 }
  • 40. 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())
  • 41. 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 }
  • 42. 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))
  • 43. 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
  • 44. 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))
  • 45. 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))
  • 46. 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 }
  • 47. 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))
  • 48. 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
  • 49. 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))
  • 50. 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))
  • 51. 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 }
  • 52. 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))
  • 53. 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
  • 54. 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))
  • 55. 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))
  • 56. 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))
  • 57. 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 }
  • 58. 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))
  • 59. 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
  • 60. 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))
  • 61. 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))
  • 62. 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 }
  • 63. 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))
  • 64. 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))
  • 65. 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 }
  • 66. 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: 1, done: false }
  • 67. 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:21, done: false }
  • 68. 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:321, done: false }
  • 69. 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().value console.log(promise) promise.then((result) => { console.log(result) var response = it.next(result) console.log(response) })
  • 70. 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().value console.log(promise) promise.then((result) => { console.log(result) var response = it.next(result) console.log(response) })
  • 71. 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().value console.log(promise) promise.then((result) => { console.log(result) var response = it.next(result) console.log(response) }) Promise { <pending> }
  • 72. 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().value console.log(promise) promise.then((result) => { console.log(result) var response = it.next(result) console.log(response) }) { username: 'nacho', hash: '12345' }
  • 73. 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().value console.log(promise) promise.then((result) => { console.log(result) var response = it.next(result) console.log(response) }) { value: { username: 'nacho', hash: '12345' }, done: true }
  • 74. 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) } //... }
  • 75. 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
  • 76. 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 ?
  • 77. 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
  • 78. redux-saga (or other libs) 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
  • 79. Sagas
  • 80. 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)
  • 81. Simple problem: 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?
  • 82. Naive solution class MoveButton extends Component { render() { return ( <Button onPress={() => soundManager.play( ‘buzz’)} /> ) } }
  • 83. Naive solution class MoveButton extends Component { render() { return ( <Button onPress={() => soundManager.play( ‘buzz’)} /> ) } } But where does soundManager come from?
  • 84. Naive solution class MoveButton extends Component { render() { return ( <Button onPress={() => dispatch(playSound( ‘buzz’))} /> ) } }
  • 85. Naive solution class MoveButton extends Component { render() { return ( <Button onPress={() => dispatch(playSound( ‘buzz’))} /> ) } } Dispatch an action and we’ll see. But the action creator doesn’t have access to SoundManager :_(
  • 86. Naive solution class MoveButton extends Component { render() { return ( <Button onPress={() => this.props.soundManager(‘buzz’)} /> ) } } Passing it from its parent, and the parent of its parent with props?
  • 87. Naive solution class MoveButton extends Component { render() { return ( <Button onPress={() => this.props.soundManager(‘buzz’)} /> ) } } Passing it from its parent, and the parent of its parent with props? Hairball ahead
  • 88. Naive solution class 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…
  • 89. Naive solution class 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… Then what, maybe use context?
  • 90. Naive solution What if we want to play a sound when the opponent moves too and we receive her movements from a websocket?
  • 91. 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)) } //... }
  • 92. 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 :_(
  • 93. Using sagas import { take } from 'redux-saga/effects' export default function* rootSaga() { const action = yield take(Constants.PLAY_SOUND_REQUEST) console.log(action.sound) }
  • 94. Using sagas import { take } from 'redux-saga/effects' export default function* rootSaga(soundManager) { const action = yield take(Constants.PLAY_SOUND_REQUEST) soundManager.play(action.sound) }
  • 95. Using sagas import { take } from 'redux-saga/effects' export default function* rootSaga(soundManager) { const action = yield take(Constants.PLAY_SOUND_REQUEST) soundManager.play(action.sound) } Ok, but we need a mock to test it
  • 96. 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) }
  • 97. 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
  • 98. 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: []}}
  • 99. 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) }
  • 100. 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
  • 101. 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) } }
  • 102. 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
  • 103. 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) }
  • 104. 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'}) } }
  • 105. 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
  • 106. Ajax example 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())
  • 107. Ajax example import { takeEvery, put, call } from 'redux-saga/effects' export default function* rootSaga() { const action = yield takeEvery(Constants.SANDWICH_REQUEST, sandwichRequest) } 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} } } dispatch({type:Constants.SANDWICH_REQUEST}) Saga
  • 108. takeLatest import { takeLatest } from 'redux-saga/effects' function* watchFetchData() { yield takeLatest('FETCH_REQUESTED', fetchData) }
  • 109. takeLatest import { takeLatest } from 'redux-saga/effects' function* watchFetchData() { yield takeLatest('FETCH_REQUESTED', fetchData) } Ensure that only the last fetchData will be running
  • 110. 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(socket, response) { yield fork(listenToSocket, socket, 'game:move', processMovements) yield fork(listenToSocket, socket, 'game:end', processEndGame) // More things that we want to do inside a game //... }
  • 111. 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(socket, response) { yield fork(listenToSocket, socket, 'game:move', processMovements) yield fork(listenToSocket, socket, 'game:end', processEndGame) // More things that we want to do inside a game //... } Fork will create a new task without blocking in the caller
  • 112. 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) } }
  • 113. Cancellation (cancel) function* game(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) } }
  • 114. Cancellation (cancel) function* game(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) } } }
  • 115. Cancellation (cancel) function* game(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) } } }
  • 116. Cancellation (cancel) function* game(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) } } }
  • 117. 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
  • 118. 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 }
  • 119. export default function* rootSaga() { yield all([ playSounds(), watchJoinGame(), //… ]) } Parallel (all) Will block until all terminate
  • 120. 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
  • 121. 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) } }
  • 122. 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) } }
  • 123. 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 in call
  • 124. Connect + listen from socket function websocketInitChannel() { return eventChannel( emitter => { const ws = new WebSocket() ws.onmessage = msg => { return emitter( { type:‘WS_EVENT’, msg } ) } // unsubscribe function return () => { ws.close() emitter(END) } }) } https://medium.com/@pierremaoui/using-websockets-with-redux-sagas-a2bf26467cab eventChannel turns the ws connection into a channel export default function* websocketSagas() { const channel = yield call(websocketInitChannel) while (true) { const action = yield take(channel) yield put(action) } } import { eventChannel, END } from 'redux-saga'
  • 125. 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
  • 126. Summary •Take (takeEvery, takeLast) •Call •Put •Fork & Spawn •Cancel •Channels & EventChannels •All & race •Select
  • 127. Takeaways • Don’t use sagas until you need to orchestrate complex side effects, but prepare for that moment. • Forget about the sagas paper to use redux-saga. • Generators = friends. Generators + promises = love. • Think how to model your problem with the effects of the lib, or combinations of them.