Skip to content

Instantly share code, notes, and snippets.

@Fordi
Created January 31, 2012 23:41
Show Gist options
  • Save Fordi/1713856 to your computer and use it in GitHub Desktop.
Save Fordi/1713856 to your computer and use it in GitHub Desktop.
Javascript Workflow Processor
(function () {
var sayHi = function (token) {
if (!token.progress) token.progress = {};
token.progress.saidHi = (token.progress.saidHi||0) + 1;
//true == simply proceed; false == reject workflow
return true;
},
shakeIt = function (token, next) {
token.progress.shook = (token.progress.shook||0) + 1;
next('right');
},
fakeIt = function (token, next) {
token.progress.faked = (token.progress.faked||0) + 1;
next(true);
},
breakIt = function (token) {
token.progress.broken = (token.progress.broken||0) + 1;
return true;
},
bakeIt = function (token) {
token.progress.baked = (token.progress.baked||0) + 1;
return true;
};
var wf = new WorkFlow( //Workflow
sayHi,
[ //Parallel
[ //Sequence
shakeIt,
{ //Branch
left: fakeIt,
right: [ //Parallel
breakIt,
shakeIt
]
}
],
bakeIt
],
sayHi,
[ sayHi, fakeIt, breakIt ], //Parallel
sayHi
);
var then = +new Date();
wf({ token: 'A' })
.done(function (token) {
var now = +new Date();
console.log("Running time: ", now-then, 'ms');
})
.progress(function (token) {
var prg = token.progress;
console.log([
"Said Hi "+(prg.saidHi||0),
"Shook "+(prg.shook||0),
"Faked "+(prg.faked||0),
"Broken "+(prg.broken||0),
"Baked "+(prg.baked||0)
].join('; '));
});
}());
/**
* WorkFlow processor
*
* @author Bryan Elliott <[email protected]>
* @info
* Create a managed, asynchronous workflow, with a corresponding
* jQuery.Promise. Each step may be synchronous, or asynchronous, meaning
* user-invoked and AJAX calls can be integrated into a sequential workflow.
*
*
* @class WorkFlow(...)
* @info Constructor for creating #WorkFlow objects
* @type #Action A Function, #Parallel, #Branch, or #WorkFlow
* @type #Sequence A Function or an array of #Actions; the first #Action in a #Sequence MUST NOT be a #Branch
* @type #Parallel An array of #Sequences
* @type #Branch An object containing a set of name to #Sequence relationships
* @rest #Action taken together, the sequence of actions to perform
* @return Function An executable #WorkFlow
*
* @function #WorkFlow(token)
* @info An instance of WorkFlow
* @param Object token a state object to be passed through the steps of the #WorkFlow
* @return jQuery.Promise Resolved when the #WorkFlow is either resolved or rejected.
*
* @depends jQuery.Deferred
**/
var WorkFlow = (function () {
var getFnName = !!(function getFnName() {}).name ? function (f) {
if (f.original instanceof Function)
return arguments.callee(f.original());
return f.name;
} : function (f) {
if (f.original instanceof Function)
return arguments.callee(f.original());
return f.toString().replace(/function\s*([^\( ]*)[\s\S]*$/, '$1');
};
var WorkFlow = function () {
var inst = this,
sequence = Array.prototype.slice.call(arguments);
return function Flow(token) {
if (!(this instanceof arguments.callee)) {
return new arguments.callee(token);
}
var flow = this;
flow.sequence = Array.prototype.slice.call(sequence);
flow.processSequence = function () {
WorkFlow.prototype.processSequence.apply(flow, arguments);
};
WorkFlow.prototype.execute.apply(this, arguments);
return this.deferred.promise();
};
};
WorkFlow.prototype.resolve = function (token) {
this.deferred.resolveWith(this, token);
};
WorkFlow.prototype.reject = function (token) {
this.deferred.rejectWith(this, token);
};
WorkFlow.prototype.processSequence = function (sequence, sequenceComplete) {
if (sequence instanceof Function)
sequence = [ sequence ];
var i,
inst = this,
promise = this.deferred.promise(),
methods = [];
if (sequence[0] instanceof Object && !(sequence[0] instanceof Array) && !(sequence[0] instanceof Function))
throw new Error("Branches MUST NOT be the first item in a sequence");
for (i=0; i<sequence.length; i++) (function (item, index) {
var next = function (result) {
inst.deferred.notifyWith(promise, [inst.token]);
inst.lastResult = result;
if (result===false) {
inst.deferred.rejectWith(promise, [inst.token]);
return;
}
index++;
if (index < methods.length) {
methods[index].call();
return;
}
if (index === methods.length) {
inst.deferred.notifyWith(promise, [inst.token]);
sequenceComplete(inst);
return;
}
throw new Error("Workflow reached an illegal state");
};
if (item instanceof Array) {
var parallel = item,
count = parallel.length;
item = function (token, proceed) {
for (var i=0; i<parallel.length; i++) (function (seq, index) {
setTimeout(function () {
inst.processSequence(seq, function () {
if (--count > 0) {
return;
}
proceed();
});
},1);
}(parallel[i], i));
};
}
if (item instanceof Object && !(item instanceof Function)) {
var branch = item,
item = function (token, proceed) {
var seq = branch[inst.lastResult];
if (!seq)
throw new Error("Could not switch to branch \""+inst.lastResult+"\"");
inst.processSequence(seq, function () {
proceed();
});
}
}
methods[index] = function () {
var ret = item.call(inst.deferred, inst.token, next);
if (typeof ret !== 'undefined')
next(ret);
};
}(sequence[i], i));
methods[0]();
};
WorkFlow.prototype.execute = function (token, next) {
if (!!this.token)
throw new Error("Cannot execute workflow already in progress");
this.token = token || {};
this.deferred = new jQuery.Deferred();
WorkFlow.prototype.processSequence.call(this, this.sequence, function (inst) {
inst.deferred.resolveWith(inst.deferred.promise(), [inst.token]);
if (next instanceof Function)
next(true);
});
return this.deferred.promise();
};
return WorkFlow;
}());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment