View all results

Waitfor Expressions

A WAITFOR expression pauses a query until something happens, then resumes and produces a value. It has two modes: waiting for a condition to become true, and waiting for an event to arrive.

Waiting for a condition

In its condition (predicate) mode, WAITFOR re-checks an ordinary expression until it is satisfied or a timeout is reached. This mode is a pure language construct — it needs no special capability.

There are four forms:

Form Waits until Returns
WAITFOR <expr> the expression is true true, or false on timeout
WAITFOR EXISTS <expr> the expression is non-empty according to EXISTS semantics true, or false on timeout
WAITFOR NOT EXISTS <expr> the expression is empty or absent true, or false on timeout
WAITFOR VALUE <expr> the expression yields anything other than NONE that value, or NONE on timeout
example.fql Ferret v2
query.fql
FQL
RETURN WAITFOR EXISTS [1, 2, 3] TIMEOUT 100ms

WAITFOR VALUE treats empty strings, arrays, and objects as valid values and returns them immediately. When you need the candidate itself but also require it to be non-empty, add a WHEN condition:

example.fql
read-only
RETURN WAITFOR VALUE loadItems() WHEN LENGTH(.) > 0 TIMEOUT 5s

The expression is re-evaluated on each attempt, so it can reflect state that changes over time. When the wait runs out, the result reports the timeout rather than raising an error:

example.fql Ferret v2
query.fql
FQL
RETURN WAITFOR FALSE TIMEOUT 50ms EVERY 10ms

Tuning the wait

Several clauses control how the wait behaves:

  • TIMEOUT <duration> — the maximum time to wait, provided as a value coercible to Duration.
  • EVERY <interval> — how often to re-check. A second coercible Duration, EVERY <interval>, <cap>, caps how large the interval can grow. Without this clause, polling defaults to 100ms.
  • BACKOFF LINEAR | EXPONENTIAL | NONE — how the interval between checks grows over time.
  • JITTER <0..1> — randomizes the interval to avoid synchronized retries.
  • WHEN <condition> — an additional condition that must also hold; the candidate value is available as ..
example.fql
read-only
WAITFOR VALUE loadStatus() TIMEOUT 10s EVERY 100ms, 1s BACKOFF EXPONENTIAL JITTER 0.2

TIMEOUT, EVERY, and its cap accept ordinary expressions, so values can be stored or computed:

example.fql
read-only
LET base = 50ms RETURN WAITFOR VALUE loadStatus() TIMEOUT base * 20 EVERY base, base * 4

TIMEOUT, EVERY, and its cap use the canonical Duration conversion rules. Numbers are milliseconds, duration strings may be compound, and singleton lists are converted recursively:

example.fql
read-only
LET timeout = "1s500ms" RETURN WAITFOR FALSE TIMEOUT timeout EVERY 25, [100]

All scheduling results must be non-negative. Conversion failures, overflow, and negative values raise runtime errors.

Recovering from a timeout

By default a timed-out wait returns false (or NONE for the VALUE form). A recovery clause lets you choose a different result.

example.fql Ferret v2
query.fql
FQL
RETURN WAITFOR VALUE NONE TIMEOUT 30ms EVERY 5ms ON TIMEOUT RETURN "gave up"

Use ON ERROR to handle a failure raised while evaluating the condition.

Waiting for an event

In event mode, WAITFOR subscribes to an event source and waits for a matching event, which it returns as a value.

example.fql
read-only
LET event = WAITFOR EVENT "navigation" IN page TIMEOUT 5s

Event timeouts use the same Duration conversion and non-negative scheduling policy as condition waits.

A WHEN filter accepts only events that match a condition. Inside the filter, the incoming event is available as .. Multiple WHEN clauses must all pass.

example.fql
read-only
WAITFOR EVENT "message" IN socket WHEN .type == "data" TIMEOUT 5s

Triggering the event

A TRIGGER clause runs statements after the subscription is set up but before waiting begins. This is how you cause the event you are waiting for without risking a race where it fires before you start listening.

example.fql
read-only
WAITFOR EVENT "response" IN page TRIGGER ( button <- "click" ) TIMEOUT 10s

The trigger body can dispatch events and run other statements. See Dispatch Expressions.

A host capability

Event mode only works when the source is an observable value — one that produces a stream of events, provided by a module or the host application, such as a browser page. Condition mode has no such requirement. See Value Capabilities and Host Values.

Next steps