Immediately Invoked Function Expressions (IIFE) Work In Lucee 5.3.2.77
Yesterday, at the end of day, I tweeted about something exciting that I had found in Lucee. It was the fact that the Immediately Invoked Function Expression construct - or IIFE (pronounced "iffy") - is supported in Lucee 5.3.2.77. More than anything, this is just a demonstration of how many things "just work" in the Lucee syntax, regardless of whether or not they are a good idea. The IIFE is a commonly-used pattern in the JavaScript world. But, if you are primarily a ColdFusion developer, this may not be a known pattern. As such, I wanted to showcase the feature support in Lucee.
An Immediately Invoked Function Expression (IIFE) is exactly what it sounds like: it is a function expression that is invoked as part of its own declaration. This is typically achieved by wrapping the function expression in a set of parenthesis and then invoking the expression as a Function:
( function expression )();
This invokes the function expression and returns the result of the function execution to the calling context.
In JavaScript, this is primarily done as a means to create a private scope that a set of revealed closures can act upon. To see this in action in Lucee CFML, we can create an IIFE that implements a simple counter:
<cfscript>
// Our ID generator is created using an Immediately Invoked Function Expression, or
// IIFE, which is simply a function that is executed as part of its definition. The
// ID generator, in this case, then becomes the return value of the IIFE execution.
nextID = (( numeric id = 0 ) => {
// This inner function, that gets returned from the IIFE, closes-over the "id"
// argument of the IIFE and can act upon it from the calling context even though
// the rest of the world can't even see that it exists (this is just a closure).
var incrementAndGet = () => {
return( ++id );
};
return( incrementAndGet );
})( 100 );
// ------------------------------------------------------------------------------- //
// ------------------------------------------------------------------------------- //
echo( "ID: #nextID()# <br />" );
echo( "ID: #nextID()# <br />" );
echo( "ID: #nextID()# <br />" );
</cfscript>
As you can see here, we're defining a Function Expression that returns another Function, incrementAndGet()
. We then immediately invoke that function expression and store the return value as our nextID
variable. The IIFE, in this case, creates a private scope for the id
value. The closure returned from the IIFE can then act upon this privately-scoped id
value, even when passed out of scope (into the calling context).
Now, if we run this ColdFusion code, we get the following output:
ID: 101
ID: 102
ID: 103
As you can see, each call to the nextID()
function - returned by the IIFE - incremented and returned the privately-scoped id
value that is being "closed over" by the incrementAndGet()
function.
Of course, you could achieve the same thing by simply defining an id
variable and then directly calling ++id
whenever you need the next ID in the series. I'm not trying to sell you on the idea of using an IIFE - I'm only demonstrating that it works in Lucee CFML.
Let's look at a slightly more robust example. In the following code, we're going to create an Immediately Invoked Function Expression (IIFE) that performs a depth-first traversal of the Element Nodes in an XML document. It will do this, in part, by exposing a small Iterator API:
<cfscript>
// Create our XML document from the following HTML.
doc = htmlParse("
<html>
<head>
<title>Hello World</title>
</head>
<body>
<h1>Hello World</h1>
<p>Welcome to the <strong>fun zone</strong>!</p>
<blockquote>The water is warm :D</blockquote>
</body>
</html>
");
// ------------------------------------------------------------------------------- //
// ------------------------------------------------------------------------------- //
// Create an iterator for our XML document using an IIFE - notice that the parsed
// HTML document is provided as the ARGUMENT for our IIFE.
iterator = (( required xml document ) => {
// Since XPath performs a depth-first search of the document, the query for all
// nodes will implicitly create an array over which we can easily walk.
var nodes = document.search( ".//*" );
var nodeCount = nodes.len();
var nodeIndex = 0;
// Return the API for our iterator.
var api = {
hasNext: () => {
return( nodeIndex < nodeCount );
},
next: () => {
if ( ! api.hasNext() ) {
throw( type = "Iterator has run out of XML nodes to traverse!" );
}
return( nodes[ ++nodeIndex ] );
}
};
return( api );
})( doc );
// ------------------------------------------------------------------------------- //
// ------------------------------------------------------------------------------- //
// Traverse the XML document and output the element names.
while ( iterator.hasNext() ) {
echo( "Element: " & iterator.next().xmlName & "<br />" );
}
</cfscript>
In this case, we're providing the parsed HTML document as the argument to our IIFE. The IIFE then returns an Iterator API that will perform the depth-first traversal of the provided HTML document. In this case, the IIFE returns the hasNext()
and next()
methods, which "close over" the privately-scoped variables that store the location of the traversal within the context of XML document.
Now, if we run this ColdFusion code, we get the following output:
Element: html
Element: head
Element: title
Element: body
Element: h1
Element: p
Element: strong
Element: blockquote
As you can see, by using an IIFE, we are able to create small little chunks of cohesive functionality without having to dump too many variables into the share scope of the calling context.
I am loving the fact that Immediately Invoked Function Expressions (IIFE) are supported in Lucee. If nothing else, it's just a testament to how much the Lucee syntax "gets out of your way" and let's you get on with the business of solving problems. You may never need to use an IIFE in your Lucee CFML. But, it's nice to you know you can if you want to.
Want to use code from this post? Check out the license.
Reader Comments
I had no idea you could do this in Lucee. This is super cool. Thanks for sharing this, Ben!
@Tony,
Right?! Now, to figure how it can actually be used :P
Mind = Blown.
Very cool - I did not realize this was supported, thanks for sharing! Will definitely be keeping it in mind, to see if I can find a practical use case, or rather, just an excuse for using it.
Hi Ben. When you say IIFE, if, in the first example, there were no output commands, like:
Would the:
Function still execute?
Are you saying that these functions are self executing?
It seems like the last pair of parentheses, call the function immediately?
If so, this is kind of cool, although you could just call the function normally, but it creates a shortcut.
@Charles,
The "self-executing" part is the thing that produces the
nextID()
function, not the invocation ofnextID()
itself, if that makes sense. Basically, it's just this part:Yeah, there are other ways to do this -- mostly, I was just excited that this actually worked from a syntax perspective.
OK. So:
Returns the closure:
And the following at the end:
Seeds the method with the initial starting ID, which gets "remembered", when the closure is returned?
This is kind of clever, because the parent function makes everything inside privately scoped and only exposes the inner function to the outside world by returning it.
Like, in JavaScript:
Your example is slightly different because it returns the inner function. But both provide a mechanism for exposing part of the content of the parent function!
@Charles,
Exactly, you are spot-on now! It's basically just like calling any other method that returns a function. If you "unwrap" the self-executing function, you are basically left with:
The only difference is that we are explicitly naming the intermediary function,
createIdGenerator()
, rather than just creating a headless function and invoking it immediately.@All,
As a small follow-up to this, the
cachedWithin
function memoization also works with these IIFEs:www.bennadel.com/blog/3697-using-an-immediately-invoked-function-expression-iife-and-cachedwithin-to-easily-cache-steps-in-a-procedural-script-in-lucee-5-3-2-77.htm
I used this to easily cache one CPU-intensive task in a procedural workflow. I wouldn't necessarily do this in production; but, it was perfect for a local, utility script.