ganglion-impulse
Version:
A JavaScript library that transmits impulses from a central ganglion, triggering chain of actions.
106 lines (94 loc) • 3.65 kB
JavaScript
let arrayOrSingle = function (data) {
return Array.isArray(data) && data.length === 1 ? data[0] : data;
};
let callNextAction = function (actions, context, data, options) {
// if the previous action or hook has set cancelImpulse=true on the context then stop
if (context.cancelImpulse) {
return options.reject({
message: `${context.fibreName} impulse has been cancelled`,
data: arrayOrSingle(data)
});
}
// if there are no more actions return the final action response to the impulse caller
if (!actions.length) {
return options.resolve(arrayOrSingle(data));
}
// assume all actions are concurrent sets
let actionSet = Array.isArray(actions[0]) ? actions[0] : [actions[0]];
// if async actions take longer than callSlowAsyncActionAfter ms, call the onSlowAsyncActionStart hook
let calledSlowAsyncAction = false;
let slowActionTimer = setTimeout(function () {
calledSlowAsyncAction = true;
options.trigger('slowAsyncActionStart', context, true);
}, options.callSlowAsyncActionAfter);
// call all actions in the set
Promise
// wait for all responses to resolve before calling the next action
.all(actionSet.map(action => action.call(context, arrayOrSingle(data))))
// all actions resolved
.then(function (responses) {
// if we called the onSlowAsyncActionStart hook call the onSlowAsyncActionEnd hook
clearTimeout(slowActionTimer);
if (calledSlowAsyncAction) {
options.trigger('slowAsyncActionEnd', context, false);
}
// call the next action
callNextAction(actions.splice(1), context, responses, options);
})
// if one of the promises failed send propigate the failure
.catch(options.reject);
};
let Ganglion = function (options = {}) {
let self = this;
this.impulse = {};
this.options = Object.assign({ context: {}, callSlowAsyncActionAfter: 500 }, options);
let events = {};
this.on = function (event, handler) {
events[event] = events[event] || [];
events[event].push(handler);
};
this.off = function (event, handler) {
if (event in events === false) {
return;
}
events[event].splice(events[event].indexOf(handler), 1);
};
let trigger = function(event, context, ...args) {
if (event in events === false) {
return;
}
events[event].forEach(function (handler) {
handler.apply(context, args);
});
};
this.fibre = this.fiber = function (name, ...actions) {
if (self.impulse[name]) {
throw `${name} fibre has already been defined`;
}
self.impulse[name] = function (...data) {
// make some metadata available on the context
let impulseContext = Object.assign({}, self.options.context, { fiberName: name, fibreName: name });
// emit the beforeImpulse event
trigger('beforeImpulse', impulseContext, data);
return new Promise(function (resolve, reject) {
// start the impulse
callNextAction(actions, impulseContext, data, Object.assign({ resolve, reject, trigger }, options));
}).then(function (responseData) {
// emit the afterImpulse event
trigger('afterImpulse', impulseContext, responseData);
return responseData;
}).catch(function (responseData) {
// emit the afterImpulse event
trigger('error', impulseContext, responseData);
return responseData;
});
};
};
this.addToContext = function (context) {
if (typeof context !== 'object') {
throw 'context data must be an object';
}
this.options.context = Object.assign(this.options.context, context);
};
};
export default Ganglion;