Using "continue" To Short-Circuit .each() Iteration In ColdFusion
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:
As you can see, we output every non-vowel letter in both CFML runtimes. This output has some critical point:
No error was thrown - the code ran successfully.
No vowel was rendered - the
continue
statement successfully exited the inner.each()
loop.The
"END:"
markers were output for every word, indicating that thefor-in
loop ran fully even though there was a nestedcontinue
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
Why do you think it's a quirk/bug? Isn't it fairly common behavior within loops for most languages?
@Chris,
It's common in a
for-in
loop, like:But, in my case, with a
.each()
iterator, thecontinue
is really just acting like areturn
in the iterator Callback. I wonder if it has to do with how the.each()
is being coded under the hood?Why do you prefer
each()
overfor in
?Two points:
It's much slower.
https://trycf.com/gist/60de4fc7a8a14052b56f9cc443e10aba/acf2021
That's why I often go the opposite way.
Cannot
break
it.break
work's likecontinue
, wtf.return false
is not implemented.What I miss is
for ( key as value in collection )
@Dirk,
I actually prefer
for-in
when I can use it. In this case, I was switching fromfor-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, whilefor-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 theCFLoop
tag specifically so I can define multiple variables like:But, clearly that is more syntacticly verbose.
@All,
I thought maybe the
.each()
worked withcontinue
because of some loop that was being implemented behind the scenes. As such, I wanted to run a quick test to see ifcontinue
would work with a callback even when no loop was in play: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:Both CFML runtimes skipped over the outputting of
b
due to thecontinue
statement. So, it seems that a loop isn't required to make this quirky behavior possible.On a somewhat related note, I just accidentally discovered that you can use
return
to short-circuit a CFML template:www.bennadel.com/blog/4425-using-return-to-short-circuit-a-cfml-template-in-coldfusion.htm
Normally, I would use
exit
to halt execution of a CFML template. I wonder if these are doing the same thing under the hood.Post A Comment — ❤️ I'd Love To Hear From You! ❤️
Post a Comment →