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}}
end

Wait 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"
end

Producer/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
end

Guard 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)"
end

Previous: Composing waits ยท Next: Telemetry