Skip to main content
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Edith Au
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Edith Au

Caching Function Outcomes With CachedWithin Caches Both The Return Value And The Output Buffer In Lucee 5.3.2.77

By
Published in Comments (8)

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:

  1. Writes to the system output via systemOutput().
  2. Writes to the output buffer via echo().
  3. 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:

The outcome a Function is cached using cachedWithin in Lucee 5.3.2.77.

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:

Memoized Functions are only invoked once per unique set of arguments in Lucee 5.3.2.77.

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.

Epilogue On CFContent And CachedWithin

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

15,902 Comments

@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.

15,902 Comments

@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.

449 Comments

Hi Ben. Just wondering why:

systemOutput

Does not relate to the outcome of the Function

And:

echo()

Does?

Surely, if something is executed within the function context, it is contributing to its outcome?

15,902 Comments

@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

449 Comments

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

"While writeOutput() writes to the page-output stream, echo() writes to the main response buffer"

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:)

15,902 Comments

@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 for writeOutput(). I just thought it was a shorter alias.

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel