Skip to content

Instantly share code, notes, and snippets.

@sayjeyhi
Last active November 4, 2019 13:51
Show Gist options
  • Save sayjeyhi/b4d2e1d127411fa2a9a5cdd76a9ae404 to your computer and use it in GitHub Desktop.
Save sayjeyhi/b4d2e1d127411fa2a9a5cdd76a9ae404 to your computer and use it in GitHub Desktop.
Advancerd Saga samples
/* 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)
])
}
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});
}
}
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;
}
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);
}
// 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