SlideShare a Scribd company logo
React Native Munich Meetup
May 2017
REDUX SAGA
Nacho Martín
@nacmartin
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?
• Do they have a strong 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)
}
}
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 }
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 }
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 }
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;
};
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
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)
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)
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={playSound(soundManager, ‘buzz’)}
/>
);
}
}
Naive solution
class MoveButton extends Component {
render() {
return (
<Button
onPress={playSound(soundManager, ‘buzz’)}
/>
);
}
}
But where does soundManager come from?
Naive solution
class MoveButton extends Component {
render() {
return (
<Button
onPress={this.props.dispatch(playSound(‘buzz’))}
/>
);
}
}
Naive solution
class 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 :_(
Naive solution
class 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?
Naive solution
class 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
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
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)
}
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)
}
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
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
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
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
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})
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
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(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
//...
}
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
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* 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* 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) }
}
}
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) }
}
}
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
}
Parallel (all)
export default function* rootSaga() {
yield all([
playSounds(),
watchJoinGame(),
//…
])
}
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
Connect + listen from socket
import { 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
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
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 })
}
Summary
•Take (takeEvery, takeLast)
•Call
•Put
•Fork & Spawn
•Cancel
•Channels & EventChannels
•All & race
•Select
•Delay
Thanks! @nacmartin
nacho@limenius.com
http://limenius.com

More Related Content

What's hot

Recompacting your react application
Recompacting your react applicationRecompacting your react application
Recompacting your react application
Greg Bergé
 
ES6 patterns in the wild
ES6 patterns in the wildES6 patterns in the wild
ES6 patterns in the wild
Joe Morgan
 
Rxjs ngvikings
Rxjs ngvikingsRxjs ngvikings
Rxjs ngvikings
Christoffer Noring
 
Unbreakable: The Craft of Code
Unbreakable: The Craft of CodeUnbreakable: The Craft of Code
Unbreakable: The Craft of Code
Joe Morgan
 
Flying Futures at the same sky can make the sun rise at midnight
Flying Futures at the same sky can make the sun rise at midnightFlying Futures at the same sky can make the sun rise at midnight
Flying Futures at the same sky can make the sun rise at midnight
Wiem Zine Elabidine
 
Pure Future
Pure FuturePure Future
Pure Future
Wiem Zine Elabidine
 
Angular2 rxjs
Angular2 rxjsAngular2 rxjs
Angular2 rxjs
Christoffer Noring
 
Workshop 23: ReactJS, React & Redux testing
Workshop 23: ReactJS, React & Redux testingWorkshop 23: ReactJS, React & Redux testing
Workshop 23: ReactJS, React & Redux testing
Visual Engineering
 
UI 모듈화로 워라밸 지키기
UI 모듈화로 워라밸 지키기UI 모듈화로 워라밸 지키기
UI 모듈화로 워라밸 지키기
NAVER SHOPPING
 
Berlin meetup
Berlin meetupBerlin meetup
Berlin meetup
Wiem Zine Elabidine
 
React hooks beyond hype
React hooks beyond hypeReact hooks beyond hype
React hooks beyond hype
Magdiel Duarte
 
Error Management: Future vs ZIO
Error Management: Future vs ZIOError Management: Future vs ZIO
Error Management: Future vs ZIO
John De Goes
 
Migrating from Flux to Redux. Why and how.
Migrating from Flux to Redux. Why and how.Migrating from Flux to Redux. Why and how.
Migrating from Flux to Redux. Why and how.
Astrails
 
オープンデータを使ったモバイルアプリ開発(応用編)
オープンデータを使ったモバイルアプリ開発(応用編)オープンデータを使ったモバイルアプリ開発(応用編)
オープンデータを使ったモバイルアプリ開発(応用編)
Takayuki Goto
 
Object-Oriented Javascript
Object-Oriented JavascriptObject-Oriented Javascript
Object-Oriented Javascript
kvangork
 
Austin Bingham. Transducers in Python. PyCon Belarus
Austin Bingham. Transducers in Python. PyCon BelarusAustin Bingham. Transducers in Python. PyCon Belarus
Austin Bingham. Transducers in Python. PyCon Belarus
Alina Dolgikh
 
Universal JavaScript
Universal JavaScriptUniversal JavaScript
Universal JavaScript
名辰 洪
 
