Skip to main content
Version: 2.5

Error Handling

As a developer, when building applications we need tools to handle errors that could pop up from different processes (eg: connection issues, unavailable service, users doing unexpected manipulations, etc.). Like any programming languages, Olympe brings tools to deal with errors that would be thrown from a brick and tools to throw custom errors.

Introduction

Before we start, let's have a look at some of the big categories of errors that exist:

  1. Syntax errors: those errors happens in code only, a wrong variable name, a misplaced parenthesis, etc.
  2. Logical errors: the actual logic put in place is wrong, the output is not what is expected
  3. Runtime errors: something unexpected happened, often in an asynchronous action, and leads to the App not working anymore

Syntax errors should be handled by the developer by testing its functions and actions, the use of a linter can help a lot. Logical errors can be tackled by using the DRAW Debugger and the browser developer tools as well.

Both the mentioned above error types are part the App development phase. However, the third type, runtime errors, is very different: they happen during the App usage, at any time, and they cannot be known in advance: what the user typed could not be in the right format, the server could be unreachable or the network is bad, etc.

In Olympe, we use a very powerful tool to tackle runtime errors: Exception

And in this page we will see how DRAW allows us to use exceptions to handle runtime errors in a simple yet powerful way.

Exception handling is the process of responding to the occurrence of exceptions (anomalous or exceptional conditions requiring special processing) -- Wikipedia

A simple example

We start with a very simple example to showcase the overall process.

Let's create an action called Simple Example with the following content:

Now let's test it by using the "Run" button located at the top-right of the editor. If we don't touch the error message input, as expected we can see the output Control Flow getting triggered with a new value:

However if we re-run the test window and we put something in the error message, for example "Error occured!", then we execute the Throw Error brick and we see that the output Error Flow is getting a value:

You are probably wondering how this is possible?

  • The output Error Flow of Throw Error is not linked to the Error Flow of Simple Example!
info

Error Flow is actually a very special type in the Olympe ecosystem, it represents an exception. When a brick has an Error Flow as an output, it means that this brick may throw an exception and has a way to handle it.

Then why is it not linked? The answer is simple: an exception, when thrown, breaks the normal flow of the App. And it goes up the running contexts until it meets an Exception handler.

This is probably a bit fuzzy right now, don't worry it will be clearer soon.

The Throw errors switch

If you tried the example by yourself, you should have seen this switch activating by itself when dropping the Throw Error brick in the action editor:

If we toggle it off, we see the output Error Flow disappearing. Now what happens if we run the action with an error? If we try it we see that the output Control Flow is not triggered as expected, but where is our exception? Did it not throw? Of course the response is no, to see it we need to open the console of the browser:

[OLYMPE.SC]: An error occurred in a brick: Error occured!
Error found in:
> Throw Error (016eeb5ede9f3094d8fa)
> Simple Example (018ba7a026d1f5abf804)

This is not a correct contract for our Simpe Example brick, a developer that uses this brick should know that it may throw an exception, and not leave it hidden. So how could we handle the exception directly in the Simple Example brick? Let's see!

Catching errors

If we don't want Simple Example to throw an error, then we need to handle it directly. To achieve this we use the brick Catch Errors:

When running this version, even with an error message, we see that the output Control Flow is triggered. And in the console we see the expected log: The error was handled successfully: "Error occured!".

We successfully handled (catched) our error (exception)!

We'll see how all those cases work in the How does the Error Handling work? section below.

Recap: Throw Error and Catch Errors

To wrap up this simple example, let's do a quick recap:

  1. Exceptions are the way Olympe handles runtime errors
  2. Exception handling is the act of processing correctly those exceptions
  3. Throw Error allows us to throw an exception in an action
  4. The Throw errors switch indicates in the action contract that an exception may arise
  5. Catch Errors allows to handle an exception at any point of the logic
  6. Error Flow is the exception type in Olympe
  7. An exception goes up the execution contexts until it meets an handler

Handle error in an UI App

We saw in the previous section how to throw and catch errors in the logic part, this can be useful in a backend app for example. But how do we handle those errors in a UI App? This is the purpose of this section.

To demonstrate it, we'll use a Visual Component called Bad Button that has error message as a property and that calls Throw Error when we click on it:

Now let's create an UI App and put a few bad buttons in it:

Now run the app, open the console and try clicking on the buttons. We can see that the errors are shown in the console like this:

[OLYMPE.SC]: An error occurred in a brick: Oh no!
Error found in:
> Throw Error (016eeb5ede9f3094d8fa)
> New Button_On Click (018ba80f76973c75997d)
> New Bad Button 2 (018ba7efe3affddb70d2)
> Home screen (018b846acba6fbe91113)
> UIApp Screenflow (018b846acba5f2b29f12)
> Error Handling (018b846acba552266e2f)

Now instead of a console error, how do we capture the error to show it to the user for example?

We can do it by creating an error handler on the screen level:

  1. Select the Home screen in the screen layers
  2. Go to the Interactions properties
  3. Click on On Error (below On Load)

By doing this we effectively created an error handler! Now let's put something in it and try clicking the buttons again:

We caught the errors correctly, and did something with it!

Nothing was said about the stack yet, it represents basically where the error occured. This is very useful as a developer to know why your application failed in some instances:

Error found in:
> Throw Error (016eeb5ede9f3094d8fa)
> New Button_On Click (018ba80f76973c75997d)
> New Bad Button 2 (018ba7efe3affddb70d2)
> Home screen (018b846acba6fbe91113)
> UIApp Screenflow (018b846acba5f2b29f12)
> Error Handling (018b846acba552266e2f)

The exception was thrown by the brick Throw Error, Throw Error was used in the On Click of New Button, by New Bad Button 2 in the Home screen, Home screen which is part of the UI App, etc...

Hierarchical error handling

One power of exceptions is that we can choose where we want to catch them. We could have defined our On Error in the Bad Button visual component directly for example. Basically anywhere in the UI tree we can define an error handler, to showcase this let's create 2 more bad buttons and put them in a group. Then we define a new error handler on the group:

This can be useful in many situations, greatly depending on the app itself.

Errors filtering

Another way to specialize your error handling is by using error filtering.

Let's add a error code property in the Bad Button component, and use it in the On Click. Then in the UI App we change the group On Error:

Also change the second bad button to use the code 1:

(we show the error code in parenthesis)

Now if we click the buttons in the group: we see that the exceptions with the code 1 is caught by the group, and the exception with the code 0 is caught by the screen:

info

Catch Error Type allows to catch exceptions with a specific code, whereas Catch Errors catches all errors regardless of their codes.

Global handling of errors

You are maybe wondering what happens to exception that goes under the radar? No Catch Error Type handles its code and there is no Catch Errors in the hierarchy.

In that case the Olympe runtime provides a default global handler that shows the error in the console. Each time in this page we had errors shown in the console, it was this default global handler!

DRAW provides an easy way to replace this global handler, simply define an error handler at the Screenflow level:

How does the Error Handling work?

As seen, any thrown exception cuts the current execution flow of the app and starts going up the context tree until it finds an error handler.

info

If you are not familiar with the concept of context tree, you can read DRAW Contexts and Brick Contexts.

To put this in image, let's say we have an app like this:

My UI App (with On Error for codes 1)
Home screen
My Visual Component
---
My Visual Component (with On Error for codes 2)
Button
On Click
API Call
---
API Call
Throw Error (may be code 1, 2 or 3)

Let's see what happens if the code thrown is 1:

  1. Call API throws an error with code 1
  2. The exception goes up until the My Visual Component's On Error
  3. The exception is not handled, so it continues until the next On Error (My UI App)
  4. My UI App's On Error handles the error

Let's see what happens if the code thrown is 2:

  1. Call API throws an error with code 2
  2. The exception goes up until the My Visual Component's On Error
  3. The exception is handled

Let's see what happens if the code thrown is 3:

  1. Call API throws an error with code 3
  2. The exception goes up until the My Visual Component's On Error
  3. The exception is not handled, so it continues until the next On Error (My UI App)
  4. My UI App's On Error doesn't handle the error neither
  5. The error continues going up the context tree
  6. No error handler did handle the error, so the default global handler does it (print in the console)

Those examples were with the On Error in a UI tree, the concept is exactly the same for logic context tree (using Catch Error Type in an action directly for example).

How about CODE?

A quick note about CODE and exceptions: all thrown exceptions in your bricks are handled by the Olympe runtime like you do in DRAW. Actually the brick Throw Error does just that!

So if you have to throw exception or already do, you are covered:

import { ActionBrick, ErrorFlow } from 'olympe';
/* ... */
update($, inputs, [forwardEvent, setErrorFlow]) {
// Solution 1: throw JS error (no filtering possible!)
throw new Error(message);

// Solution 2: throw ErrorFlow
throw ErrorFlow.create(message, code);

// Solution 3: setErrorFlow (legacy)
setErrorFlow(ErrorFlow.create(message, code));
return; // for breaking the flow

// Solution 4: use the BrickContext
$.throw(new Error(message)); // no filtering possible!
$.throw(message); // no filtering possible!
$.throw(ErrorFlow.create(message, code));
}

When using synchronous code or async - await syntax that preserves the javascript execution stack, the following form will be caught by the runtime with the right context. This works also when the javascript runtime throws an error (eg: when calling a method on undefined).

throw ErrorFlow.create(msg, code);

The main advantage of this is that it breaks the execution flow (like return) would do.

The last form $.throw(ErrorFlow.create(message, code)) is mainly useful when using Promises (asynchronous flow) because it ensures to keep the context stack:

Promise.all([query1, query2]).catch(err => $.throw(err.message, 0));

So using the Promise.then().catch() syntax, you must always use $.throw() to ensure that the error is properly handled.