Error Handling
FQL does not use try/catch blocks. Instead, a recovery tail attaches directly to the expression that might fail. The tail tells the runtime what to do when a failure occurs — return a fallback value, retry the operation, or propagate the error.
For member access on values that may be NONE, FQL provides the optional chaining operator (?.), which returns NONE instead of failing.
Returning a fallback value
The most common recovery is ON ERROR RETURN, which catches a runtime failure and produces a fallback value instead.
The fallback expression is only evaluated when the guarded expression fails. Any expression can serve as the fallback — a literal, a variable, a function call, or a collection.
Propagating errors
By default, a runtime failure propagates up and halts evaluation. ON ERROR FAIL makes that behavior explicit. It is useful when a recovery tail is required for readability or when paired with a separate ON TIMEOUT clause.
Retrying on failure
ON ERROR RETRY re-executes the guarded expression a given number of times before giving up.
The retry count is the number of additional attempts after the first failure. If all retries are exhausted and the expression still fails, the final error propagates.
Delay and backoff
A DELAY clause adds a pause between retries. An optional BACKOFF strategy controls how the delay grows.
| Strategy | Behavior |
|---|---|
CONSTANT |
every retry waits the same duration |
LINEAR |
the delay grows by a fixed increment each retry |
EXPONENTIAL |
the delay doubles each retry |
BACKOFF requires DELAY. Without BACKOFF, the delay is constant.
DELAY accepts any value supported by the canonical Duration conversion. Numbers are milliseconds, duration strings may be compound, and singleton lists are converted recursively. The converted delay must be non-negative; conversion failures, overflow, and negative values raise runtime errors.
Because the unparenthesized OR token begins the retry fallback, wrap a logical OR used inside the delay expression in parentheses:
Fallback after retries
When all retries are exhausted, OR RETURN provides a fallback value instead of propagating the final error. OR FAIL makes propagation explicit.
Handling timeouts
ON TIMEOUT handles timeout failures separately from other errors. It is only valid on WAITFOR expressions that include a TIMEOUT clause.
ON ERROR and ON TIMEOUT are independent — they can appear together on the same expression, each with its own action.
A timeout is not retried by ON ERROR RETRY. The two handlers apply to different failure kinds.
Grouped expressions
Any expression can be wrapped in parentheses to attach a recovery tail. This is how you add error recovery to constructs that do not accept recovery tails directly, such as FOR loops.
Retry works on grouped expressions too. When a grouped FOR is retried, the loop restarts from the beginning — partial results from a failed attempt are discarded.
Optional chaining
The optional chaining operator ?. accesses a member on a value that may be NONE. Instead of failing, it produces NONE.
It works with computed property names as well.
Without ?., accessing a member on NONE is a runtime error.
Optional chaining applies only to member access. func?() and arr?[0] are not supported — use ON ERROR RETURN or a grouped expression instead.
Where recovery applies
| Construct | Recovery tails | Optional chaining |
|---|---|---|
| Function calls | func() ON ERROR ... |
— |
| Member access | obj.prop ON ERROR ... |
obj?.prop |
QUERY |
QUERY ... ON ERROR ... |
— |
DISPATCH |
DISPATCH ... ON ERROR ... |
— |
WAITFOR |
ON ERROR ..., ON TIMEOUT ... |
— |
Grouped (...) |
(...) ON ERROR ... |
— |
Each expression may define ON ERROR at most once. WAITFOR expressions may additionally define ON TIMEOUT at most once. RETRY and its OR fallback are only available under ON ERROR.