Created
February 21, 2012 15:13
-
-
Save bennadel/1876978 to your computer and use it in GitHub Desktop.
ColdFusion 10 Beta - Closures And Function Expressions And Threads
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!--- | |
Start a CFScript block. Closures can only be used inside | |
of CFScript due to the syntax required to define them. | |
---> | |
<cfscript> | |
// I am a factory for the thread callbacks. | |
function getThreadCallback(){ | |
// Keep a running tally of how many times the log methods | |
// are called. We are doing this to see how Closure bindings | |
// are copied into the thread scope (copy vs. reference). | |
var callCount = 0; | |
// Define the log file for this demo -- we will need a log | |
// file since threads are async and we won't be able to | |
// write to the output. | |
var logFilePath = expandPath( "./thread.log" ); | |
// Define the callback handler. This will use both a | |
// lexically-bound counter as well as a passed-in value for | |
// counter; this way, we can look at the various forms of | |
// attribute-copy. | |
var callback = function( metaCounter ){ | |
// Open the file for appending. | |
var logFile = fileOpen( logFilePath, "append" ); | |
fileWriteLine( | |
logFile, | |
"[#++callCount#][#metaCounter#] Finished with a success!" | |
); | |
// Clean up the file access. | |
fileClose( logFile ); | |
}; | |
// Return the callback. | |
return( callback ); | |
} | |
// Get the callback closure (bound to the counter). | |
callback = getThreadCallback(); | |
// ------------------------------------------------------ // | |
// ------------------------------------------------------ // | |
// Create a struct with a counter so that we can demonstrate that | |
// threads copy attributes by VALUE. | |
metaData = { | |
counter: 0 | |
}; | |
// ------------------------------------------------------ // | |
// ------------------------------------------------------ // | |
// Run a successful thread - pass in the callback handler and | |
// the meta count. Notice that these are both being passed-in | |
// via the attributes scope. | |
thread | |
name = "demoThread" | |
action="run" | |
util = metaData | |
onsuccess = callback | |
{ | |
// Execute the success callback. | |
onsuccess( ++util.counter ); | |
} | |
// Run a successful thread - pass in the callback handler and | |
// the meta count. Notice that these are both being passed-in | |
// via the attributes scope. | |
thread | |
name = "demoThread2" | |
action="run" | |
util = metaData | |
onsuccess = callback | |
{ | |
// Execute the success callback. | |
onsuccess( ++util.counter ); | |
} | |
// Run a successful thread - pass in the callback handler and | |
// the meta count. Notice that these are both being passed-in | |
// via the attributes scope. | |
thread | |
name = "demoThread3" | |
action="run" | |
util = metaData | |
onsuccess = callback | |
{ | |
// Execute the success callback. | |
onsuccess( ++util.counter ); | |
} | |
// ------------------------------------------------------ // | |
// ------------------------------------------------------ // | |
// Wait for all the threads to finish running. | |
thread action="join"; | |
// Debug the threads. | |
// writeDump( cfthread ); | |
</cfscript> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!--- | |
Start a CFScript block. Closures can only be used inside | |
of CFScript due to the syntax required to define them. | |
---> | |
<cfscript> | |
// I accept N thread instances and a callback (the last | |
// argument). The callback is invoked when each one of the given | |
// threads has completed execution (success or failure). The | |
// thread object is echoed back in the callback arguments. | |
function threadsDone(){ | |
// Extract the callback from the arguemnts. | |
var callback = arguments[ arrayLen( arguments ) ]; | |
// Extract the thread objects - arguments[1..N-1]. | |
var threads = arraySlice( arguments, 1, arrayLen( arguments ) - 1 ); | |
// I am a utiltiy function that determine if a given thread | |
// is still running based on its status. | |
var isThreadRunning = function( threadInstance ){ | |
// A thread can end with a success or an error, both of | |
// which lead to different final status values. | |
return( | |
(threadInstance.status != "TERMINATED") && | |
(threadInstance.status != "COMPLETED") | |
); | |
}; | |
// I am a utility function that determines if any of the | |
// given blogs are still running. | |
var threadsAreRunning = function(){ | |
// Loop over each thread to look for at least ONE that is | |
// still running. | |
for (var threadInstance in threads){ | |
// Check to see if this thread is still running. This | |
// is if it has not yet completed (successfully) or | |
// terminated (error). | |
if (isThreadRunning( threadInstance )){ | |
// We don't need to continue searching; if this | |
// thread is running, that's good enough. | |
return( true ); | |
} | |
} | |
// If we made it this far, then no threads are running. | |
return( false ); | |
}; | |
// I am utility function that invokes the callback with the | |
// given thread instances. | |
var invokeCallback = function(){ | |
// All of the threads we are monitoring have stopped | |
// running. Now, we can invoke the callback. Let's build | |
// up an argument collection. | |
var callbackArguments = {}; | |
// Translate the array-based arguments into a struct- | |
// based collection of arguments. | |
for (var i = 1 ; i < arrayLen( threads ) ; i++){ | |
callbackArguments[ i ] = threads[ i ]; | |
} | |
// Invoke the callback with the given threads. | |
callback( argumentCollection = callbackArguments ); | |
}; | |
// In order to check the threads, we need to launch an | |
// additional thread to act as a monitor. This will do | |
// nothing but sleep and check the threads. | |
// | |
// NOTE: We need to pass the two methods INTO the thread | |
// explicitly since the thread body does NOT have access | |
// to the local scope of the parent function. | |
thread | |
name = "threadsDone_#getTickCount()#" | |
action = "run" | |
threadsarerunning = threadsAreRunning | |
invokecallback = invokeCallback | |
{ | |
// Check to see if the threads are running. | |
while (threadsAreRunning()){ | |
// Sleep briefly to allow other threads to complete. | |
thread | |
action="sleep" | |
duration="10" | |
; | |
} | |
// If we made it this far, it means that the threads | |
// have all finished executing and the while-loop has | |
// been exited. Let's invoke the callback. | |
invokeCallback(); | |
}; | |
} | |
// ------------------------------------------------------ // | |
// ------------------------------------------------------ // | |
// ------------------------------------------------------ // | |
// ------------------------------------------------------ // | |
// Launch a thread - remember this is an ASYNCHRONOUS operation. | |
thread | |
name = "thread1" | |
action = "run" { | |
// Sleep the thread breifly. | |
sleep( randRange( 10, 100 ) ); | |
} | |
// Launch a thread - remember this is an ASYNCHRONOUS operation. | |
thread | |
name = "thread2" | |
action = "run" { | |
// Sleep the thread breifly. | |
sleep( randRange( 10, 100 ) ); | |
} | |
// Launch a thread - remember this is an ASYNCHRONOUS operation. | |
thread | |
name = "thread3" | |
action = "run" { | |
// In this one, let's throw an error to show that this works | |
// with failed threads as well as successful one. | |
// | |
// NOTE: Since this is an asynchronous process, this error | |
// does not kill the parent process. | |
throw( | |
type = "threadError", | |
message = "Something went wrong in the thread!" | |
); | |
} | |
// ------------------------------------------------------ // | |
// ------------------------------------------------------ // | |
// Wait for all threads to finish and the run the given | |
// callback. | |
// | |
// NOTE: For demo purposes, we're going to JOIN the threads so | |
// that we can write to the page output. | |
threadsDone( | |
cfthread.thread1, | |
cfthread.thread2, | |
cfthread.thread3, | |
function( thread1, thread2, thread3 ){ | |
// Log the status of the completed threads. | |
writeOutput( "Threads have finished! #now()#<br />" ); | |
writeOutput( "#thread1.name# - #thread1.status#<br />" ); | |
writeOutput( "#thread2.name# - #thread2.status#<br />" ); | |
writeOutput( "#thread3.name# - #thread3.status#<br />" ); | |
} | |
); | |
// ------------------------------------------------------ // | |
// ------------------------------------------------------ // | |
// Wait for all threads to join so we can see the output. | |
thread action="join"; | |
// Debug threads. | |
// writeDump( cfthread ); | |
</cfscript> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!--- | |
Start a CFScript block. Closures can only be used inside | |
of CFScript due to the syntax required to define them. | |
---> | |
<cfscript> | |
// Create a closure that is bound to the primary page's | |
// output buffer. | |
writeToBrowser = function( value ){ | |
// Get the current page context hash code. | |
var hashCode = getPageContext().hashCode(); | |
// Write to the currently-bound output buffer. | |
writeOutput( "[#hashCode#] #value#" ); | |
}; | |
// Test the top-level output / hashCode binding. | |
writeToBrowser( "Top-level CONTROL write.<br />" ); | |
// ------------------------------------------------------ // | |
// ------------------------------------------------------ // | |
// Run a thread and pass-in the parent-page-bound writer. When | |
// this thread is done executing, it will save a thread-bound | |
// writer to the THREAD scope. | |
thread | |
name = "test" | |
action = "run" | |
toplevelwriter = writeToBrowser | |
{ | |
// Try writing to whatever output is currently defined. | |
writeOutput( "Direct output within thead.<br />" ); | |
// Try writing to the closure-based writer. | |
topLevelWriter( "Top-level writer within thread.<br />" ); | |
// Let's export a closure who's bound to the thread output | |
// buffer. | |
thread.writeToThread = function( value ){ | |
// Get the current page context hash code. | |
var hashCode = getPageContext().hashCode(); | |
// Write to currently-bound output buffer. | |
writeOutput( "[#hashCode#] #value#" ); | |
}; | |
// Execute the internal write. | |
thread.writeToThread( "Wrapped output within thread." ); | |
// Sleep the thread to allow access to the exported function | |
// before the thread has actuall finished running. | |
sleep( 1000 ); | |
} | |
// ------------------------------------------------------ // | |
// ------------------------------------------------------ // | |
// HOPEFULLY give the above thread enough time to export the | |
// writer method. | |
sleep( 500 ); | |
// Try invoking the thread-exported writer. | |
cfthread.test.writeToThread( | |
"Thread-writer output - where's it gonna go?<br />" | |
); | |
// Join all the threads back to the page. | |
thread action = "join"; | |
// Debug the threads. | |
writeDump( cfthread ); | |
</cfscript> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!--- | |
Start a CFScript block. Closures can only be used inside | |
of CFScript due to the syntax required to define them. | |
---> | |
<cfscript> | |
// I create evented objects for publish and subscribe | |
// functionality. This factory creates objects that have the | |
// following method API: | |
// | |
// - on( eventType, callback ) | |
// - off( eventType, callback ) | |
// - trigger( eventType, data ) | |
EventEmitter = function(){ | |
// In order for the event bindings to work, we need to be | |
// able to bind and Unbind them. For this, we'll need to keep | |
// a unique ID for each data. And, in order to generate that | |
// ID, we'll need to store event-emitter meta data. | |
var eventEmitterMetaData = getMetaData( EventEmitter ); | |
// Add the unique ID if it doesn't exist. | |
if (!structKeyExists( eventEmitterMetaData, "uuid" )){ | |
// Initialize the uuid. | |
eventEmitterMetaData.uuid = 0; | |
} | |
// Create a cache of event bindings. Each event will be | |
// defined as a type and a set of callbacks. | |
var events = {}; | |
// Create an instance of emitter. | |
var emitter = {}; | |
// I add the given callback to the given event cache. | |
var addCallback = function( eventType, callback ){ | |
// Check to see if this callback needs to be prepared | |
// for use with the event system. | |
if (!structKeyExists( getMetaData( callback ), "eventEmitterUUID" )){ | |
// Add the UUID to the callback meta data. | |
// | |
// NOTE: Since this it is very likely (expected) that | |
// event callbacks will be created as Closrues, we | |
// can expect each closure to be unique and therefore | |
// to have its own proprietary meta data. | |
getMetaData( callback ).eventEmitterUUID = ++eventEmitterMetaData.uuid; | |
} | |
// Make sure we have a cache for this particular type of | |
// event before we try to access it. | |
if (!structKeyExists( events, eventType )){ | |
// Create the cache. | |
events[ eventType ] = []; | |
} | |
// Add the callback to this collection. | |
arrayAppend( events[ eventType ], callback ); | |
}; | |
// I remove the callback from the given event cache. | |
var removeCallback = function( eventType, callback ){ | |
// Make sure the callback has an event emitter UUID. If | |
// it doesn't, then we it shouldn't be part of the system. | |
if (!structKeyExists( getMetaData( callback ), "eventEmitterUUID" )){ | |
throw( type = "InvalidCallback" ); | |
} | |
// Check to make sure we have a cache for the given | |
// event type. | |
if (!structKeyExists( events, eventType )){ | |
// No cache - nothing more to check. | |
return; | |
} | |
// Get the target UUID. | |
var uuid = getMetaData( callback ).eventEmitterUUID; | |
// Filter the callback out of the event type. | |
// | |
// NOTE: I am using an intermediary variable here for the | |
// result to get around some weird parsing bug. | |
var cache = arrayFilter( | |
events[ eventType ], | |
function( boundCallback ){ | |
// Exclude if this method is bound. | |
if (getMetaData( boundCallback ).eventEmitterUUID == uuid){ | |
return( false ); | |
} | |
// Include this callback in the result. | |
return( true ); | |
} | |
); | |
// Save the filtered cache of event callbacks. | |
// | |
// NOTE: See note from above (bug). | |
events[ eventType ] = cache; | |
}; | |
// Create the bind method. | |
emitter.on = function( eventType, callback ){ | |
// Add the callback to the appropriate callback cache. | |
addCallback( eventType, callback ); | |
// Return "this" object for method chaining. | |
return( emitter ); | |
}; | |
// Create the unbind method. | |
emitter.off = function( eventType, callback ){ | |
// Remove the callback from the appropriate callback cache. | |
removeCallback( eventType, callback ); | |
// Return "this" object for method chaining. | |
return( emitter ); | |
}; | |
// Create the trigger method. | |
emitter.trigger = function( eventType, data ){ | |
// Make sure we have callbacks for this event. | |
if ( | |
!structKeyExists( events, eventType ) || | |
!arrayLen( events[ eventType ] ) | |
){ | |
// No callbacks to trigger - exit out. | |
return; | |
} | |
// Loop over each callback to invoke it for the event. | |
arrayEach( | |
events[ eventType ], | |
function( callback ){ | |
// Invoke the callback with the given data. | |
// | |
// NOTE: I am using the argumentCollection here | |
// to get around a strange named-argument bug. | |
callback( argumentCollection = { "1" = data } ); | |
} | |
); | |
}; | |
// Return the event emitter instance. | |
return( emitter ); | |
}; | |
// ------------------------------------------------------ // | |
// ------------------------------------------------------ // | |
// ------------------------------------------------------ // | |
// ------------------------------------------------------ // | |
// Create an instance of event emitter. This will have the | |
// publication and subscription methods for: | |
// | |
// - on() | |
// - off() | |
// - trigger() | |
evented = EventEmitter(); | |
// Run a thread an pass in the event emitter. This will allow the | |
// the thread to announce events during its execution. | |
thread | |
name = "eventedThread" | |
action = "run" | |
beacon = evented | |
{ | |
// Sleep this thread immediately to let the code AFTER the | |
// thread run and bind to the events. | |
sleep( 100 ); | |
// Trigger start event. | |
beacon.trigger( | |
"threadStarted", | |
"The thread has started!" | |
); | |
sleep( 100 ); | |
// Trigger end event. | |
beacon.trigger( | |
"threadEnded", | |
"The thread has ended!" | |
); | |
} | |
// ------------------------------------------------------ // | |
// ------------------------------------------------------ // | |
// Before we check the valid event emitting, we want to check to | |
// make sure that the UNBIND feature works. | |
tempCallback = function( message ){ | |
writeOutput( "This should never be called.<br />" ); | |
}; | |
// Bind and UNBIND the callback. We want to make sure that it | |
// will NOT get called for the given event types. | |
evented | |
.on( "threadEnded", tempCallback ) | |
.off( "threadEnded", tempCallback ) | |
; | |
// ------------------------------------------------------ // | |
// ------------------------------------------------------ // | |
// Bind to the "Start" event on the thread. | |
evented.on( | |
"threadStarted", | |
function( message ){ | |
writeOutput( message & "<br />" ); | |
} | |
); | |
// Bind to the "End" event on the thread. | |
evented.on( | |
"threadEnded", | |
function( message ){ | |
writeOutput( message & "<br />" ); | |
} | |
); | |
// ------------------------------------------------------ // | |
// ------------------------------------------------------ // | |
// Halt the page until the thread has finished execution. This | |
// will cause the events to be triggered BEFORE this page has | |
// finished running. | |
thread action = "join"; | |
// Debug threads. | |
writeDump( cfthread ); | |
</cfscript> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!--- | |
Start a CFScript block. Closures can only be used inside | |
of CFScript due to the syntax required to define them. | |
---> | |
<cfscript> | |
// Run a thread that composes a function and a closure. | |
thread | |
name = "testComposition" | |
action = "run" | |
{ | |
// Cannot use a FUNCTION DECLARATION inside of a CFThread | |
// since CFThread runs as a Function behind the scenes and | |
// function declarations cannot be nested. | |
// | |
// Error: Invalid CFML construct found on line X at column Y. | |
// The function innerFunction is incorrectly nested inside | |
// another function definition | |
// _cffunccfthread_cfthread42ecfm2828400851. | |
function innerFunction(){ }; | |
// Define an inner closure. We *know* this will work. | |
var innerClosure = function(){ }; | |
// Define an inner closure that, itself, defines a Function. | |
var innerComposite = function(){ | |
function innerInnerFunction(){ } | |
innerInnerFunction(); | |
}; | |
// Try invoking composite closure. | |
innerComposite(); | |
} | |
// ------------------------------------------------------ // | |
// ------------------------------------------------------ // | |
// Join thread back to page so we can debug the output. | |
thread action = "join"; | |
// Debug the thread. | |
writeDump( cfthread ); | |
</cfscript> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment