ctx.waitUntil buys thirty seconds after the response, not forever

Work passed to waitUntil is cancelled 30 s after the response or disconnect, with only a log line to show for it. Longer work belongs in a Queue or a Workflow.

Workers

· Chapter

31

·

2

min read

The answer. ctx.waitUntil(promise) keeps the invocation alive after you return the response, which is what you want for analytics, cache writes and cleanup. It is not a background job runner. For HTTP-triggered Workers the budget is 30 seconds after the response is sent or the client disconnects, shared by every waitUntil in that request; unsettled promises are cancelled and the only trace is a Workers Logs line ("waitUntil() tasks did not complete within the allowed time"). We learned this in our API Worker when a slow image-fallback fetch was quietly killed after the article response had already gone out. If the work can take longer than a few seconds, or must not be lost, put a message on a Queue (delivery is retried) or start a Workflow (durable steps). Cron, Queue and alarm handlers are different: the runtime waits for the handler promise itself, up to 15 minutes.

The pattern.

export default {
  async fetch(request, env, ctx) {
    const data = await handle(request, env);
    ctx.waitUntil(env.CACHE.put("latest", JSON.stringify(data)));       // seconds: fine here
    ctx.waitUntil(logEvent(env, data).catch((e) => console.error(e)));  // never let a leg throw
    await env.JOBS.send({ kind: "render-images", id: data.id });        // minutes: a Queue
    return Response.json(data);
  },
} satisfies ExportedHandler<Env>;

Watch out.

  • Do not destructure it: const { waitUntil } = ctx throws "Illegal invocation" at call time.
  • A rejected waitUntil promise surfaces as an unhandled exception in logs; catch inside, or one leg's failure looks like the request failed.
  • Streaming a response keeps the invocation alive without waitUntil; the 30 s clock starts when the body finishes.

Related: cron-one-tick-many-isolated-legs · queues-batches-acks-and-retries · workflows-step-do-is-an-rpc-receiver