Last active
November 4, 2019 13:51
-
-
Save sayjeyhi/b4d2e1d127411fa2a9a5cdd76a9ae404 to your computer and use it in GitHub Desktop.
Advancerd Saga samples
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* eslint-disable no-constant-condition */ | |
import { take, put, call, fork, select, all } from 'redux-saga/effects' | |
import { api, history } from '../services' | |
import * as actions from '../actions' | |
import { getUser, getRepo, getStarredByUser, getStargazersByRepo } from '../reducers/selectors' | |
// each entity defines 3 creators { request, success, failure } | |
const { user, repo, starred, stargazers } = actions | |
// url for first page | |
// urls for next pages will be extracted from the successive loadMore* requests | |
const firstPageStarredUrl = login => `users/${login}/starred` | |
const firstPageStargazersUrl = fullName => `repos/${fullName}/stargazers` | |
/***************************** Subroutines ************************************/ | |
// resuable fetch Subroutine | |
// entity : user | repo | starred | stargazers | |
// apiFn : api.fetchUser | api.fetchRepo | ... | |
// id : login | fullName | |
// url : next page url. If not provided will use pass id to apiFn | |
function* fetchEntity(entity, apiFn, id, url) { | |
yield put( entity.request(id) ) | |
const {response, error} = yield call(apiFn, url || id) | |
if(response) | |
yield put( entity.success(id, response) ) | |
else | |
yield put( entity.failure(id, error) ) | |
} | |
// yeah! we can also bind Generators | |
export const fetchUser = fetchEntity.bind(null, user, api.fetchUser) | |
export const fetchRepo = fetchEntity.bind(null, repo, api.fetchRepo) | |
export const fetchStarred = fetchEntity.bind(null, starred, api.fetchStarred) | |
export const fetchStargazers = fetchEntity.bind(null, stargazers, api.fetchStargazers) | |
// load user unless it is cached | |
function* loadUser(login, requiredFields) { | |
const user = yield select(getUser, login) | |
if (!user || requiredFields.some(key => !user.hasOwnProperty(key))) { | |
yield call(fetchUser, login) | |
} | |
} | |
// load repo unless it is cached | |
function* loadRepo(fullName, requiredFields) { | |
const repo = yield select(getRepo, fullName) | |
if (!repo || requiredFields.some(key => !repo.hasOwnProperty(key))) | |
yield call(fetchRepo, fullName) | |
} | |
// load next page of repos starred by this user unless it is cached | |
function* loadStarred(login, loadMore) { | |
const starredByUser = yield select(getStarredByUser, login) | |
if (!starredByUser || !starredByUser.pageCount || loadMore) | |
yield call( | |
fetchStarred, | |
login, | |
starredByUser.nextPageUrl || firstPageStarredUrl(login) | |
) | |
} | |
// load next page of users who starred this repo unless it is cached | |
function* loadStargazers(fullName, loadMore) { | |
const stargazersByRepo = yield select(getStargazersByRepo, fullName) | |
if (!stargazersByRepo || !stargazersByRepo.pageCount || loadMore) | |
yield call( | |
fetchStargazers, | |
fullName, | |
stargazersByRepo.nextPageUrl || firstPageStargazersUrl(fullName) | |
) | |
} | |
/******************************************************************************/ | |
/******************************* WATCHERS *************************************/ | |
/******************************************************************************/ | |
// trigger router navigation via history | |
function* watchNavigate() { | |
while(true) { | |
const {pathname} = yield take(actions.NAVIGATE) | |
yield history.push(pathname) | |
} | |
} | |
// Fetches data for a User : user data + starred repos | |
function* watchLoadUserPage() { | |
while(true) { | |
const {login, requiredFields = []} = yield take(actions.LOAD_USER_PAGE) | |
yield fork(loadUser, login, requiredFields) | |
yield fork(loadStarred, login) | |
} | |
} | |
// Fetches data for a Repo: repo data + repo stargazers | |
function* watchLoadRepoPage() { | |
while(true) { | |
const {fullName, requiredFields = []} = yield take(actions.LOAD_REPO_PAGE) | |
yield fork(loadRepo, fullName, requiredFields) | |
yield fork(loadStargazers, fullName) | |
} | |
} | |
// Fetches more starred repos, use pagination data from getStarredByUser(login) | |
function* watchLoadMoreStarred() { | |
while(true) { | |
const {login} = yield take(actions.LOAD_MORE_STARRED) | |
yield fork(loadStarred, login, true) | |
} | |
} | |
function* watchLoadMoreStargazers() { | |
while(true) { | |
const {fullName} = yield take(actions.LOAD_MORE_STARGAZERS) | |
yield fork(loadStargazers, fullName, true) | |
} | |
} | |
export default function* root() { | |
yield all([ | |
fork(watchNavigate), | |
fork(watchLoadUserPage), | |
fork(watchLoadRepoPage), | |
fork(watchLoadMoreStarred), | |
fork(watchLoadMoreStargazers) | |
]) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function * loadUser () { | |
try { | |
// [1] | |
const user = yield call (getUser); | |
// [2] | |
yield put ({type: 'FETCH_USER_SUCCESS', payload: user}); | |
} catch (error) { | |
// [3] | |
yield put ({type: 'FETCH_FAILED', error}); | |
} | |
} | |
function * loadDashboardSequenced () { | |
try { | |
// [1] | |
yield take ('FETCH_USER_SUCCESS'); | |
// [2] | |
const user = yield select (state => state.user); | |
// [3] | |
const departure = yield call (loadDeparture, user); | |
// [4] | |
const flight = yield call (loadFlight, departure.flightID); | |
const forecast = yield call (loadForecast, departure.date); | |
// [5] | |
yield put ({ | |
type: 'FETCH_DASHBOARD_SUCCESS', | |
payload: {forecast, flight, departure} | |
}); | |
} catch (error) { | |
// [6] | |
yield put ({ | |
type: 'FETCH_FAILED', | |
error: error.message | |
}); | |
} | |
} | |
function * loadDashboardNonSequenced () { | |
try { | |
// Waiting for redux action | |
yield take ('FETCH_USER_SUCCESS'); | |
// Search for user information in store | |
const user = yield select (getUserFromState); | |
// Search shipping information | |
const departure = yield call (loadDeparture, user); | |
// HERE THE MAGIC HAPPENS 🎉🎉🎉 | |
const [flight, forecast] = yield [ | |
call (loadFlight, departure.flightID), | |
call (loadForecast, departure.date) | |
]; | |
// Returning values to our application | |
yield put ({ | |
type: 'FETCH_DASHBOARD_2_SUCCESS', | |
payload: {departure, flight, forecast} | |
}); | |
} catch (error) { | |
yield put ({type: 'FETCH_FAILED', error: error.message}); | |
} | |
} | |
function * rootSaga () { | |
yield [ | |
fork (loadUser), | |
takeLatest ('LOAD_DASHBOARD', loadDashboardSequenced), | |
takeLatest ('LOAD_DASHBOARD2' loadDashboardNonSequenced) | |
]; | |
} | |
function * loadDashboardNonSequencedNonBlocking () { | |
try { | |
// Waiting for redux action | |
yield take ('FETCH_USER_SUCCESS'); | |
// Search for user information in store | |
const user = yield select (getUserFromState); | |
// Search shipping information | |
const departure = yield call (loadDeparture, user); | |
// Dispatch an action to update the UI | |
yield put ({type: 'FETCH_DASHBOARD3_SUCCESS', payload: {departure,}}); | |
// Dispatch the action required for the Weather and Flight saga to begin ... | |
// We can pass an object in the put | |
yield put effect ({type: 'FETCH_DEPARTURE3_SUCCESS', departure}); | |
} catch (error) { | |
yield put ({type: 'FETCH_FAILED', error: error.message}); | |
} | |
} | |
// ==================== | |
// Flight Saga | |
// ==================== | |
function * isolatedFlight () { | |
try { | |
/ * departure will get the object sent by the put effect * / | |
const departure = yield take ('FETCH_DEPARTURE_3_SUCCESS'); | |
const flight = yield call (loadFlight, departure.flightID); | |
yield put ({type: 'FETCH_DASHBOARD_3_SUCCESS', payload: {flight}}); | |
} catch (error) { | |
yield put ({type: 'FETCH_FAILED', error: error.message}); | |
} | |
} | |
// ==================== | |
// Forecast Saga | |
// ==================== | |
function * isolatedForecast () { | |
try { | |
/ * departure will get the object sent by the put effect * / | |
const departure = yield take ('FETCH_DEPARTURE_3_SUCCESS'); | |
const forecast = yield call (loadForecast, departure.date); | |
yield put ({type: 'FETCH_DASHBOARD_3_SUCCESS', payload: {forecast,}}); | |
} catch (error) { | |
yield put ({type: 'FETCH_FAILED', error: error.message}); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { race, cancel, take, fork, all, put, select } from 'redux-saga/effects'; | |
export function* fetchDataForPage() { | |
const userId = yield select(getUserId); | |
try { | |
if (!userId) { | |
throw new Error('No user - aborting'); | |
} | |
const apiCalls = yield all([ | |
fork(requestAndPut, [requestData, 'current', userId, MAX_CURRENT], requestOneActionCreator), | |
fork(requestAndPut, [requestData, 'past', userId, MAX_PAST], requestTwoActionCreator) | |
]); | |
const { cancelSagas, success } = yield race({ | |
cancelSagas: take(ON_PAGE_CHANGED), | |
success: waitForTheseRequestsToFinish() | |
}); | |
// If cancelSagas wins the race, cancel all of our sagas | |
if (cancelSagas) { | |
for (let i = 0; i < apiCalls.length; i++) { | |
yield cancel(apiCalls[i]); | |
} | |
} | |
else { | |
return success; | |
} | |
} | |
catch (e) { | |
yield put({ type: ON_PAGE_MOUNT_ERROR, payload: e }); | |
} | |
} | |
// Returns true when all of these action types are fired | |
function* waitForTixRequestsToFinish() { | |
yield all([ | |
take(ON_REQUEST_ONE_SUCCESS), | |
take(ON_REQUEST_TWO_SUCCESS) | |
]); | |
return true; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { call, put, takeEvery } from 'redux-saga/effects'; | |
import { | |
API_BUTTON_CLICK, | |
API_BUTTON_CLICK_SUCCESS, | |
API_BUTTON_CLICK_ERROR, | |
} from './actions/consts'; | |
import { getDataFromAPI } from './api'; | |
export function* apiSideEffect(action) { | |
try { | |
const data = yield call(getDataFromAPI); | |
yield put({ type: API_BUTTON_CLICK_SUCCESS, payload: data }); | |
} catch (e) { | |
yield put({ type: API_BUTTON_CLICK_ERROR, payload: e.message }); | |
} | |
} | |
// the 'watcher' - on every 'API_BUTTON_CLICK' action, run our side effect | |
export function* apiSaga() { | |
yield takeEvery(API_BUTTON_CLICK, apiSideEffect); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Watcher | |
export function* onPageInit() { | |
yield takeLatest(ON_PAGE_MOUNT, fetchDataForPage); | |
} | |
// Saga | |
export function* fetchDataForPage() { | |
// Get any dependencies (from your store, or locally, etc) | |
const userId = yield select(getUserId); | |
const dependency = 42; | |
try { | |
// error checking | |
if (!userId) { | |
throw new Error('No user - aborting'); | |
} | |
// run all fetch requests in parallel | |
yield all([ | |
fork(requestAndPut, [firstRequest, userId], firstRequestActionCreator), | |
fork(requestAndPut, [secondRequest, userId, dependency], secondRequestActionCreator), | |
fork(requestAndPut, [thirdRequest, userId], thirdRequestActionCreator) | |
]); | |
} | |
catch (e) { | |
// error handling | |
yield put({ type: ON_PAGE_MOUNT_ERROR, payload: e }); | |
} | |
} | |
// Helpers | |
function* requestAndPut(requestParameters, actionCreator) { | |
const result = yield call(...requestParameters); | |
yield put(actionCreator(result)); | |
} | |
const firstRequest = (userId) => { | |
return request(`${Config.api.base}/foo/bar`, { userId }) | |
.then(resp => resp.json()); | |
} | |
const firstRequestActionCreator = data => ({ | |
type: FETCH_FIRST_REQUEST_SUCCESS, | |
payload: data | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment