Skip to content

Instantly share code, notes, and snippets.

@danielnieto
Created February 10, 2020 23:25
Show Gist options
  • Save danielnieto/bb74d50c43023c39574303c9d9bb7989 to your computer and use it in GitHub Desktop.
Save danielnieto/bb74d50c43023c39574303c9d9bb7989 to your computer and use it in GitHub Desktop.
a hacked flourine version to report execution times
require('colors')
Class('Flow').includes(CustomEventSupport, NodeSupport)({
prototype: {
/**
holder for the name of the flow
@property name <public> [String]
**/
name: '',
/**
initializer for the class
@property init <public> [Function]
@argument config <optional> [Object]
**/
init: function init(config) {
Object.keys(config).forEach(function (property) {
this[property] = config[property];
}, this);
},
/**
Define a step in the flow. this is a dsl like starter because returs functions.
flowInstance.step('name')(function (step) {...});
@property step <public> [Function]
@argument name <required> [String]
@return Function a closure to the created step that you can use either to finish the step
creation or define more metadata.
**/
step: function step(name) {
var flow, dependencies, nodeFactory;
flow = this;
dependencies = [];
nodeFactory = function nodeFactory(code, errorCode) {
var node = new FlowNode({
name: name,
code: code,
errorCode: errorCode,
dependencies: dependencies
});
flow.appendChild(node);
dependencies.forEach(function dependenciesIterator(dependencyName) {
flow.bind(dependencyName, function () {
flow.runNode(node);
});
});
flow.runNode(node);
};
nodeFactory.dependsOn = function dependsOn() {
dependencies = Array.prototype.slice.call(arguments, 0);
return nodeFactory;
};
return nodeFactory;
},
/**
Checks for a node candidate to run, in order to run this node
all its preconditions must be met (dependencies completed). after this steps
pass it creates the appropiete data that will pass to the node and finally
it executes the node passing a capability object to this step that contains
the data from its dependencies.
@property runNode <public> [Function]
@argument node <required> [FlowNode] the node that you want to execute.
**/
runNode: function runNode(node) {
var dependencies = [].concat(node.dependencies);
var hasRejectedDependency = false;
var fulfilledDependencies = dependencies.filter(function (dependencyName) {
if (this.hasOwnProperty(dependencyName) === false) {
return;
}
if (this[dependencyName].isFulfilled === true || this[dependencyName].isRejected === true) {
if (this[dependencyName].isRejected === true) {
hasRejectedDependency = true;
}
return true;
}
}, this);
if (dependencies.length === fulfilledDependencies.length) {
var nodeLikeObject = {
success: function nodeSuccess(data) {
node.fulfill(data);
},
fail: function nodeFail(error) {
node.reject(error);
}
};
if (hasRejectedDependency === false) {
nodeLikeObject.data = {};
fulfilledDependencies.forEach(function fulfilledDependenciesIterator(dependencyName) {
nodeLikeObject.data[dependencyName] = this[dependencyName].data;
}, this);
}
else {
nodeLikeObject.errors = {};
fulfilledDependencies.forEach(function (dependencyName) {
nodeLikeObject.errors[dependencyName] = this[dependencyName].error;
}, this);
}
node.run(nodeLikeObject);
}
},
/**
A flow basically is a graph which implies that the garbage collector will not be able
to clear this objects if they are no longer needed by the developer.
this method takes care of clearing those references and gives the developer full control
of when to discard the flow.
@property destroy <public> [Function]
**/
destroy: function destroy() {
this.children.forEach(function (child) {
child.destroy();
});
return null;
},
/**
This is a utility method to introspect a flow. flow use case is for when a complex sequence
needs to be solved and its very hard to solve it by simpler programming constructs, so mapping
this sequences is hard, this method dumps the flow into a dot directed graph to be visualized.
@property toDot <public> [Function]
@return string The actual dot string that represents this flow graph.
**/
toDot: function toDot() {
var dotGraph = [];
dotGraph.push("digraph " + this.name + " {");
this.children.forEach(function (step) {
dotGraph.push(step.name);
step.dependencies.forEach(function (dependencyName) {
dotGraph.push(dependencyName + " -> " + step.name);
});
});
dotGraph.push("}");
console.debug("Put this in a file and run this in a terminal: dot -Tsvg yourDotFile > graph.svg");
return dotGraph.join("\n");
}
}
});
Class('FlowNode').includes(NodeSupport)({
prototype: {
name: '',
data: undefined,
error: undefined,
isFulfilled: false,
isRejected: false,
startTime: undefined,
code: function () { throw 'no code'; },
errorCode: undefined,
init: function (config) {
Object.keys(config).forEach(function (property) {
this[property] = config[property];
}, this);
},
run: function (nodeLikeObject) {
var node = this;
this.startTime = new Date();
if (nodeLikeObject.hasOwnProperty('data') === true) {
setTimeout(function runFlowNode() {
node.code(nodeLikeObject);
}, 0);
}
else if (typeof this.errorCode !== 'undefined') {
setTimeout(function runFlowNodeError() {
node.errorCode(nodeLikeObject);
}, 0);
}
},
fulfill: function (data) {
this.data = data;
this.isFulfilled = true;
var executionTime = (new Date() - this.startTime)/1000;
console.log(`STEP SUCCESS: ${this.name} took ${executionTime}s`);
if (this.parent === null) {
return;
}
this.parent.dispatch(this.name);
},
reject: function (error) {
this.error = error;
this.isRejected = true;
var executionTime = (new Date() - this.startTime)/1000;
console.log(`STEP FAILED: ${this.name} took ${executionTime}`);
if (this.parent === null) {
return;
}
this.parent.dispatch(this.name);
this.parent.dispatch('reject', {
data: { node: this, error: error }
});
},
destroy: function destroy() {
this.parent.removeChild(this);
return null;
}
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment