View all results

Error handling and resilience

Real-world pages are unpredictable. Elements may be missing, networks may be slow, and page structures may change. This guide shows practical patterns for writing extraction scripts that handle these failures gracefully.

For the full syntax reference, see Error Handling.

Return a fallback value

The most common pattern: attach ON ERROR RETURN to provide a default when an expression fails.

terminal
ferret run -e 'LET page = WEB::HTML::OPEN("https://mockery.ferretlang.org")LET title = QUERY ONE ".nonexistent" IN page USING css ON ERROR RETURN NONERETURN title?.textContent'

Common fallback values:

Fallback When to use
NONE Single optional value
[] Expected list that may be empty
{} Expected object with no data
"unknown" Display-safe placeholder

Optional chaining

The ?. operator accesses properties on values that may be NONE. Instead of failing, it returns NONE:

example.fql
read-only
LET page = WEB::HTML::OPEN("https://mockery.ferretlang.org") LET element = QUERY ONE ".maybe-missing" IN page USING css ON ERROR RETURN NONE // Safe — returns NONE if element is NONE LET text = element?.textContent LET href = element?.attributes?.href RETURN { text, href }

Without ?., accessing a property on NONE is a runtime error.

Retry on failure

Use ON ERROR RETRY when a transient failure may resolve on a second attempt:

example.fql
read-only
LET page = WEB::HTML::OPEN("https://mockery.ferretlang.org") ON ERROR RETRY 3

Add a delay between retries

example.fql
read-only
LET page = WEB::HTML::OPEN("https://mockery.ferretlang.org") ON ERROR RETRY 3 DELAY 500ms

Use exponential backoff

example.fql
read-only
LET page = WEB::HTML::OPEN("https://mockery.ferretlang.org") ON ERROR RETRY 3 DELAY 200ms BACKOFF EXPONENTIAL

The delay doubles on each retry: 200ms, 400ms, 800ms.

Fall back after all retries fail

example.fql
read-only
LET page = WEB::HTML::OPEN("https://mockery.ferretlang.org") ON ERROR RETRY 3 DELAY 500ms BACKOFF EXPONENTIAL OR RETURN NONE RETURN page != NONE ? page.title : "page unavailable"

Handle timeouts

WAITFOR expressions support ON TIMEOUT RETURN for a timeout-specific fallback. This is separate from ON ERROR:

example.fql
read-only
LET page = WEB::HTML::OPEN("https://mockery.ferretlang.org", { driver: "cdp" }) LET result = WAITFOR VALUE QUERY ONE ".slow-loading" IN page USING css TIMEOUT 5s ON TIMEOUT RETURN NONE ON ERROR RETURN NONE RETURN result?.textContent

Extract with fallback selectors

When a site changes its layout, old selectors may stop working. Try multiple selectors and use the first one that matches:

example.fql
read-only
LET page = WEB::HTML::OPEN("https://mockery.ferretlang.org") LET title = QUERY ONE ".new-title" IN page USING css ON ERROR RETURN NONE LET titleFallback = title == NONE ? (QUERY ONE ".old-title" IN page USING css ON ERROR RETURN NONE) : title RETURN titleFallback?.textContent

Or with a function for reuse:

example.fql
read-only
FUNC queryFirst(page, selectors) { FOR selector IN selectors LET result = QUERY ONE selector IN page USING css ON ERROR RETURN NONE FILTER result != NONE LIMIT 1 RETURN result } LET page = WEB::HTML::OPEN("https://mockery.ferretlang.org") LET title = FIRST(queryFirst(page, [".new-title", ".old-title", "h1"])) RETURN title?.textContent

Protect a loop from individual failures

When iterating over items, a single failure in one item should not stop the entire extraction. Use ON ERROR RETURN on individual operations, or wrap the whole loop body:

example.fql
read-only
LET page = WEB::HTML::OPEN("https://mockery.ferretlang.org") LET items = page[~ css`article`] FOR item IN items LET title = item[~? css`h2`]?.textContent LET link = item[~? css`a`]?.attributes?.href LET description = item[~? css`p`]?.textContent RETURN { title: title != NONE ? title : "untitled", link, description }

To catch and skip items that fail entirely, wrap the loop body in a grouped expression:

example.fql
read-only
LET page = WEB::HTML::OPEN("https://mockery.ferretlang.org") LET items = page[~ css`article`] FOR item IN items LET result = ({ title: item[~? css`h2`]?.textContent, link: item[~? css`a`]?.attributes?.href }) ON ERROR RETURN NONE FILTER result != NONE RETURN result

Combine error and timeout handling

A common pattern for browser-backed extraction: retry on error, provide a timeout fallback, and use optional chaining throughout:

example.fql
read-only
LET page = WEB::HTML::OPEN("https://mockery.ferretlang.org", { driver: "cdp" }) ON ERROR RETRY 2 DELAY 1s BACKOFF EXPONENTIAL OR RETURN NONE LET loaded = page != NONE ? (WAITFOR EXISTS QUERY ONE ".content" IN page USING css TIMEOUT 10s ON TIMEOUT RETURN FALSE) : FALSE RETURN loaded ? { title: page?.title, items: page[~ css`.content .item`][* RETURN { name: .?.textContent } ] } : { error: "page unavailable" }

Next steps