Tweaking the interactive grid
Tweaking the interactive gridTweaking the interactive grid
Tweaking the interactive grid
Roel Hartman
 
You will learn RxJS in 2017
You will learn RxJS in 2017You will learn RxJS in 2017
You will learn RxJS in 2017
名辰 洪
 
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
 

What's hot (20)

Recompacting your react application
Recompacting your react applicationRecompacting your react application
Recompacting your react application
 
ES6 patterns in the wild
ES6 patterns in the wildES6 patterns in the wild
ES6 patterns in the wild
 
Rxjs ngvikings
Rxjs ngvikingsRxjs ngvikings
Rxjs ngvikings
 
Unbreakable: The Craft of Code
Unbreakable: The Craft of CodeUnbreakable: The Craft of Code
Unbreakable: The Craft of Code
 
Flying Futures at the same sky can make the sun rise at midnight
Flying Futures at the same sky can make the sun rise at midnightFlying Futures at the same sky can make the sun rise at midnight
Flying Futures at the same sky can make the sun rise at midnight
 
Pure Future
Pure FuturePure Future
Pure Future
 
Angular2 rxjs
Angular2 rxjsAngular2 rxjs
Angular2 rxjs
 
Workshop 23: ReactJS, React & Redux testing
Workshop 23: ReactJS, React & Redux testingWorkshop 23: ReactJS, React & Redux testing
Workshop 23: ReactJS, React & Redux testing
 
UI 모듈화로 워라밸 지키기
UI 모듈화로 워라밸 지키기UI 모듈화로 워라밸 지키기
UI 모듈화로 워라밸 지키기
 
Berlin meetup
Berlin meetupBerlin meetup
Berlin meetup
 
React hooks beyond hype
React hooks beyond hypeReact hooks beyond hype
React hooks beyond hype
 
Error Management: Future vs ZIO
Error Management: Future vs ZIOError Management: Future vs ZIO
Error Management: Future vs ZIO
 
Migrating from Flux to Redux. Why and how.
Migrating from Flux to Redux. Why and how.Migrating from Flux to Redux. Why and how.
Migrating from Flux to Redux. Why and how.
 
オープンデータを使ったモバイルアプリ開発(応用編)
オープンデータを使ったモバイルアプリ開発(応用編)オープンデータを使ったモバイルアプリ開発(応用編)
オープンデータを使ったモバイルアプリ開発(応用編)
 
Object-Oriented Javascript
Object-Oriented JavascriptObject-Oriented Javascript
Object-Oriented Javascript
 
Austin Bingham. Transducers in Python. PyCon Belarus
Austin Bingham. Transducers in Python. PyCon BelarusAustin Bingham. Transducers in Python. PyCon Belarus
Austin Bingham. Transducers in Python. PyCon Belarus
 
Universal JavaScript
Universal JavaScriptUniversal JavaScript
Universal JavaScript
 
Tweaking the interactive grid
Tweaking the interactive gridTweaking the interactive grid
Tweaking the interactive grid
 
You will learn RxJS in 2017
You will learn RxJS in 2017You will learn RxJS in 2017
You will learn RxJS in 2017
 
JDD 2016 - Pawel Byszewski - Kotlin, why?
JDD 2016 - Pawel Byszewski - Kotlin, why?JDD 2016 - Pawel Byszewski - Kotlin, why?
JDD 2016 - Pawel Byszewski - Kotlin, why?
 

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

EcmaScript unchained
EcmaScript unchainedEcmaScript unchained
EcmaScript unchained
Eduard Tomàs
 
Think Async: Asynchronous Patterns in NodeJS
Think Async: Asynchronous Patterns in NodeJSThink Async: Asynchronous Patterns in NodeJS
Think Async: Asynchronous Patterns in NodeJS
Adam L Barrett
 
Monadologie
MonadologieMonadologie
Monadologie
league
 
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
 
Swift 함수 커링 사용하기
Swift 함수 커링 사용하기Swift 함수 커링 사용하기
Swift 함수 커링 사용하기
진성 오
 
Javascript
JavascriptJavascript
Javascript
Vlad Ifrim
 
ES6(ES2015) is beautiful
ES6(ES2015) is beautifulES6(ES2015) is beautiful
ES6(ES2015) is beautiful
monikagupta18jan
 
