Skip to content

Instantly share code, notes, and snippets.

@2j2e
Last active February 29, 2016 11:27
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save 2j2e/ed88c8767c69f06586b0 to your computer and use it in GitHub Desktop.
Save 2j2e/ed88c8767c69f06586b0 to your computer and use it in GitHub Desktop.
AltJS async action & store enhancers
const asyncAction = (target, action, descriptor) => {
// Define async handler action names
let actionBegin = action + 'Begin';
let actionEnd = action + 'End';
let actionFailure = action + 'Failure';
// Action should explicitly call dispatch
let actionFunc = x => dispatch => dispatch(x);
// Async handlers methods
target[actionBegin] = actionFunc;
target[actionEnd] = actionFunc;
target[actionFailure] = actionFunc;
// Action wrapper
const wrapper = function () {
let args = arguments;
return (dispatch) => {
this[actionBegin]();
let response;
// Run parent action promise and handle it in appropriate way
return descriptor.value.apply(this, args)
.then(res => {
response = res;
return res.json();
})
.then(json => {
if (response.ok) {
dispatch(json);
} else {
// handle server side error (validation, etc.)
let issue = {
generalErrors: json.__all__,
errors: json.errors
};
this[actionFailure](issue);
}
this[actionEnd]();
return response;
}, () => {
// handle unexpected error (network, etc.)
response.text()
.then(text => {
let issue = {
generalErrors: [text]
};
this[actionFailure](issue);
this[actionEnd]();
});
return response;
});
}
};
// Replace method with wrapper
return Object.assign({}, descriptor, {value: wrapper});
};
export default asyncAction;
const asyncStoreEnhancer = (Store, ActionsClass, actions) => {
class AsyncStore extends Store {
constructor(...args) {
super(...args);
this.state = this.state || {};
this.state.aux = {};
this.bindListeners(listeners);
this.exportPublicMethods(publicMethods);
}
}
let listeners = {};
let publicMethods = {};
// Supported async actions
let postFixes = ['Begin', 'Failure', 'End'];
actions.forEach(action => {
postFixes.forEach(postfix => {
// Camel case action name
let capitilizedAction = action.charAt(0).toUpperCase() + action.slice(1);
// Handler name (onLoadBegin)
let handlerName = `on${capitilizedAction}${postfix}`;
// Source action name (loadBegin)
let actionName = `${action}${postfix}`;
if (postfix == 'Begin') {
AsyncStore.prototype[handlerName] = function () {
this.setState({
aux: {[action]: {isFetching: true}}
})
};
}
if (postfix == 'End') {
AsyncStore.prototype[handlerName] = function () {
this.setState({
aux: {[action]: {isFetching: false}}
})
};
}
if (postfix == 'Failure') {
AsyncStore.prototype[handlerName] = function () {
this.setState({
aux: {[action]: {isFetching: false}}
})
};
}
listeners[handlerName] = ActionsClass[actionName];
// fetching method helper like isLoadFetching()
publicMethods[`is${capitilizedAction}Fetching`] = function () {
return this.getState().aux && this.getState().aux[action] && this.getState().aux[action].isFetching;
}
})
});
return AsyncStore;
}
export default asyncStoreEnhancer;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment