Saving A Future In An Intermediary Variable Breaks Error Handling In ColdFusion 2018
A couple of days ago, I stumbled upon a "bug" in ColdFusion 2018 in which passing a Future though a proxy method breaks the error handling on that Future. Well, this morning, I ran in to a related bug that is even more befuddling. It turns out, if you save a Future into an intermediary variable before attaching the .then() and .error() bindings, it breaks the error handling on that Future in ColdFusion 2018.
I swear, I must be taking crazy-pills. Someone please look at this stuff and tell me what the heck I am missing! First, the control case - a Future chain in which the .then() and .error() bindings are attached directly to the runAsync() result:
<cfscript>
future = runAsync(
function() {
throw( "Error in runAsync callback." );
}
)
.then(
function() {
return( "This will never get called." );
}
)
.error(
function( required any error ) {
return( "Caught value!" );
}
);
writeOutput( future.get() );
</cfscript>
As you can see, the runAsync() method will throw an error. This will bypass the .then() handler and proceed directly to the .error() handler. The .error() handler will "recover" the error. And, when we run this, we get the expected outcome:
Caught value!
OK, now to open the box of bananas. Let's take that code and make not changes other than storing the runAsync() result in a variable before applying the .then() and .error() bindings:
<cfscript>
future = runAsync(
function() {
throw( "Error in runAsync callback." );
}
);
// In this version the ONLY DIFFERENCE is that we are storing the FUTURE in an
// intermediary variable before we apply the .then() binding. Functionally, this
// should NOT BE ANY DIFFERENT than chaining the .then() directly to the results of
// the runAsync() method.
// --
// NOTE: The fact that the intermediary variable and the final variable are both
// called "future" makes no difference (you can call one "temp" if it makes you feel
// more comfortable). I am doing that to emphasize the fact that I'm using the value
// that the METHOD CHAINING would have used in the first place.
future = future
.then(
function() {
return( "This will never get called." );
}
)
.error(
function( required any error ) {
return( "Caught value!" );
}
);
writeOutput( future.get() );
</cfscript>
As you can see, functionally-speaking, this code should be 100% the same. Ultimately, I'm still attaching the .then() and .error() methods to the result of the runAsync() function. The only difference is that I'm storing that result in a variable instead of using method chaining.
This should be the same thing, right? But, when we run this code, we get the following ColdFusion output:
As you can see, this time - with the intermediary variable - the error handling completely breaks. The error bubbled-up to the ColdFusion application as an unhandled exception.
I feel like I must be missing something, but I've gone over this code a dozen times and I can't see the issue. The only thing I can think of is that Futures are somehow passed-by value, and the intermediary variable is somehow breaking a reference that the compiler was "magically" using during method-chaining?
But, I can't find evidence of that:
<cfscript>
function test() {
return( "hello" );
}
a = runAsync( test );
b = runAsync( test );
// Assign Future in order to see if it is passed-by reference or passed-by value.
aPrime = a;
// Check object codes.
writeOutput( "A: #a.hashCode()# <br />" );
writeOutput( "A': #aPrime.hashCode()# <br />" );
writeOutput( "Equals: #a.equals( aPrime )# <br />" );
writeOutput( "==: #( a == aPrime )# <br />" );
writeOutput( "B: #b.hashCode()# <br />" );
writeOutput( "==: #( a == b )# <br />" );
</cfscript>
I know the .hashCode() doesn't denote "equality". But, when we run this code, we get the following ColdFusion output:
A: 643971388
A': 643971388
Equals: YES
==: YES
B: 1368874029
==: NO
As you can see, a and aPrime (the intermediary variable) have the same hashCode, pass the .equals() test, and can be compared with the equality operator. As such, it looks and feels like Futures are passed-by reference. Which, of course, means there should be no difference in using the runAsync() result directly; or, by first stuffing it in an intermediary variable.
Oh, and here's the craziest part of this whole thing: if you comment-out the .then() code, the error handling suddenly starts working again:
<cfscript>
future = runAsync(
function() {
throw( "Error in runAsync callback." );
}
);
// In this version the ONLY DIFFERENCE is that we are storing the FUTURE in an
// intermediary variable before we apply the .then() binding. Functionally, this
// should NOT BE ANY DIFFERENT than chaining the .then() directly to the results of
// the runAsync() method.
// --
// NOTE: The fact that the intermediary variable and the final variable are both
// called "future" makes no difference (you can call one "temp" if it makes you feel
// more comfortable). I am doing that to emphasize the fact that I'm using the value
// that the METHOD CHAINING would have used in the first place.
future = future
// .then(
// function() {
//
// return( "This will never get called." );
//
// }
// )
.error(
function( required any error ) {
return( "Caught value!" );
}
);
writeOutput( future.get() );
</cfscript>
When we run this, we get the following ColdFusion output:
Caught value!
It works. But, I don't understand why.
In my previous posts, I've described learning Futures in ColdFusion 2018 as an uphill battle. But, I think it's more like an uphill battle in a mine-field while being blindfolded. Stuff just keeps blowing up and I can't see it coming or understand why it's happening.
Want to use code from this post? Check out the license.
Reader Comments
Hi Ben,
We have identified this issue and the fix will be available in update.
@Vijay,
Do you know if this will fix the issue where error-handling breaks if you pass the Future through a proxy method? I wasn't sure if this was the same issue or a different issue?
@Ben,
Yup, that issue will be fixed as well.
Hi Ben,
Greetings and Good News!!
We have done some recent changes to ColdFusion 2018 Async Framework - the first level then(), error() and their corresponding timed versions are unblocking now. Your use case for showing error details as cause to the terminal exception and proxy usage have also been solved. It should be available as a part of ColdFusion 2018 update.
I will keep you posted on the update details.