JavaScript ∩ WebAssembly
JavaScript ∩ WebAssemblyJavaScript ∩ WebAssembly
JavaScript ∩ WebAssembly
Tadeu Zagallo
 
Hitchhiker's Guide to Functional Programming
Hitchhiker's Guide to Functional ProgrammingHitchhiker's Guide to Functional Programming
Hitchhiker's Guide to Functional Programming
Sergey Shishkin
 
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
tdc-globalcode
 
Imugi: Compiler made with Python
Imugi: Compiler made with PythonImugi: Compiler made with Python
Imugi: Compiler made with Python
Han Lee
 
Go ahead, make my day
Go ahead, make my dayGo ahead, make my day
Go ahead, make my day
Tor Ivry
 
I dont know what is wrong with this roulette program I cant seem.pdf
I dont know what is wrong with this roulette program I cant seem.pdfI dont know what is wrong with this roulette program I cant seem.pdf
I dont know what is wrong with this roulette program I cant seem.pdf
archanaemporium
 
What's in Kotlin for us - Alexandre Greschon, MyHeritage
What's in Kotlin for us - Alexandre Greschon, MyHeritageWhat's in Kotlin for us - Alexandre Greschon, MyHeritage
What's in Kotlin for us - Alexandre Greschon, MyHeritage
DroidConTLV
 
Functional Programming with Groovy
Functional Programming with GroovyFunctional Programming with Groovy
Functional Programming with Groovy
Arturo Herrero
 
Mcq cpup
Mcq cpupMcq cpup
Mcq cpup
tahir_ali786
 
Kotlin Bytecode Generation and Runtime Performance
Kotlin Bytecode Generation and Runtime PerformanceKotlin Bytecode Generation and Runtime Performance
Kotlin Bytecode Generation and Runtime Performance
intelliyole
 
Functional programming basics
Functional programming basicsFunctional programming basics
Functional programming basicsopenbala
 
Kotlin, why?
Kotlin, why?Kotlin, why?
Kotlin, why?
Paweł Byszewski
 
SDC - Einführung in Scala
SDC - Einführung in ScalaSDC - Einführung in Scala
SDC - Einführung in Scala
Christian Baranowski
 

Similar to Redux saga: managing your side effects. Also: generators in es6 (20)

EcmaScript unchained
EcmaScript unchainedEcmaScript unchained
EcmaScript unchained
 
Think Async: Asynchronous Patterns in NodeJS
Think Async: Asynchronous Patterns in NodeJSThink Async: Asynchronous Patterns in NodeJS
Think Async: Asynchronous Patterns in NodeJS
 
Monadologie
MonadologieMonadologie
Monadologie
 
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"
 
Swift 함수 커링 사용하기
Swift 함수 커링 사용하기Swift 함수 커링 사용하기
Swift 함수 커링 사용하기
 
Javascript
JavascriptJavascript
Javascript
 
ES6(ES2015) is beautiful
ES6(ES2015) is beautifulES6(ES2015) is beautiful
ES6(ES2015) is beautiful
 
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
 
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
 
Imugi: Compiler made with Python
Imugi: Compiler made with PythonImugi: Compiler made with Python
Imugi: Compiler made with Python
 
Go ahead, make my day
Go ahead, make my dayGo ahead, make my day
Go ahead, make my day
 
I dont know what is wrong with this roulette program I cant seem.pdf
I dont know what is wrong with this roulette program I cant seem.pdfI dont know what is wrong with this roulette program I cant seem.pdf
I dont know what is wrong with this roulette program I cant seem.pdf
 
What's in Kotlin for us - Alexandre Greschon, MyHeritage
What's in Kotlin for us - Alexandre Greschon, MyHeritageWhat's in Kotlin for us - Alexandre Greschon, MyHeritage
What's in Kotlin for us - Alexandre Greschon, MyHeritage
 
Functional Programming with Groovy
Functional Programming with GroovyFunctional Programming with Groovy
Functional Programming with Groovy
 
Mcq cpup
Mcq cpupMcq cpup
Mcq cpup
 
Kotlin Bytecode Generation and Runtime Performance
Kotlin Bytecode Generation and Runtime PerformanceKotlin Bytecode Generation and Runtime Performance
Kotlin Bytecode Generation and Runtime Performance
 
Functional programming basics
Functional programming basicsFunctional programming basics
Functional programming basics
 
Kotlin, why?
Kotlin, why?Kotlin, why?
Kotlin, why?
 
SDC - Einführung in Scala
SDC - Einführung in ScalaSDC - Einführung in Scala
SDC - Einführung in Scala
 

More from Ignacio Martín

Elixir/OTP for PHP developers
Elixir/OTP for PHP developersElixir/OTP for PHP developers
Elixir/OTP for PHP developers
Ignacio Martín
 
Introduction to React Native Workshop
Introduction to React Native WorkshopIntroduction to React Native Workshop
Introduction to React Native Workshop
Ignacio 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 Symfony
Ignacio Martín
 
Symfony 4 Workshop - Limenius
Symfony 4 Workshop - LimeniusSymfony 4 Workshop - Limenius
Symfony 4 Workshop - Limenius
Ignacio 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 PHP
Ignacio Martín
 
Extending Redux in the Server Side
Extending Redux in the Server SideExtending Redux in the Server Side
Extending Redux in the Server Side
Ignacio Martín
 
React Native Workshop - React Alicante
React Native Workshop - React AlicanteReact Native Workshop - React Alicante
React Native Workshop - React Alicante
Ignacio Martín
 
Asegurando APIs en Symfony con JWT
Asegurando APIs en Symfony con JWTAsegurando APIs en Symfony con JWT
Asegurando APIs en Symfony con JWT
Ignacio Martín
 
Integrating React.js with PHP projects
Integrating React.js with PHP projectsIntegrating React.js with PHP projects
Integrating React.js with PHP projects
Ignacio Martín
 
Introduction to Redux
Introduction to ReduxIntroduction to Redux
Introduction to Redux
Ignacio 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 Webpack
Ignacio 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 Projects
Ignacio 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
 
Symfony 2 CMF
Symfony 2 CMFSymfony 2 CMF
Symfony 2 CMF
Ignacio Martín
 
Doctrine2 sf2Vigo
Doctrine2 sf2VigoDoctrine2 sf2Vigo
Doctrine2 sf2Vigo
Ignacio Martín
 
Presentacion git
Presentacion gitPresentacion git
Presentacion git
Ignacio 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

JAVIER LASA-EXPERIENCIA digital 1986-2024.pdf
JAVIER LASA-EXPERIENCIA digital 1986-2024.pdfJAVIER LASA-EXPERIENCIA digital 1986-2024.pdf
JAVIER LASA-EXPERIENCIA digital 1986-2024.pdf
Javier Lasa
 
学位认证网(DU毕业证)迪肯大学毕业证成绩单一比一原版制作
学位认证网(DU毕业证)迪肯大学毕业证成绩单一比一原版制作学位认证网(DU毕业证)迪肯大学毕业证成绩单一比一原版制作
学位认证网(DU毕业证)迪肯大学毕业证成绩单一比一原版制作
zyfovom
 
Gen Z and the marketplaces - let's translate their needs
Gen Z and the marketplaces - let's translate their needsGen Z and the marketplaces - let's translate their needs
Gen Z and the marketplaces - let's translate their needs
Laura Szabó
 
Bridging the Digital Gap Brad Spiegel Macon, GA Initiative.pptx
Bridging the Digital Gap Brad Spiegel Macon, GA Initiative.pptxBridging the Digital Gap Brad Spiegel Macon, GA Initiative.pptx
Bridging the Digital Gap Brad Spiegel Macon, GA Initiative.pptx
Brad Spiegel Macon GA
 
急速办(bedfordhire毕业证书)英国贝德福特大学毕业证成绩单原版一模一样
急速办(bedfordhire毕业证书)英国贝德福特大学毕业证成绩单原版一模一样急速办(bedfordhire毕业证书)英国贝德福特大学毕业证成绩单原版一模一样
急速办(bedfordhire毕业证书)英国贝德福特大学毕业证成绩单原版一模一样
3ipehhoa
 
Bài tập unit 1 English in the world.docx
Bài tập unit 1 English in the world.docxBài tập unit 1 English in the world.docx
Bài tập unit 1 English in the world.docx
nhiyenphan2005
 
