Skip to main content
Ben Nadel at CFUNITED 2009 (Lansdowne, VA) with: Kim
Ben Nadel at CFUNITED 2009 (Lansdowne, VA) with: Kim

Using "continue" To Short-Circuit .each() Iteration In ColdFusion

By
Published in Comments (6)

CAUTION: This is likely a bug in the CFML language; but, it's kind of neat in that it expresses loop iteration intent slightly differently.

Yesterday, I was refactoring some ColdFusion code to go from using a standard for-in loop to using an .each() iteration member method. The for-in version of the code had some short-circuiting logic that used continue statements to skip to the next loop iteration. And, when I refactored to using .each(), I forgot to change the continue keyword to be a return keyword. And, wouldn't you know it - the code worked anyway. This is likely a quirk, not a feature, of the CFML platform; but, I thought it would be fun to share.

To see this in action, I'm going to perform a nested loop. The outer loop will use the for-in syntax to iterate over words. The inner loop will use the .each() syntax to iterate over the letters in each word. And, when iterating over the letters, I'm going to use the continue statement to skip over any vowels:

<cfscript>

	words = [ "Bloody", "Hell", "Man" ];

	// OUTER LOOP: Using a standard for-loop here, which would normally work in
	// conjunction with the "CONTINUE" statement.
	for ( word in words ) {

		writeDump([ "START: #word#" ]);

		// INNER LOOP: Using an iterator, which would normally work in conjunction with
		// the "RETURN" statement.
		word.reMatch( "." ).each(
			( letter ) => {

				// Skip outputting any vowels.
				if ( letter.reFind( "[aeiou]" ) ) {

					// CAUTION: This should have been a RETURN statement; but, I
					// accidentally left in CONTINUE when I was refactoring my code. But,
					// it seems to work fine in both Lucee CFML and Adobe ColdFusion.
					continue;

				}

				writeDump([ letter ]);

			}
		);

		writeDump([ "END: #word#" ]);

	}

</cfscript>

Notice that at the end of the outer-loop, I'm outputting a value. This is important because it will help us determine if the continue statement within the inner loop is accidentally short-circuiting the outer loop, not the inner loop. And, when we run this code in both the latest Lucee CFML and Adobe ColdFusion, we get the following output:

Page showing that every non-vowel letter in each word has been successfully output.

As you can see, we output every non-vowel letter in both CFML runtimes. This output has some critical point:

  1. No error was thrown - the code ran successfully.

  2. No vowel was rendered - the continue statement successfully exited the inner .each() loop.

  3. The "END:" markers were output for every word, indicating that the for-in loop ran fully even though there was a nested continue statement.

While I know that this likely runs by accident, there's something I like about it. Using continue expresses more intent when compared to using return. I understand that there is nothing technically different about the .each() method - it's just a member method; however, using continue does make it feel more loop-like.

Anyway, happy hump day!

Want to use code from this post? Check out the license.

Reader Comments

15,841 Comments

@Chris,

It's common in a for-in loop, like:

for ( value in values ) {
	if ( value == "boop" ) {
		continue;
	}
	// .... rest of loop
}

But, in my case, with a .each() iterator, the continue is really just acting like a return in the iterator Callback. I wonder if it has to do with how the .each() is being coded under the hood?

15,841 Comments

@Dirk,

I actually prefer for-in when I can use it. In this case, I was switching from for-in over to .each() so that I could use ColdFusion's ability to perform iteration in parallel threads. Doing so lead to a large increase in throughput; so, while for-in might be faster in general, using .each() proved to be much faster due to the parallelization.

I do like the idea of (key as value in collection) - that would be a cool syntax. I sometimes - at least in Lucee CFML - use the CFLoop tag specifically so I can define multiple variables like:

loop
	key = "local.key"
	value = "local.value"
	struct = payload
	{

	// ... use `key` and `value` here.
}

But, clearly that is more syntacticly verbose.

15,841 Comments

@All,

I thought maybe the .each() worked with continue because of some loop that was being implemented behind the scenes. As such, I wanted to run a quick test to see if continue would work with a callback even when no loop was in play:

<cfscript>

	visitValues(
		[ "a", "b", "c" ],
		( value ) => {

			if ( value == "b" ) {

				continue; // Seeing if this works.

			}

			writeOutput( value );

		}
	);

	// ------------------------------------------------ //
	// ------------------------------------------------ //

	public void function visitValues(
		required array collection,
		required function callback
		) {

		// NOTE: I'm not using any looping at all here!
		// This is just hard-coded to look for 3-values.
		callback( collection[ 1 ] );
		callback( collection[ 2 ] );
		callback( collection[ 3 ] );

	}

</cfscript>

In this case, there is no loop - my visitValues() method is hard-coded to look at 3 values, passing each item to the given callback. And, when I run this in Lucee CFML and Adobe ColdFusion 2021, they both output:

ac

Both CFML runtimes skipped over the outputting of b due to the continue statement. So, it seems that a loop isn't required to make this quirky behavior possible.

Post A Comment — I'd Love To Hear From You!

Post a Comment

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