flow/poll
Canonical reference for flow/poll: bounded polling loops for external async work using deterministic orchestration helpers.
Quick Answer
Use flow/poll when you need to repeatedly check external job status. Always bound it with :timeout or :max-attempts, and define success with :return-on.
When To Use
| Scenario | Why flow/poll fits |
|---|---|
| You start a remote job and must check status until completion. | Encodes bounded retry loop with clear completion criteria. |
| Readiness is determined by response fields, not callback push. | Keeps polling logic in one deterministic config map. |
| You need deterministic retry/backoff without hand-written loop boilerplate. | Uses first-class bounds/backoff and standardized semantics. |
Prefer :wait when an external actor pushes a callback/signal (human approvals, webhook completions).
Config Shape
flow/poll requires a literal config map.
(flow/poll
{:interval "10s"
:timeout "20m" ;; or :max-attempts
:backoff {:type :exponential :factor 2 :max "2m"}
:abort-on {:status #{400 401 403} :error? true}
:return-on (fn [result] (= :completed (:status result)))
:id :order-status-poll-sleep}
(flow/step :http :fetch-job-status
{:connection :jobs-api
:method :get
:path (str "/jobs/" job-id)}))
Important fields:
| Field | Required | Meaning |
|---|---|---|
:interval | Yes | Poll interval ("10s" style duration or milliseconds). |
:timeout | Conditionally | Duration/ms timeout cap. |
:max-attempts | Conditionally | Attempt cap. |
:timeout or :max-attempts | At least one | At least one bound is required. |
:return-on | Yes | Predicate function/symbol for success completion. |
:abort-on | No | Stop condition for statuses/errors. |
:backoff | No | Interval progression (:constant, :linear, :exponential). |
:id | No | Stable generated sleep step id label. |
High-Level Implementation Model
flow/poll is expanded before execution into ordinary orchestration primitives:
| Runtime behavior | Primitive |
|---|---|
| Capture poll start time | flow/now-ms |
| Track attempts | loop / recur |
| Execute poll body | normal flow/step body |
| Evaluate completion condition | :return-on predicate |
| Apply stop rules | abort conditions + bounds |
| Sleep between attempts | flow/step :sleep |
| Compute next interval | flow/backoff |
| Enforce timeout | flow/elapsed? |
Practical implication: you get a reusable polling primitive built from the same deterministic constructs you use directly.
Advanced Pattern: Poll + Persist + Compact Output
'(let [input (flow/input)
started (flow/step :http :start-export
{:connection :reports-api
:method :post
:path "/exports"
:json {:account-id (:account-id input)}})
job-id (:job-id started)
final-status ^{:label "Wait for export completion"}
(flow/poll
{:interval "5s"
:timeout "15m"
:backoff {:type :exponential :factor 1.5 :max "60s"}
:abort-on {:status #{400 404 500} :error? true}
:return-on (fn [r] (= :completed (:status r)))
:id :export-status-sleep}
(flow/step :http :get-export-status
{:connection :reports-api
:method :get
:path (str "/exports/" job-id)}))
artifact (flow/step :function :persist-export-status
{:input final-status
:persist {:type :blob}})]
{:job-id job-id
:status (:status final-status)
:status-ref (:ref artifact)})
This keeps orchestration clear and avoids large inline payloads while polling.
Using Implicit Flow Context In Custom Patterns
flow/poll body runs inside your flow orchestration context, so it can safely use:
| Context source | Typical use |
|---|---|
Previously bound let values | Reuse ids, cursors, and guard flags. |
| Connection/binding-driven configs | Keep auth/routing in profile bindings. |
| Deterministic helper outputs | Reuse flow/now-ms, persisted refs, and stable keys. |
Patterns that work well:
| Pattern | Shape |
|---|---|
| Staged polling | Fast initial poll, then slower secondary poll branch. |
| Status-class routing | Poll to terminal status, then branch with labeled if/cond. |
| Child-flow health polling | flow/call-flow + status poll in parent orchestrator. |
Validation Rules You Should Expect
| Rule | Validation expectation |
|---|---|
| Config shape | Must be a literal map. |
:return-on | Required and must be function/symbol. |
:timeout | Must parse as duration/ms when provided. |
| Linear backoff | :backoff {:type :linear} requires :step. |
| Bounded execution | At least one bound is required (:timeout or :max-attempts). |
See platform caps in Limits And Recovery.