Last active
February 26, 2019 21:12
-
-
Save bradennapier/af12f1ee3b31ccc934e5011ec1f43897 to your computer and use it in GitHub Desktop.
Saga Defer Example
This file contains hidden or 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 _ from 'lodash'; | |
import { | |
take, | |
call, | |
put, | |
fork, | |
spawn, | |
delay, | |
cancel | |
} from 'redux-saga/effects'; | |
import { eventChannel } from 'redux-saga'; | |
import defer from 'sagas/defer'; | |
import connecting from './messages/connecting'; | |
import critical from './messages/critical'; | |
// import { subscribeToMarket } from 'client/wsActions'; | |
let notifications = []; | |
let i = 0; | |
function createNotification(payload) { | |
i += 1; | |
return { | |
key: i, | |
props: {}, | |
...payload, | |
created: Date.now() | |
}; | |
} | |
function actions(n) { | |
return eventChannel(emitter => { | |
n.emit = (type, payload) => emitter({ type, payload }); | |
return () => n.emit('close'); | |
}); | |
} | |
function* removeNotification(n) { | |
const cancelling = []; | |
const nextNotifications = notifications.reduce((arr, notification) => { | |
if (notification.key === n.key) { | |
cancelling.push(n.task); | |
return arr; | |
} | |
arr.push(notification); | |
return arr; | |
}, []); | |
if (cancelling.length) { | |
notifications = nextNotifications; | |
yield put({ | |
type: 'COMMIT_NOTIFICATIONS', | |
notifications | |
}); | |
yield cancel(cancelling); | |
} | |
} | |
const UPDATE_N = Symbol('@@notifications/UPDATE_NOTIFICATION_EVENT'); | |
/** | |
* Each notification has an `emit` function | |
* which will emit events to control the | |
* notification `notification.emit('click')` | |
*/ | |
function* watchNotification(chan, n) { | |
let notification = n; | |
let prevTask; | |
try { | |
while (true) { | |
const { type, payload } = yield take(chan); | |
switch (type) { | |
case UPDATE_N: { | |
notification = payload; | |
break; | |
} | |
case 'click': { | |
if (prevTask && prevTask.isRunning) { | |
yield cancel(prevTask); | |
} | |
if (!notification.onClick && !notification.props.sticky) { | |
prevTask = yield defer(fork, removeNotification, notification); | |
} else if (notification.onClick) { | |
prevTask = yield defer(spawn, notification.onClick, notification); | |
} | |
break; | |
} | |
case 'close': { | |
yield defer(fork, removeNotification, notification); | |
break; | |
} | |
case 'update': { | |
yield defer(fork, handleNotification, { | |
...payload, | |
key: n.key | |
}); | |
break; | |
} | |
} | |
} | |
} finally { | |
if (prevTask && prevTask.isRunning) { | |
yield cancel(prevTask); | |
} | |
yield call(notification.chan.close); | |
} | |
} | |
/** | |
* For now we are not doing much here, but normalizing to ACCOUNT_LOGOUT | |
* to have a unified simple event pushed. | |
* | |
* We also dispatch the account to logout of for use by the callers if | |
* necessary (so it is not removed before they get it). | |
*/ | |
function* handleNotification(payload) { | |
const currentIndex = payload.key | |
? notifications.findIndex(notification => notification.key === payload.key) | |
: -1; | |
const n = createNotification(payload); | |
if (currentIndex !== -1) { | |
// update notification with given key - we rebuild the | |
// object to ensure rendering can be intelligent if it | |
// has a given notification | |
const currentNotification = notifications[currentIndex]; | |
const nextNotification = _.merge({}, currentNotification, n); | |
notifications.splice(currentIndex, 1, nextNotification); | |
yield call(currentNotification.emit, UPDATE_N, nextNotification); | |
} else { | |
const chan = yield call(actions, n); | |
n.chan = chan; | |
n.task = yield spawn(watchNotification, chan, n); | |
notifications.unshift(n); | |
notifications = notifications.sort((a, b) => | |
a.sticky && b.sticky | |
? a.created - b.created | |
: a.sticky && !b.sticky | |
? 1 | |
: -1 | |
); | |
} | |
yield put({ | |
type: 'COMMIT_NOTIFICATIONS', | |
notifications: notifications.slice() | |
}); | |
} | |
function* handleAdd() { | |
yield put({ | |
type: 'NOTIFICATION', | |
payload: critical.blockedRegion | |
}); | |
yield delay(1000); | |
yield put({ | |
type: 'NOTIFICATION', | |
payload: connecting.connecting | |
}); | |
yield delay(5000); | |
yield put({ | |
type: 'NOTIFICATION', | |
payload: connecting.connected | |
}); | |
} | |
export default function* notificationSaga() { | |
yield fork(handleAdd); | |
while (true) { | |
const action = yield take(['NOTIFICATION']); | |
yield fork(handleNotification, action.payload); | |
} | |
} |
This file contains hidden or 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, cancel } from 'redux-saga/effects'; | |
let nextTickProm; | |
function* deferredExecute(fn, ...args) { | |
if (!nextTickProm) { | |
nextTickProm = Promise.resolve().then(() => { | |
nextTickProm = undefined; | |
}); | |
} | |
yield call(() => nextTickProm); | |
if (typeof fn !== 'function') { | |
return; | |
} | |
return yield call(fn, ...args); | |
} | |
export default function* defer(effect, ...args) { | |
if ([cancel].includes(effect) || !effect) { | |
// deferred effects without fnDescriptor | |
// footprint | |
yield call(deferredExecute); | |
if (!effect) { | |
return; | |
} | |
return yield effect(...args); | |
} | |
return yield effect(deferredExecute, ...args); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment