skip to content

In a web framework, what does it mean for a request handler to return a future, promise or coroutine instead of a finished response?

level: juniorimportance: must knowfreq 70%

answer

  1. a claim ticket, not the goods
  2. handler returns before the body exists
  3. framework subscribes to the handle
  4. written on completion, not on return

basics

~20 s

The handler returns a handle to a response that does not exist yet. The framework leaves the exchange open, attaches a completion callback, and writes status, headers and body only once that handle completes with a value.

solid answer

~40 s

A value-returning handler computes the response and the framework serializes it the moment the call returns. An awaitable-returning handler instead starts the work and hands back a `future`, `promise` or suspended coroutine, then returns almost immediately. The framework recognises that return as a handle rather than a body, keeps the exchange open with nothing yet written to the socket, and subscribes to its completion. When the handle completes with a value, that value goes through the same return-mapping the framework applies to a synchronous return — negotiation, serialization, default status. When it completes with a failure, the framework routes it into its error stage. Returning a handle changes *when* the response is produced, not what the client sees.

go deeper

for a junior

Recall the one-line shape: the handler gives back a handle to a response that is not ready, and the framework fills in the response when that handle completes.

for a middle

Explain the mechanics: the framework recognises the awaitable return, attaches a completion callback, keeps the exchange uncommitted, then applies the ordinary return-mapping to the completed value or the error mapping to a failure.

for a senior

Show you can read the symptoms in production — an immediate empty success followed by late log lines, or failures appearing as unhandled errors rather than error responses, both point at a handle that was created but never returned.

for a principal

Frame it as a capacity decision rather than a style one: the handle buys in-flight concurrency, not per-request latency, so the payoff depends on how much of your request time is spent waiting rather than computing.

## Two shapes a handler can have A handler is the function a framework calls once routing has picked it. On most server-side frameworks it can be written in one of two shapes. - **Value-returning.** The handler computes everything it needs and returns the response itself — a body model, a status, or a response object. The framework converts that return into bytes as soon as the call returns. - **Awaitable-returning.** The handler starts the work and returns a *handle* to a result that does not exist yet: a **future**, a **promise**, or a coroutine that will suspend at its first wait point. The handle is a subscription, not a body. | | value-returning handler | awaitable-returning handler | |---|---|---| | what the return value is | the response, or the model for it | a handle to a future response | | when the response is written | when the handler returns | when the handle completes | | while the work is outstanding | control is still inside the handler | the handler has already returned | | where a failure surfaces | thrown out of the call | a failed completion of the handle | | what ends the exchange | the return | the completion callback | ## What the framework does with the handle 1. Routing picks the handler and the framework calls it. 2. The handler starts its work and returns a handle, usually within microseconds. 3. The framework recognises the return as awaitable — from the declared return type, from the registration style used to install the handler, or because every handler on that surface is awaitable — and does **not** try to serialize the handle itself as a body. 4. It attaches a completion callback and leaves the exchange open. Nothing has gone to the socket yet, so status, headers and body are all still changeable. 5. On successful completion it takes the completed value and runs it through the same return-mapping a synchronous return would get: content negotiation, serialization, a default status when none was set. 6. On failed completion it hands the failure to the error stage rather than the serializer. ## Completion carries a value or a failure An awaitable settles exactly once, in one of two ways. A **success** carries the value the handler would otherwise have returned, and the framework converts it normally. A **failure** carries an error, and the framework's error mapping — the same stage that turns a thrown error into a status and a body — gets it instead. Both paths end with the framework, not the handler, writing the response; the handler's job finished the moment it returned the handle. A third outcome is possible and is the dangerous one: the handle **never settles**. There is then no completion callback to fire, so the framework has nothing to write and the exchange simply stays open, holding whatever per-request state it allocated, until some unrelated mechanism tears it down. ## What returning a handle does not change - It does **not** make a blocking call non-blocking. Blocking work inside the handler still occupies whatever thread is running it; wrapping it in a handle only changes the shape of the return. - It does **not** change what the client sees. The protocol carries a status line, headers and a body; it has no notion of how the server produced them. - It does **not**, by itself, speed up a single request. The latency of one request is still dominated by its slowest dependency; what changes is how many requests the process can hold in flight while waiting. - It does **not** exempt the result from validation, negotiation or serialization — the completed value meets the same rules as any return. ## Frameworks differ in how they recognise it Frameworks differ here, and it is worth knowing which style you are in: some inspect the handler's declared return type and adapt whatever awaitable shape they find; some require the handler to be installed through a separate registration so the framework knows in advance; and some make every handler awaitable so the question never arises. The observable behaviour — response written on completion, failure routed to the error stage — is broadly the same across all three. ## Symptoms that you got the shape wrong - The client gets an immediate empty success while log lines from the handler keep arriving afterwards: the handle was created but not returned, so the framework thought the handler was done. - Failures show up as unhandled-error log entries instead of error responses: the failure settled a handle nobody was subscribed to. - Throughput does not improve after a conversion: something on the path still blocks while the handle is outstanding.

  • How does the framework know a handler wants this treatment rather than serializing the handle as a body?
    By the registration style or the declared return type. Some frameworks inspect what the handler declares it returns and adapt any awaitable shape they recognise; others require the handler to be installed through a separate registration; others make every handler awaitable. If none of those signals is present, the framework is likely to treat the handle as an ordinary value and serialize it, which typically ships the wrapper's internals or an empty object.
  • If the handler has already returned, why can the framework still set the status code?
    Because nothing has been written to the socket yet. The response is committed at the first flush of bytes, not at the handler's return, so between the return and the completion the status and headers are still mutable. That is exactly the window the framework uses to map a failed completion onto an error status.
  • Does an awaitable-returning handler make an individual request faster?
    No. One request still waits for its own dependencies, and a handle adds a small amount of scheduling overhead. The benefit is concurrency: while a request waits, the resource that would have been pinned to it is free to carry other requests, so the process holds far more requests in flight at the same total latency.

It is a claim ticket at a counter. The handler hands back a ticket rather than the parcel, and the counter stays open until the ticket is redeemed — at which point the clerk, not you, packs and sends the parcel.

saying these in an interview costs you the question

  • Thinks returning a handle makes a blocking call non-blocking
  • Believes the response is written when the handler returns
  • Says the framework waits on the handle before continuing
  • Claims an awaitable handler makes each request faster
  • Forgets a failed handle still needs error mapping
  • Thinks the client can tell which handler shape was used