一比一原版(LBS毕业证)伦敦商学院毕业证成绩单专业办理
一比一原版(LBS毕业证)伦敦商学院毕业证成绩单专业办理一比一原版(LBS毕业证)伦敦商学院毕业证成绩单专业办理
一比一原版(LBS毕业证)伦敦商学院毕业证成绩单专业办理
eutxy
 
原版仿制(uob毕业证书)英国伯明翰大学毕业证本科学历证书原版一模一样
原版仿制(uob毕业证书)英国伯明翰大学毕业证本科学历证书原版一模一样原版仿制(uob毕业证书)英国伯明翰大学毕业证本科学历证书原版一模一样
原版仿制(uob毕业证书)英国伯明翰大学毕业证本科学历证书原版一模一样
3ipehhoa
 
7 Best Cloud Hosting Services to Try Out in 2024
7 Best Cloud Hosting Services to Try Out in 20247 Best Cloud Hosting Services to Try Out in 2024
7 Best Cloud Hosting Services to Try Out in 2024
Danica Gill
 
1比1复刻(bath毕业证书)英国巴斯大学毕业证学位证原版一模一样
1比1复刻(bath毕业证书)英国巴斯大学毕业证学位证原版一模一样1比1复刻(bath毕业证书)英国巴斯大学毕业证学位证原版一模一样
1比1复刻(bath毕业证书)英国巴斯大学毕业证学位证原版一模一样
3ipehhoa
 
办理毕业证(UPenn毕业证)宾夕法尼亚大学毕业证成绩单快速办理
办理毕业证(UPenn毕业证)宾夕法尼亚大学毕业证成绩单快速办理办理毕业证(UPenn毕业证)宾夕法尼亚大学毕业证成绩单快速办理
办理毕业证(UPenn毕业证)宾夕法尼亚大学毕业证成绩单快速办理
uehowe
 
成绩单ps(UST毕业证)圣托马斯大学毕业证成绩单快速办理
成绩单ps(UST毕业证)圣托马斯大学毕业证成绩单快速办理成绩单ps(UST毕业证)圣托马斯大学毕业证成绩单快速办理
成绩单ps(UST毕业证)圣托马斯大学毕业证成绩单快速办理
ysasp1
 
Meet up Milano 14 _ Axpo Italia_ Migration from Mule3 (On-prem) to.pdf
Meet up Milano 14 _ Axpo Italia_ Migration from Mule3 (On-prem) to.pdfMeet up Milano 14 _ Axpo Italia_ Migration from Mule3 (On-prem) to.pdf
Meet up Milano 14 _ Axpo Italia_ Migration from Mule3 (On-prem) to.pdf
Florence Consulting
 
制作毕业证书(ANU毕业证)莫纳什大学毕业证成绩单官方原版办理
制作毕业证书(ANU毕业证)莫纳什大学毕业证成绩单官方原版办理制作毕业证书(ANU毕业证)莫纳什大学毕业证成绩单官方原版办理
制作毕业证书(ANU毕业证)莫纳什大学毕业证成绩单官方原版办理
cuobya
 
一比一原版(SLU毕业证)圣路易斯大学毕业证成绩单专业办理
一比一原版(SLU毕业证)圣路易斯大学毕业证成绩单专业办理一比一原版(SLU毕业证)圣路易斯大学毕业证成绩单专业办理
一比一原版(SLU毕业证)圣路易斯大学毕业证成绩单专业办理
keoku
 
APNIC Foundation, presented by Ellisha Heppner at the PNG DNS Forum 2024
APNIC Foundation, presented by Ellisha Heppner at the PNG DNS Forum 2024APNIC Foundation, presented by Ellisha Heppner at the PNG DNS Forum 2024
APNIC Foundation, presented by Ellisha Heppner at the PNG DNS Forum 2024
APNIC
 
guildmasters guide to ravnica Dungeons & Dragons 5...
guildmasters guide to ravnica Dungeons & Dragons 5...guildmasters guide to ravnica Dungeons & Dragons 5...
guildmasters guide to ravnica Dungeons & Dragons 5...
Rogerio Filho
 
重新申请毕业证书(RMIT毕业证)皇家墨尔本理工大学毕业证成绩单精仿办理
重新申请毕业证书(RMIT毕业证)皇家墨尔本理工大学毕业证成绩单精仿办理重新申请毕业证书(RMIT毕业证)皇家墨尔本理工大学毕业证成绩单精仿办理
重新申请毕业证书(RMIT毕业证)皇家墨尔本理工大学毕业证成绩单精仿办理
vmemo1
 
[HUN][hackersuli] Red Teaming alapok 2024
[HUN][hackersuli] Red Teaming alapok 2024[HUN][hackersuli] Red Teaming alapok 2024
[HUN][hackersuli] Red Teaming alapok 2024
hackersuli
 
可查真实(Monash毕业证)西澳大学毕业证成绩单退学买
可查真实(Monash毕业证)西澳大学毕业证成绩单退学买可查真实(Monash毕业证)西澳大学毕业证成绩单退学买
可查真实(Monash毕业证)西澳大学毕业证成绩单退学买
cuobya
 

Recently uploaded (20)

JAVIER LASA-EXPERIENCIA digital 1986-2024.pdf
JAVIER LASA-EXPERIENCIA digital 1986-2024.pdfJAVIER LASA-EXPERIENCIA digital 1986-2024.pdf
JAVIER LASA-EXPERIENCIA digital 1986-2024.pdf
 
学位认证网(DU毕业证)迪肯大学毕业证成绩单一比一原版制作
学位认证网(DU毕业证)迪肯大学毕业证成绩单一比一原版制作学位认证网(DU毕业证)迪肯大学毕业证成绩单一比一原版制作
学位认证网(DU毕业证)迪肯大学毕业证成绩单一比一原版制作
 
Gen Z and the marketplaces - let's translate their needs
Gen Z and the marketplaces - let's translate their needsGen Z and the marketplaces - let's translate their needs
Gen Z and the marketplaces - let's translate their needs
 
Bridging the Digital Gap Brad Spiegel Macon, GA Initiative.pptx
Bridging the Digital Gap Brad Spiegel Macon, GA Initiative.pptxBridging the Digital Gap Brad Spiegel Macon, GA Initiative.pptx
Bridging the Digital Gap Brad Spiegel Macon, GA Initiative.pptx
 
急速办(bedfordhire毕业证书)英国贝德福特大学毕业证成绩单原版一模一样
急速办(bedfordhire毕业证书)英国贝德福特大学毕业证成绩单原版一模一样急速办(bedfordhire毕业证书)英国贝德福特大学毕业证成绩单原版一模一样
急速办(bedfordhire毕业证书)英国贝德福特大学毕业证成绩单原版一模一样
 
Bài tập unit 1 English in the world.docx
Bài tập unit 1 English in the world.docxBài tập unit 1 English in the world.docx
Bài tập unit 1 English in the world.docx
 
一比一原版(LBS毕业证)伦敦商学院毕业证成绩单专业办理
一比一原版(LBS毕业证)伦敦商学院毕业证成绩单专业办理一比一原版(LBS毕业证)伦敦商学院毕业证成绩单专业办理
一比一原版(LBS毕业证)伦敦商学院毕业证成绩单专业办理
 
原版仿制(uob毕业证书)英国伯明翰大学毕业证本科学历证书原版一模一样
原版仿制(uob毕业证书)英国伯明翰大学毕业证本科学历证书原版一模一样原版仿制(uob毕业证书)英国伯明翰大学毕业证本科学历证书原版一模一样
原版仿制(uob毕业证书)英国伯明翰大学毕业证本科学历证书原版一模一样
 
7 Best Cloud Hosting Services to Try Out in 2024
7 Best Cloud Hosting Services to Try Out in 20247 Best Cloud Hosting Services to Try Out in 2024
7 Best Cloud Hosting Services to Try Out in 2024
 
1比1复刻(bath毕业证书)英国巴斯大学毕业证学位证原版一模一样
1比1复刻(bath毕业证书)英国巴斯大学毕业证学位证原版一模一样1比1复刻(bath毕业证书)英国巴斯大学毕业证学位证原版一模一样
1比1复刻(bath毕业证书)英国巴斯大学毕业证学位证原版一模一样
 
办理毕业证(UPenn毕业证)宾夕法尼亚大学毕业证成绩单快速办理
办理毕业证(UPenn毕业证)宾夕法尼亚大学毕业证成绩单快速办理办理毕业证(UPenn毕业证)宾夕法尼亚大学毕业证成绩单快速办理
办理毕业证(UPenn毕业证)宾夕法尼亚大学毕业证成绩单快速办理
 
