usher
Version:
Simple DSL for composing decision workflows for AWS Simple Workflow
225 lines (167 loc) • 7.14 kB
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: decider/tasks/accumulator.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: decider/tasks/accumulator.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/*!
* Usher
* Copyright(c) 2016 meltmedia <mike@meltmedia.com>
*/
'use strict';
var util = require('util'),
winston = require('winston'),
_ = require('lodash'),
async = require('async'),
WorkflowExecution = require('../execution'),
Context = require('../context'),
Task = require('./task'),
STATUS = require('./status');
module.exports = Accumulator;
/**
* The Accumulator loop executes the `fragment` until the `resultsFn` returns an empty array,
* the results of each execution are then pushed into an array via the `resultsFn` for use by other tasks.
*
* @constructor
*/
function Accumulator(name, deps, fragment, resultsFn, options) {
if (!(this instanceof Accumulator)) {
return new Accumulator(name, deps, fragment, resultsFn, options);
}
Task.apply(this, Array.prototype.slice.call(arguments));
this.fragment = fragment;
if (!_.isFunction(resultsFn)) {
throw new Error('You must provide a function to indicate when the accumulator is complete');
}
this.resultsFn = resultsFn;
this.options = options || {};
}
util.inherits(Accumulator, Task);
Accumulator.prototype.execute = function execute(ctx, done) {
var resolvedName = ctx.namespace ? ctx.namespace + '-' + this.name : this.name,
input = ctx.input(this),
state = ctx.lastState(this),
self = this,
executionContexts;
// Make sure we have a state
state = state ? state : { currentIndex: 0 };
function evaluateState() {
executionContexts = [];
for (var i = 0; i <= state.currentIndex; i++) {
// Build the execution context for the current iteration
// Depending on the index, this could be the first of our iterations, or
// picking back up a previously run iteration
var currentExecution = {
execution: new WorkflowExecution(self.fragment.sequencedTasks()),
context: new Context(ctx.decisionTask, self.name + '-' + i, ctx.localVariables)
};
executionContexts.push(currentExecution);
}
// Execute every iteration we've scheduled so far to see what state they are in
// Does this need to be .mapSeries?
async.reduce(executionContexts, [], function (memo, item, next) {
// Store the previous result for use by later iterations
input._variables.previousResult = _.get(_.last(memo), 'results', null);
item.context.setWorkflowInput(input);
item.execution.execute(item.context, function (err) {
if (err) {
return next(err);
}
if (item.context.failed()) {
return next(new Error());
}
// If current execution has finished running, check to see if it resolves our 'done' state
if (item.context.done()) {
// Get the results of this iteration
var results = self.resultsFn.call(self, item.context.results);
// We are done, we can mark our task resolved
if (!_.isArray(results) || results.length < 1) {
memo.push({
outstanding: false,
done: true,
results: results
});
return next(null, memo); // The results of this execution did resolve the accumulators 'done' state
} else {
memo.push({
outstanding: false,
done: false,
results: results
});
return next(null, memo); // The results of this execution did NOT resolve the accumulators 'done' state
}
}
// This execution is still outstanding
memo.push({
outstanding: true,
done: false,
results: []
});
return next(null, memo);
});
// Once the states of our current iterations are complete, we evaluate the state of our task.
// From here we can still be outstanding (waiting for task to finish), resolved (all work is done), or
// schedule new tasks.
}, function (err, results) {
if (err) {
winston.log('error', 'An problem occured in the accumulator: %s, failing due to: ', resolvedName, err.stack);
return done(STATUS.mask('failed'));
}
var isOutstanding = _.some(results, 'outstanding'),
isDone = _.some(results, 'done');
if (isOutstanding && isDone) {
// This should never be true, if so, we need to let someone know
winston.log('error', 'Invalid state in accumulator: %s, both \'done\' and \'outstanding\' state are currently true.', resolvedName);
}
// Still more work to be done on existing iterations
if (isOutstanding) {
return done(STATUS.mask('outstanding'));
}
// All iterations are complete and our 'done' criteria has been meet
if (isDone) {
// Make sure the results are available as a single array
ctx.addResult(self, _.reduce(results, function (result, item) {
return result.concat(item.results);
}, []));
return done(STATUS.mask('complete', 'resolved'));
}
// Not done yet, schedule another iteration
state.currentIndex++;
// Save our state
ctx.saveState(self, state);
// Our input can change based on our current state
input = ctx.input(self);
// Evaluate the newly scheduled iteration
evaluateState();
});
}
// Run through the current state of the accumulator and set our state and schedule any work that needs to be done
evaluateState();
};
</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Index</a></h2><h3>Classes</h3><ul><li><a href="Accumulator.html">Accumulator</a></li><li><a href="ActivityPoller.html">ActivityPoller</a></li><li><a href="ActivityTask.html">ActivityTask</a></li><li><a href="DecisionPoller.html">DecisionPoller</a></li><li><a href="Fragment.html">Fragment</a></li><li><a href="Loop.html">Loop</a></li><li><a href="Usher.html">Usher</a></li><li><a href="WhileLoop.html">WhileLoop</a></li><li><a href="WorkflowVersion.html">WorkflowVersion</a></li></ul>
</nav>
<br clear="both">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.3.0-alpha5</a> on Mon Sep 12 2016 14:59:00 GMT-0700 (MST)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>