There are many operations in FileMaker that can result in an error. Error handling is meant to let developers enforce the desired behavior once an error occurs.
Errors typically surface to the developer via 2 main vectors allowing for detection:
Get ( LastError )
to determine the error code for the step. With that code, it is possible to know if the step was successful or if it raised an error.?
as the result, or an error string that begins with ?
like with the JSON functions. Get ( LastError )
is not impacted by those errors.Get ( LastError )
constitutes the crucial part of an error, there is a lot of information aside from the error code itself that can prove beneficial, or essential even, in understanding why the error occurred. When debugging, more information can lead the developer to a faster resolution, avoiding what caused the error in future script executions. In production, coupled with error logging, having more information will make it easier for the developer to explain why the error took place and what, if anything, should be done about it.Get ( LastError )
function, developers have to sometimes test for Get ( LastError )
and test their calculation results at other times.It is our opinion that the issues above can discourage developers from handling errors altogether, meaning that developers affected by those issues will eventually make a determination between the effort of appropriately handling errors of a given script step versus the perceived likeliness of an error happening, a tradeoff of sorts where even if a developer knows an error can happen, it is too inconvenient to handle it.
This best practice will aim at reducing the effort of error detection & error handling by addressing the issues mentioned above.
The Error suite of custom functions is hosted on the filemakerstandards GitHub repo. The collection of functions includes the following.
Error ( type; errorCode; description; details )
ErrorApp ( errorCode; description )
ErrorDetails ()
ErrorEnvironment ( includeList )
ErrorFmp ( details )
ErrorFound ( errorData )
ErrorIf ( test; type; errorCode; description ; details )
ErrorInScriptResult ()
ErrorLog ( level; component; entry; errorCode )
ErrorMessage ( errorCode )
To augment the information available to the developer when an error occurs, we suggest using the set of custom functions provided by the contributors of this site. The custom functions provide a very similar output to Get ( LastError )
— they simply provide a numerical error code. This specific trait means the functions can be used as a test within flow control structures just like the Get ( LastError )
function.
Critical knowledge about these functions!
The Error suite of custom functions suggested by this best practice uses what is known as a Reserved word. In this particular case it's a reserved $variable You MUST be ok with reserving the variable $error for storing a JSON based object of error related information.
WARNING: In other words...
You CANNOT set the variable named $error ANYWHERE in your code other than calling these functions or this Error suite may not function properly! Hence $error is reserved by these functions. You may reference (aka, read) the variable's contents, but do not directly set or modify the $error variable except through the custom functions themselves and/or/unless you are fully aware of the results of your modifications and desire to do so. THIS INCLUDES ACCIDENTALLY LEAVING CALLS TO ERROR FUNCTIONS WITHIN THE DATA VIEWER'S WATCH VARIABLES! Make sure to CLEAR any watch variables when testing scripts which use the Error suite of functions. You have been warned.
The ErrorFmp ()
and ErrorApp ()
functions are what you will typically use most often and both behave in the same fashion, aside from the fact that ErrorApp () allows you to specify your own error code per the specific circumstances your code may require. Both of these functions are simply convenience wrappers around the core Error()
function.
Something to note is that FileMaker 19.6.1 and higher implemented the function Get ( LastErrorLocation ) in which Claris allows a developer to use a developer specified error code within the range of 5000 thru 5499 with script steps which allow you to specify an error code, such as the Revert Transaction script step. Errors in this range are returned by the Get ( LastError ) function as "native" FMP errors and will work with the functions suggested by this site.
The above functions can be used as part of your flow control steps ( If
, ExitLoopIf
).
ErrorDetails()
is a custom function which can be modified to include whatever error specific details you'd like to include within the $error object. Make note that it too is controlled by a variable which you can optionally reserve. An example using the variable name of $errorLevel is provided within the function. This allows you to specify variable levels of details as desired.
Additionally, you can use the ErrorEnvironment()
custom function to extend this even further by being able to compose whatever details you'd like from most all of FileMaker's Get ()
environment functions. These are broken down into subgroupings of the following.
"datetime";
"default";
"field";
"file";
"include";
"keyboard";
"layout";
"misc";
"mobile";
"object";
"path";
"portal";
"record";
"request";
"security";
"system";
"trigger";
"user";
"window";
The ErrorInScriptResult ()
function is meant to be called after a step which normally generates a script result (e.g. Perform Script
, Perform Script On Server
, etc.). Note, you must enable and modify this custom function to use it. It performs some tests to determine if an error occurred as the step was performed or if the subscript itself returned an error object. Similar to the above functions, this function is also designed to be called as part of flow control steps.
Notice: if you choose to use this extension to the Error handling suite, it also instantiates a reserved variable of $result.
Once the script flow has been routed to the dedicated portion of a script meant to do something specific with the error, the developer can call on ErrorFound ( $error )
to determine if an error occurred. The developer can also use the native JSON functions to retrieve specific information from the error object.
Another convenience function is the ErrorIf()
function which can alternatively be named ErrorThrow() where you use a test to forceably cause the instantiation/population of the $error variable.
Finally, the ErrorMessage()
function is helpful for returning the definition of a given error code returned by ErrorFmp(), ErrorApp() and other error generating functions – event the native Get ( LastError )
as well.
A bonus custom function named ErrorLog()
showcases some code using a popular plug-in named MBS which supports direct SQL writes to a table named Log. You would need to both create a table named Log and have a licensed version of the MBS plug-in to utilize the code as provided. Otherwise, there are many other ways to handle the logging of your application's errors.
As noted above, the $error
payload can be customized by specifying the $errorLevel
desired within the running script. Additional information can also be included via the details
parameter of the functions through the ErrorDetails() function.
This best practice takes advantage of, but does not require, the single-pass loop pattern for flow control. The single-pass loop is a loop where the script step immediately before an End Loop
step is an Exit Loop If [True]
. This guarantees the loop will never iterate on itself.
Within the loop, we can then use the Exit Loop If
step to test for errors ( error detection ) and exit outside of the loop at the bottom of the script where errors are then handled. This gives code that is DRYer than its equivalent when not relying on the single-pass loop, and when combined with the custom functions mentioned above, considerably reduces the effort of handling errors.
This best practice introduces the following nuances about implementing the single-pass loop pattern.
Get ( LastError )
output ahead of the single-pass loop, in order to avoid altering the value given by that function before the script execution (as can be seen with script triggers).Exit Loop If
where the developer calls on the ErrorFmp ()
function for error detection.Set Variable [ $userAbortIsAllow ; Value: Get ( AllowAbortState ) ]
Set Variable [ $errorCaptureIsDisabled ; Value: Get ( ErrorCaptureState ) = 0 ]
Allow User Abort [ Off ]
Set Error Capture [ On ]
Set Variable [ $error ]
This example showcases the reserved range of 5000-5499 available to developers from FileMaker.
# PURPOSE: Describe script purpose here.
Allow User Abort [ Off ]
Set Error Capture [ On ]
Set Variable [ $error ; Value: "{}" ]
Set Variable [ $errorLevel ; Value: 0 // Use 1 to invoke ErrorDetails() ]
Set Variable [ $result ; Value: "default result" ]
Set Variable [ $validCondition ; Value: Get ( RecordOpenCount ) = 1 ]
Set Variable [ $errorCode ; Value: 5000 // Use anything between 5000-5499 for your own errors. ]
# Transaction attempt.
Open Transaction []
# Stuff would happen here.
Revert Transaction [ Condition: Let ( $errorMsg = "Thrown error" ; not $validCondition ) ; Error Code: $errorCode ; Error Message: $errorMsg ]
Set Variable [ $madeItHere ; Value: "Yeah!" ]
Set Variable [ $result ; Value: "a modified result" ]
Commit Transaction
Set Variable [ $lastError ; Value: ErrorApp ( Get ( LastError ) ; "Failed transaction" ) ]
# Handle error.
If [ $error ≠ "{}" ]
Set Variable [ $errorHappened ; Value: "Handle the $error object here." ]
End If
# Finally
Set Variable [ $cleanup ; Value: "Run some cleanup steps." ]
Exit Script [ Text Result: $result ]
The custom functions can also be used to relieve a developer from having to test the output of a calculation for a specific string like ?
.
For instance, assuming the custom function isSquare
is defined like this:
Case (
Sqrt ( number ) = Int ( Sqrt ( number ) ) ;
True;
// Else
False & Left ( Error ( "calculation" ; -2 ; "Not a square." ; "\"isSquare\" custom function raised this error." ) ; 0 )
)
With the function, the developer can then write a portion of script like this:
Set Variable [ $myNumber ; Value: 7 ]
Set Variable [ $myNumberIsSquare ; Value: isSquare ( $myNumber ) ]
Exit Loop If [ ErrorFound ( $error ) ]
Or, in this case, because the function itself outputs a boolean result, we can take this one step further like this:
Set Variable [ $myNumber ; Value: 7 ]
Exit Loop If [ not isSquare ( $myNumber ) ]
In both cases, as we exit the loop, the $error
variable will contain an error object.
This best practice requires the use of reserved variables. Some developers may find this practice objectionable, but in this case it is introduced to make the general pattern less verbose and disruptive for the developer reading the script. This choice is by design, we acknowledge that error handling can be achieved without it should that be your preference.
The $error variable is one of only a few variables that these standards considers a reserved variable