成绩单ps(UST毕业证)圣托马斯大学毕业证成绩单快速办理
成绩单ps(UST毕业证)圣托马斯大学毕业证成绩单快速办理成绩单ps(UST毕业证)圣托马斯大学毕业证成绩单快速办理
成绩单ps(UST毕业证)圣托马斯大学毕业证成绩单快速办理
 
Meet up Milano 14 _ Axpo Italia_ Migration from Mule3 (On-prem) to.pdf
Meet up Milano 14 _ Axpo Italia_ Migration from Mule3 (On-prem) to.pdfMeet up Milano 14 _ Axpo Italia_ Migration from Mule3 (On-prem) to.pdf
Meet up Milano 14 _ Axpo Italia_ Migration from Mule3 (On-prem) to.pdf
 
制作毕业证书(ANU毕业证)莫纳什大学毕业证成绩单官方原版办理
制作毕业证书(ANU毕业证)莫纳什大学毕业证成绩单官方原版办理制作毕业证书(ANU毕业证)莫纳什大学毕业证成绩单官方原版办理
制作毕业证书(ANU毕业证)莫纳什大学毕业证成绩单官方原版办理
 
一比一原版(SLU毕业证)圣路易斯大学毕业证成绩单专业办理
一比一原版(SLU毕业证)圣路易斯大学毕业证成绩单专业办理一比一原版(SLU毕业证)圣路易斯大学毕业证成绩单专业办理
一比一原版(SLU毕业证)圣路易斯大学毕业证成绩单专业办理
 
APNIC Foundation, presented by Ellisha Heppner at the PNG DNS Forum 2024
APNIC Foundation, presented by Ellisha Heppner at the PNG DNS Forum 2024APNIC Foundation, presented by Ellisha Heppner at the PNG DNS Forum 2024
APNIC Foundation, presented by Ellisha Heppner at the PNG DNS Forum 2024
 
guildmasters guide to ravnica Dungeons & Dragons 5...
guildmasters guide to ravnica Dungeons & Dragons 5...guildmasters guide to ravnica Dungeons & Dragons 5...
guildmasters guide to ravnica Dungeons & Dragons 5...
 
重新申请毕业证书(RMIT毕业证)皇家墨尔本理工大学毕业证成绩单精仿办理
重新申请毕业证书(RMIT毕业证)皇家墨尔本理工大学毕业证成绩单精仿办理重新申请毕业证书(RMIT毕业证)皇家墨尔本理工大学毕业证成绩单精仿办理
重新申请毕业证书(RMIT毕业证)皇家墨尔本理工大学毕业证成绩单精仿办理
 
[HUN][hackersuli] Red Teaming alapok 2024
[HUN][hackersuli] Red Teaming alapok 2024[HUN][hackersuli] Red Teaming alapok 2024
[HUN][hackersuli] Red Teaming alapok 2024
 
可查真实(Monash毕业证)西澳大学毕业证成绩单退学买
可查真实(Monash毕业证)西澳大学毕业证成绩单退学买可查真实(Monash毕业证)西澳大学毕业证成绩单退学买
可查真实(Monash毕业证)西澳大学毕业证成绩单退学买
 

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

  • 1. React Native Munich Meetup May 2017 REDUX SAGA Nacho Martín @nacmartin
  • 2. 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.
  • 3. Roadmap: • Are Sagas indispensable? • Do they have a strong theoretical background? • ES6 Generators • Sagas
  • 4. Maybe you don’t need redux-saga
  • 5.
  • 6.
  • 7. What we need dispatch({type: ‘API_REQUEST’}) Reducer
  • 8. What we need dispatch({type: ‘API_REQUEST’}) Reducer state = {…state, requesting : true } Store
  • 9. What we need dispatch({type: ‘API_REQUEST’}) Reducer Store Middleware ⚙
  • 10. What we need dispatch({type: ‘API_REQUEST’}) Reducer Store Middleware dispatch({type: ‘API_REQUEST_SUCCESS’, data}) ⚙
  • 11. What we need dispatch({type: ‘API_REQUEST’}) Reducer state = {…state, data : action.data } Store Middleware dispatch({type: ‘API_REQUEST_SUCCESS’, data}) ⚙
  • 12. What we need dispatch({type: ‘API_REQUEST’}) Reducer state = {…state, showError: true } Store Middleware dispatch({type: ‘API_REQUEST_ERROR’}) ⚙
  • 13. What we need dispatch({type: ‘API_REQUEST’}) Reducer state = {…state, showError: true } Store Middleware dispatch({type: ‘API_REQUEST_ERROR’}) ⚙ Side effects Pure code
  • 14. 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())
  • 15. Redux-thunk The lib has only 14 lines of code If you don’t need more, stick with it But what if you do?
  • 16. If you need more •Redux-saga •Redux-observable •… •Maybe your own in the future?
  • 22. But nowadays A long and complicated story with many details e.g. “my neighbor told me the saga of his divorce again”
  • 23. Originally in C.S. Héctor García-Molina
  • 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 ( ) Multiple workflows, each providing compensating actions for every step of the workflow where it can fail
  • 27. But nowadays A process manager used to orchestrate complex operations.
  • 28. But nowadays A process manager used to orchestrate complex operations. A library to manage side-effects. And in redux-saga:
  • 30. 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())
  • 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()) { value: 'one', done: false }
  • 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())
  • 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()) { value: 'two', done: false }
  • 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())
  • 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()) { value: 'three', done: false }
  • 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())
  • 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()) { value: undefined, done: true }
  • 41. 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))
  • 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)) heaven of data
  • 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))
  • 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)) { value: '1st 0’, done: false }
  • 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))
  • 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)) x = 0 + 1
  • 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))
  • 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)) { value: '2nd 1’, done: false }
  • 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))
  • 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)) x = 1 + 20
  • 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))
  • 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)) { value: '3rd 21’, done: false }
  • 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))
  • 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)) x = 21 + 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))
  • 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)) { value: '4th 321’, done: false }
  • 62. 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))
  • 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)) { value: 0, done: false }
  • 65. 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 }
  • 66. 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 }
  • 67. 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 }
  • 68. 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; };
  • 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. 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)
  • 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. 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={playSound(soundManager, ‘buzz’)} /> ); } }
  • 83. Naive solution class MoveButton extends Component { render() { return ( <Button onPress={playSound(soundManager, ‘buzz’)} /> ); } } But where does soundManager come from?
  • 84. Naive solution class MoveButton extends Component { render() { return ( <Button onPress={this.props.dispatch(playSound(‘buzz’))} /> ); } }
  • 85. Naive solution class 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 :_(
  • 86. Naive solution class 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?
  • 87. Naive solution class 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
  • 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 What if we want to play a sound when the opponent moves too and we receive her movements from a websocket?
  • 90. 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)) } //... }
  • 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)) } //... } What has to do connectSocket with soundManager? We are forced to do this because we don’t know anything better :_(
  • 92. Using sagas import { take } from 'redux-saga/effects' export default function* rootSaga() { const action = yield take(Constants.PLAY_SOUND_REQUEST) console.log(action.sound) }
  • 93. 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) }
  • 94. 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
  • 95. 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) }
  • 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) } { CALL: { fn: soundManager.play, args: ['buzz'] } } Call returns an object
  • 97. 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: []}}
  • 98. 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) }
  • 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) } Will take 1 action, play a sound, and terminate
  • 100. 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) } }
  • 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) } } Will take every action
  • 102. 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) }
  • 103. 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'}) } }
  • 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'}) } } put dispatches a new action
  • 105. 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())
  • 106. 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
  • 107. 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})
  • 108. 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
  • 109. takeLatest import { takeLatest } from 'redux-saga/effects' function* watchFetchData() { yield takeLatest('FETCH_REQUESTED', fetchData) }
  • 110. takeLatest import { takeLatest } from 'redux-saga/effects' function* watchFetchData() { yield takeLatest('FETCH_REQUESTED', fetchData) } Ensure that only the last fetchData will be running
  • 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(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 //... }
  • 112. 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
  • 113. 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) } }
  • 114. 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) } } }
  • 115. 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) } } }
  • 116. 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) } } }
  • 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. Parallel (all) export default function* rootSaga() { yield all([ playSounds(), watchJoinGame(), //… ]) } 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
  • 124. Connect + listen from socket import { 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
  • 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. 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 }) }
  • 127. Summary •Take (takeEvery, takeLast) •Call •Put •Fork & Spawn •Cancel •Channels & EventChannels •All & race •Select •Delay