Caching Function Outcomes With CachedWithin Caches Both The Return Value And The Output Buffer In Lucee 5.3.2.77
One of the cool features of the Lucee CFML engine is that you can have it memoize the outcome of a Function if you define a cachedWithin
directive in the Function's meta-data. The cachedWithin
directive caches the outcome of the Function; and then, if the Function is invoked again with the same arguments, the cached result is applied and the processing of the underlying Function is skipped. One thing that surprised me as I was digging into this feature was that this memoization caches both the Function return value and the output buffer. Which means that any data that you write to the output buffer during the Function execution will also be applied to the cache.
To see this in action, I've set up a simple demo in which I have a Function that does three things:
- Writes to the system output via
systemOutput()
. - Writes to the output buffer via
echo()
. - Returns a value.
I then provide the cachedWithin="request"
directive and proceed to call this Function with two different sets of arguments:
<cfscript>
// I test the caching of Function "outcome" in Lucee.
// --
// NOTE: By using cachedWithin="request", the outcome of this Function will be
// memoized based on its arguments.
public any function testCaching() cachedWithin = "request" {
// This WILL NOT BE CACHED - it does not relate to the "outcome" of the Function.
systemOutput(
obj = "SYSTEM OUTPUT: #serializeJson( arguments )#",
addNewLine = true
);
// This WILL BE CACHED - it relates to the "outcome" of the Function.
echo( "ECHO: #encodeForHtml( serializeJson( arguments ) )#" );
// This WILL BE CACHED - it related to the "outcome" of the Function.
return( "RETURN: #serializeJson( arguments )#" );
}
// ------------------------------------------------------------------------------- //
// ------------------------------------------------------------------------------- //
// Second line should consume cached outcome.
echo( "<p>" & encodeForHtml( testCaching( "a" ) ) & "</p>" );
echo( "<p>" & encodeForHtml( testCaching( "a" ) ) & "</p>" );
// Second line should consume cached outcome.
echo( "<p>" & encodeForHtml( testCaching( "b" ) ) & "</p>" );
echo( "<p>" & encodeForHtml( testCaching( "b" ) ) & "</p>" );
</cfscript>
As you can see, each set of arguments is used to invoke the testCaching()
Function twice. The first call primes the cache for the request; the second call consumes the cache within the request. Now, when we run the above Lucee ColdFusion code, we get the following page output:
Notice that with each invocation of the testCaching()
function, we get both the return value and the changes to the output buffer (via echo()
). And, to prove that the underlying Function is really only executing once (per set of arguments), let's look at the log:
As you can see, our systemOutput()
call only fires twice - oncer per set of unique arguments. So, while the results of the echo()
call are "applied" for each testCaching()
consumption, we can see that the Function logic is truly skipped on subsequent calls with the same set of arguments.
Being able to cache the outcome of a Function without actually implementing any explicit memoization logic is a cool little feature of Lucee CFML 5.3.2.77. One thing to be aware of, however, is that the cached value contains both the return value and the alterations made to the output buffer.
CFContent
And CachedWithin
Epilogue On Once I realized that the cachedWithin
directive would cache the changes made to the output buffer, I wondered if it would also cache content that is written to the response stream via CFContent
. To test, I ran this little experiment:
<cfscript>
// I reset the output buffer and write a message to the response.
public void function writeResponse() cachedWithin = createTimeSpan( 0, 0, 1, 0 ) {
var message = "Now: #now().timeFormat( 'full' )#";
content
type = "text/plain; charset=utf-8"
variable = charsetDecode( message, "utf-8" )
;
}
// ------------------------------------------------------------------------------- //
// ------------------------------------------------------------------------------- //
writeResponse();
</cfscript>
Notice that, this time, the cachedWithin
value is not request
but rather a time-span of 1-minute. However, the cachedWithin
directive doesn't appear to cache the variable written to the response; every time I refresh the page, I get a fresh time-stamp in the output.
It was worth a shot.
Want to use code from this post? Check out the license.
Reader Comments
Damn. Working with Lucee since early Railo days and never knew about this - thanks. I am loving these Lucee posts... keep it up!
@All,
As a quick follow-up post, another behavior that was non-obvious to me, especially coming from a JavaScript-heavy background, was that input evaluation - in terms of caching - appears to inspect complex objects by value, not by reference. This is non-obvious to those coming from something like Redux / NgRx background:
www.bennadel.com/blog/3656-the-cachedwithin-function-memoization-feature-appears-to-compare-complex-objects-by-value-in-lucee-5-3-2-77.htm
Also, an empty Array and an empty Struct appear to be treated as the same value.
@Andrew,
Thank you, good sir. It's fun to be digging into ColdFusion more deeply again. I've been on ColdFusion 10 for .... too long. So, being on Lucee now at work has reinvigorated my studies.
Hi Ben. Just wondering why:
Does not relate to the outcome of the Function
And:
Does?
Surely, if something is executed within the function context, it is contributing to its outcome?
@Charles,
I think because the
systemOutput()
is considered more of a "side-effect" of the function execution than an "outcome" of the function. In so much as the execution of a Function is expected to potentially return a value and it is expected to potentially write to the output buffer. But, nothing else inside the Function logic is "expected". Meaning, you would execute a query, write to a file, make an HTTP request - all of this is completely custom for that Function. It would not be feasible to include all of those possibilities in a caching strategy.Of course, this all means that you have to use caching with function in which the caching makes sense :D
OK. I see. That makes sense.
I had a look at the CF Docs Echo() definition and there was something I didn't understand:
CF Docs
Just out of interest is. What is the difference between, writing to the page output stream and the main response buffer.
Whenever I see the word "buffer", my mind goes blank:)
@Charles,
Hmmmm, I have no idea :D I assumed they were the same thing. I have yet to see an example of
echo()
that didn't feel like a drop-in replacement forwriteOutput()
. I just thought it was a shorter alias.@All,
A quick follow-up to this post, it appears that the return value of a
cachedWithin
function is also returned by-value, not by-reference:www.bennadel.com/blog/3674-function-results-are-returned-by-value-when-using-cachedwithin-in-lucee-5-3-2-77.htm
This pertains even to complex objects like Arrays, Structs, and ColdFusion components, that would typically be passed-around by-reference.