View all results

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.

example.fql
read-only
RETURN getData() ON ERROR RETURN NONE

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.

example.fql
read-only
LET rows = QUERY `.items` IN doc ON ERROR RETURN []
example.fql
read-only
DISPATCH "click" IN element ON ERROR RETURN NONE

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.

example.fql
read-only
LET value = WAITFOR VALUE check() TIMEOUT 5s ON TIMEOUT RETURN NONE ON ERROR FAIL

Retrying on failure

ON ERROR RETRY re-executes the guarded expression a given number of times before giving up.

example.fql
read-only
RETURN fetchData() ON ERROR RETRY 3

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.

example.fql
read-only
RETURN fetchData() ON ERROR RETRY 3 DELAY 100ms BACKOFF EXPONENTIAL
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:

example.fql
read-only
LET base = 100 LET preferredDelay = NONE RETURN fetchData() ON ERROR RETRY 3 DELAY base * 2 OR RETURN NONE RETURN fetchData() ON ERROR RETRY 3 DELAY (preferredDelay OR base) OR RETURN NONE

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.

example.fql
read-only
RETURN fetchData() ON ERROR RETRY 3 DELAY 100ms BACKOFF EXPONENTIAL OR RETURN "unavailable"
example.fql
read-only
RETURN fetchData() ON ERROR RETRY 2 OR FAIL

Handling timeouts

ON TIMEOUT handles timeout failures separately from other errors. It is only valid on WAITFOR expressions that include a TIMEOUT clause.

example.fql
read-only
LET result = WAITFOR VALUE loadStatus() TIMEOUT 10s ON TIMEOUT RETURN "timed out"

ON ERROR and ON TIMEOUT are independent — they can appear together on the same expression, each with its own action.

example.fql
read-only
LET token = WAITFOR VALUE authenticate() TIMEOUT 5s ON TIMEOUT RETURN "timeout" ON ERROR RETRY 2 DELAY 100ms OR RETURN "error"

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.

example.fql
read-only
LET results = (FOR item IN items RETURN process(item) ) ON ERROR RETURN []

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.

example.fql
read-only
LET results = (FOR item IN items RETURN process(item) ) ON ERROR RETRY 1 OR RETURN []

Optional chaining

The optional chaining operator ?. accesses a member on a value that may be NONE. Instead of failing, it produces NONE.

example.fql Ferret v2
query.fql
FQL
LET obj = NONE RETURN obj?.name

It works with computed property names as well.

example.fql Ferret v2
query.fql
FQL
LET obj = NONE LET key = "name" RETURN obj?.[key]

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.

Next steps