A New Way to Handle Errors

We’ve recently introduced a new error-handling mechanism using the new throw() command.
This marked the first step towards a new error handling system, positioned as close as possible to the code generating them.
In the next phase with 4D v20 R4, we’re expanding this approach to intercept errors during expression execution.
Let’s delve into the details.

try(), a new helpful keyword

With the introduction of 4D v20 R4, developers can now access a useful new keyword: Try(Expression). The Try() keyword enables developers to execute an expression enclosed in parentheses and seamlessly intercept any errors thrown during its execution. These errors can be handled using the Last errors command immediately after the expression execution. This allows you to define your error management as close as possible to the code that generated them and eventually use local variables.

Sample Scenarios before detailed explanations

Division by zero

Consider a simple scenario: division by zero. The following sample demonstrates an Euclidean division method that throws an error and arbitrarily returns zero if the divisor is zero. With Try(), developers can manage errors more elegantly:

#DECLARE($dividend : Real; $divisor : Real)->$result : Real
If ($divisor=0)
  $result:=0
   throw(-12345; "Division by zero!")
Else
  $result:=($dividend/$divisor)
End if

$result:=Try(division($dividend; $divisor))
If (Last errors#Null)
  // Error management
  logErrors(Last errors)
End if

ACCESSING a Document

Managing errors when accessing a document becomes more straightforward if the document doesn’t exist.
To prevent the 4D error dialog from being displayed, an error handler needed to be installed, and the error management code had to be written within this error handler:

$previousErrorHandler:=Method called on error
ON ERR CALL("errorOpenDoc")
var $fileHandle : 4D.FileHandle:=File($path).open()
If ($fileHandle#Null)
  ON ERR CALL("errorReadDoc")
  $text:=$fileHandle.readText()
  If (Last errors#Null)
    $text:="Error reading the file"
  End if
End if
ON ERR CALL($previousErrorHandler)

With the Try() keyword, everything can be written in the same section of code, accessing local variables and then simplifying readability:

var $fileHandle : 4D.FileHandle:=Try(File($path).open())
If ($fileHandle#Null)
  $text:=Try($fileHandle.readText()) || "Error reading the file"
End if

ORDA save function

Another robust use case involves the ORDA save() function, which can generate predictable and unpredictable errors.
Predictable errors, like entity lock, can be directly handled through the result success attribute. However, unpredictable errors, such as primary key duplicates, required an error handler installed with the ON ERR CALL command; otherwise, the 4D error dialog would be displayed:

var $customer : cs.CustomerEntity
// Do something with the $customer entity
$previousErrorHandler:=Method called on error
ON ERR CALL("errorSaveManagement")
$result:=$customer.save()
ON ERR CALL($previousErrorHandler)
If (Not($result.success))
  Case of
    : ($result.status=3)
      // Locked entity management
    : ($result.status=4)
      // Other error management
  End case
End if

But now, the Try() keyword becomes a valuable ally in handling both predictable and unpredictable errors without showing the 4D error dialog:

var $customer : cs.CustomerEntity
// Do something with the $customer entity
$result:=Try($customer.save())
If (Not($result.success))
  Case of
    : ($result.status=3)
      // Locked entity management
    : ($result.status=4)
      // Other error management
  End case
End if

Expression Execution

The Try() keyword accepts any 4D expression between the parentheses, be it variables, assignments, methods, functions, or more. In case of an error, the execution flow stops and returns to the latest Try() keyword encountered (the first found back in the call stack). As a result, you just have to check the error stack after the expression execution to know if errors occurred.
Note that if an error is generated using the throw() command in deferred mode, the execution flow continues until the end of the current method/function execution, providing a simple way to manage error propagation.

Results

On successful execution, Try() returns the result of the expression.
In case of an error, it returns the latest result of the expression or ‘undefined’ if unavailable.
If the expression defines no result (e.g., a method or command with no result), the try() keyword returns ‘undefined.’

About the Error Stack

The current error stack is cleared before the expression is executed, offering a clean slate for error handling within the Try() context. Errors encountered during expression execution are added to the error stack, providing developers with comprehensive information for debugging.

Error Handling

If no current error handler is defined using the ON ERR CALL command, the error dialog won’t disrupt the user experience using Try().
Global and local error handlers set before entering the Try() block won’t be called, ensuring a clean separation between the try context and the rest of the code.
If the expression defines a global or local error handler for the current database, it will be invoked when an error occurs during execution.

Explore New Horizons in Error Management

This new feature opens up a world of possibilities for error management. But it’s only the second step in the new 4D error management era that opens the way to multi-line try-catch blocks!
Share your thoughts and experiences on our forum, and let us know what you think about this new feature.

Happy coding!

Avatar
• Product Owner •Damien Fuzeau has joined the 4D Product team in February 2019. As a Product Owner, he is in charge of writing user stories, then translating them to functional specifications. His job also entails making sure that the feature implementations delivered are meeting the customer needs.Damien is graduated from the University of Nantes in software engineering. He spent more than 23 years in its former company, first as developer (discovering 4D in 1997), and later as engineering manager and software architect. This company is a 4D OEM partner and deployed 4D based business softwares for thousands users, on hundreds servers. So, Damien is used to 4D development and deployment in a multi-language context.