Skip to content

Instantly share code, notes, and snippets.

@dajester2013
Created March 20, 2017 03:32
Show Gist options
  • Save dajester2013/58f8304c0d3e3dd8b2037b60b4365652 to your computer and use it in GitHub Desktop.
Save dajester2013/58f8304c0d3e3dd8b2037b60b4365652 to your computer and use it in GitHub Desktop.
Promises and Async
/**
* Thread wrapper for executor functions.
*
* @author Jesse Shaffer
* @license MIT
**/
component accessors=true {
property name="fn" setter=false;
SoftReference = createObject("java", "java.lang.ref.SoftReference");
/**
* Creates an Async runner.
*
* @fn A function to run in a thread. If a closure is used, any references will be preserved.
* @priority Thread priority
**/
public Async function init(required fn, priority="normal") {
variables.id = createuuid();
variables.fn = fn;
variables.priority = priority;
variables.promise = javaCast("null","");
return this;
}
/**
* Begin executing. Any arguments passed in are forwarded to the executor.
**/
public Promise function run() {
promise = new Promise();
thread name = "async-#id#"
action = "run"
priority = priority
fn = SoftReference.init(fn)
promise = SoftReference.init(promise)
args = SoftReference.init(arguments)
{
try {
fn = fn.get();
promise.get().resolve(fn(argumentCollection=args.get()));
} catch(any e) {
promise.get().reject(e);
}
}
return promise;
}
/**
* Joins the async thread and returns the result of the promise.
**/
public any function await() {
thread action="join" name="async-#id#";
if (!isNull(promise)) {
var value = promise.getValue();
promise = javaCast("null","");
return value;
}
}
/**
* Get the thread status
**/
public string function status() {
return threadData().status;
}
/**
* Get the thread's output buffer
**/
public string function getOutput() {
return threadData().output;
}
/**
* Get the thread data
**/
public struct function threadData() {
return cfthread[id];
}
}
/**
* Promise implementation for CFML
*
* @author Jesse Shaffer
* @license MIT
**/
component accessors=true {
property name="state" setter=false;
property name="value" setter=false;
PENDING = "PENDING";
FULFILLED = "FULFILLED";
REJECTED = "REJECTED";
/**
* Create a promise
*
* @executor A callback function to begin executing - passed resolve and reject. If an executor is not supplied, you must manually resolve the promise with a value.
**/
public function init(executor) {
var self = this;
var selfVars = variables;
state = PENDING;
value = javaCast("null","");
handlers = [];
// closures to ensure references stay intact
resolver = function(result) {
self.resolve(result);
};
// closures to ensure references stay intact
rejecter = function(reason) {
self.reject(reason);
};
if (!isNull(executor))
this.doResolve(executor, resolver, rejecter);
return this;
}
/**
* Immediately fulfill this promise with a value. This is not the prefered method - use resolve()
**/
public function fulfill(result) {
state = FULFILLED;
value = result;
handleAll();
}
/**
* Reject this promise with an error.
**/
public function reject(reason) {
state = REJECTED;
value = reason;
handleAll();
}
/**
* Resolve this promise with a value.
**/
public function resolve(result) {
try {
if (isPromise(result)) {
doResolve(result, resolver, rejecter);
return;
} else {
this.fulfill(result);
}
} catch(any e) {
this.reject(e);
}
}
/**
* Add callbacks for when this promise is completed.
**/
public function done(onFulfilled, onRejected) {
addHandler(onFulfilled, onRejected);
}
/**
* Chainble method to add callbacks for when this promise is completed.
**/
public function then(onFulfilled, onRejected) {
var self = this;
return new Promise(function(resolve, reject) {
self.done(function(result) {
if (!isNull(onFulfilled)) {
try {
return resolve(onFulfilled(result));
} catch(any e) {
return reject(e);
}
} else {
return resolve(result);
}
},function(reason) {
if (!isNull(onRejected)) {
try {
return resolve(onRejected(reason));
} catch(any e) {
return reject(e);
}
} else {
return reject(result);
}
});
});
}
/**
*
**/
public function catch(onError) {
return this.then(function() {}, onError);
}
// safely executes a function so that this promise can only be fulfilled or rejected
private function doResolve(fn, onFulfilled, onRejected) {
var done = false;
try {
fn(function(value) {
if (done) return;
done=true;
onFulfilled(value);
}, function(reason){
if (done) return;
done=true;
onRejected(reason);
});
} catch (any e) {
if (done) return;
done=true;
onRejected(e);
}
}
// execute all handlers
private function handleAll() {
for (handler in handlers) handle(handler);
}
// queues and executes callback handlers
private function handle(handler) {
switch(state) {
case "PENDING":
handlers.push(handler);
break;
case "FULFILLED":
handler.onFulfilled(value);
break;
case "REJECTED":
handler.onFulfilled(value);
break;
}
}
// helper method to add a handler
private function addHandler(onFulfilled, onRejected) {
handlers.append(arguments);
}
// determine if the value is a promise object
private function isPromise(value) {
return isObject(value) && (isInstanceOf(value, "org.jdsnet.cfml.Promise") || isInstanceOf(value, "Promise"));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment