Skip to content
LogoLogo

Timing Out

A timeout limits how long a caller waits for work to finish. Effect interrupts the timed computation, waits for its cleanup, and then produces the chosen timeout outcome.

Limiting execution duration

// run: Console.log(buildPreview()); Console.log(Effect.flip(() => previewWithDeadline())._tag)
function buildPreview() {
  sleep("3 seconds")
  Console.log("Preview rendered")
  return "Rendered preview"
}
 
function previewWithDeadline() {
  return timeout(buildPreview(), "500 millis")
}
// string!Cause.TimeoutError!
Preview rendered
Rendered preview
TimeoutError

Effect.timeout adds Cause.TimeoutError to E. A timely success returns its original value, and a domain failure before the deadline keeps its original error. The source call is passed by name because the parameter expects an Effect, so the timer surrounds the work itself; an explicit () => buildPreview() also works.

// run: Console.log(previewLabel())
function previewLabel() {
  try {
    return previewWithDeadline()
  } catch (error) {
    // error._tag is "TimeoutError"
    return "Preview still unavailable"
  }
}
Preview still unavailable

The interruption used internally to stop the source is separate from the typed timeout error returned to its caller. Ordinary catch inside buildPreview cannot consume that interruption.

Current limitation. Timeout waits for the interrupted source's cleanup, but Effect 4.0.5 drops failures from that cleanup at the timeout boundary. This affects timeout, timeoutOption, and timeoutOrElse, including finally and using cleanup. D256 clarifies that D15 binds cleanup VibeLang itself runs; Effect operators keep Effect's semantics under D22, including dropping a loser's cleanup failure.

Representing timeout as absence

// run: Console.log(optionalPreview())
function optionalPreview() {
  const preview = Effect.timeoutOption(() => buildPreview(), "500 millis")
  return preview.isNone() ? "No preview yet" : preview.value
}
No preview yet

timeoutOption returns None for a timeout and Some(value) for a timely success. Expected failures from the source stay in E. It does not treat every failed computation as absence.

Choosing an alternative or custom failure

// run: Console.log(previewOrPlaceholder()); Console.log(Effect.flip(() => requiredPreview("doc-1")).documentId)
function previewOrPlaceholder() {
  return Effect.timeoutOrElse(buildPreview(), {
    duration: "500 millis",
    orElse: () => "Static placeholder"
  })
}
 
error PreviewDeadline {
  documentId: string
}
 
function requiredPreview(documentId: string) {
  return Effect.timeoutOrElse(buildPreview(), {
    duration: "500 millis",
    orElse: () => { throw new PreviewDeadline({ documentId }) }
  })
}
// string!PreviewDeadline!
Static placeholder
doc-1

The alternative runs lazily after the timer wins. It can use services, fail, or return another success type, and those channels join the overall contract. Use a typed failure for an expected deadline; a defect is appropriate only when timing out breaks an invariant.

Interruption of protected work

// run: Console.log(Effect.flip(() => protectedPreview())._tag)
function protectedPreview() {
  return timeout(
    () => Effect.uninterruptible(buildPreview()),
    "500 millis"
  )
}
Preview rendered
TimeoutError

The Preview rendered log appears before the timeout failure because the protected work finishes before interruption takes effect. Uninterruptible work defers cancellation, so this call can take longer than 500 milliseconds. finally and resource releases complete before timeout returns, including releases that take time. Model work that must outlive a request with an explicit fiber lifetime rather than assuming a timeout stops it instantly.


The matching Effect page: effect.website/docs/v4/error-management/timing-out