Practical patterns for common waiting problems. All examples assume import WaitForIt.
Wait for a database record
Wait until a record exists, binding it directly:
{:ok, order} = match_wait({:ok, %Order{}}, Repo.fetch(Order, order_id), timeout: :timer.seconds(5))Or, if you only need to assert it eventually appears:
order = wait(Repo.get(Order, order_id), timeout: :timer.seconds(5))Wait for a record to reach a state
case_wait/3 is a natural fit when a record moves through states and you want to react to each
terminal outcome:
case_wait Repo.reload(job).status, timeout: :timer.seconds(30), interval: 250 do
:completed -> {:ok, job}
:failed -> {:error, :job_failed}
else
status -> {:error, {:timeout, status}}
endWait for a process or named entity to register
Wait for a process to be registered (for example via Registry or a named GenServer):
pid = wait(Process.whereis(MyApp.Worker), timeout: 2_000)match_wait([{pid, _}] when is_pid(pid), Registry.lookup(MyApp.Registry, :worker), timeout: 2_000)Poll an external HTTP endpoint until ready
Useful when bringing up a dependency (a service, a container) that becomes ready asynchronously:
case_wait Req.get(health_url), timeout: :timer.seconds(30), interval: 500 do
{:ok, %{status: 200}} -> :ready
else
_ -> raise "service did not become healthy in time"
endProducer/consumer backpressure with signaling
When you control the producer, signaling avoids polling latency. The consumer blocks until the buffer has enough items; the producer signals after adding to it:
# CONSUMER
messages =
case_wait Queue.get_messages(queue), signal: :queue_filled, timeout: :timer.seconds(30) do
msgs when length(msgs) >= 5 -> msgs
else
msgs -> msgs
end
# PRODUCER
Queue.put(queue, message)
WaitForIt.signal(:queue_filled)See Polling vs signaling for the trade-offs.
Wait for several conditions at once
cond_wait/2 waits until any one of several independent conditions becomes truthy:
cond_wait timeout: :timer.seconds(10) do
File.exists?("ready.flag") -> :ready_by_flag
System.monotonic_time(:second) >= deadline -> :ready_by_deadline
else
:gave_up
endGuard against runaway mailboxes
A real-world use: pause before pushing more work onto a process whose mailbox is filling up:
case_wait Process.info(worker_pid, :message_queue_len), interval: 10, timeout: 60_000 do
{:message_queue_len, len} when len < 500 -> send_next_chunk()
else
{:message_queue_len, len} -> raise "worker overwhelmed (#{len} queued messages)"
endPrevious: Composing waits ยท Next: Telemetry