skip to content

How does an OData 4.01 service run a long $batch asynchronously, and what may the client see before the whole batch has finished?

level: seniorimportance: nice to knowfreq 4%

answer

  1. a preference on the outer POST
  2. 202 and a status monitor
  3. interim results plus a continuation
  4. never a 202 inside a change set

basics

~20 s

With respond-async on the batch POST, the service may answer 202 Accepted with a status-monitor Location. Polling it can yield interim results: a multipart response ending in a 202 part, or a JSON response with nextLink. Change sets never return 202.

solid answer

~50 s

The `respond-async` preference on a `$batch` request applies to the batch as a whole and does not cascade to the requests inside it. If the service honours it, it answers `202 Accepted` with a `Location` naming a status monitor; once the batch finishes, a `GET` on the monitor returns `200 OK` with the full batch response. Before then the service MAY return **interim results**: in the multipart format, a partial response whose last part is a `202 Accepted` with a new monitor `Location`; in the 4.01 JSON format, the responses so far plus a `nextLink`, whose `GET` may yield another `202`. Because a change set is atomic, `202 Accepted` MUST NOT appear within one. Only in a JSON batch may an individual request that specifies `respond-async`, has no dependents and sits outside any atomicity group run asynchronously on its own, reported as a response object with status `202`.

go deeper

for a junior

Recall that a client can ask for a whole OData batch to run asynchronously and then poll a status monitor for the result.

for a middle

Explain the 202 with a monitor Location, the final 200 with the batch response, and why change sets never report 202.

for a senior

Show a polling loop that follows interim results in both formats and handles per-request 202 objects in a JSON batch.

for a principal

Weigh one long asynchronous batch against several smaller synchronous ones: latency to first result, partial progress and how much state the client must track.

## Asking for the whole batch to run asynchronously A client that expects a long batch adds the `respond-async` preference to the `Prefer` header of the outer `POST` to `$batch`. The OData protocol says that on a batch request the preference applies to **the batch as a whole**, not to the individual requests inside it; it does not cascade. The service MAY honour it, and if it does, it MUST say so in a `Preference-Applied` response header. An honoured request is answered with `202 Accepted` and a `Location` header naming a **status monitor** resource, optionally with `Retry-After`. Polling the monitor follows OData's general asynchronous-request rules: another `202` while work continues, and `200 OK` with the full batch response once it is done. For OData 4.01 responses that final `200 OK` carries an `AsyncResult` header; when the polling request accepts `application/http`, or comes from a 4.0 client with no `Accept` header, the batch response is instead wrapped as a complete HTTP message. ## How this fits the 200 OK rule A synchronous batch is answered `200 OK` as soon as its headers are valid, whatever happens to the requests inside it. The asynchronous path is the one case where the batch itself is answered `202 Accepted`, and only because the client asked: the protocol says a service MUST NOT reply `202 Accepted` to a request that did not include `respond-async`. A client that never sends the preference never has to handle a batch-level `202`. ## Interim results A service MAY return results for the requests it has already finished before the whole batch is done. The two formats signal "there is more" differently. | | Multipart format | JSON format (OData 4.01) | |---|---|---| | What the monitor returns | `200 OK` with a multipart batch response holding the finished parts | `200 OK` with a JSON batch response holding the finished response objects | | The "more to come" signal | the **last part** is a `202 Accepted` with a new monitor `Location` | the response carries `nextLink` control information | | How the client continues | `GET` the `Location` from that last part | `GET` the next link, which MAY answer `202 Accepted` with a new monitor `Location` | Interim results are not failures. A trailing `202` part or a `nextLink` means "poll again", and the remaining requests arrive in later responses. ## Atomic units stay atomic Asynchronous processing never splits an all-or-nothing unit. - **Change sets.** Because a change set is executed atomically, `202 Accepted` MUST NOT be returned within a change set. - **Individual requests in a multipart batch.** The protocol ties this to the interim-results signal: a multipart batch may end its response with a `202 Accepted` part, therefore `respond-async` MUST NOT be applied to individual requests within a batch whose response is multipart, and the service MUST ignore it on them. - **Individual requests in a JSON batch.** Here the rule relaxes. A request that specifies `respond-async`, that no other request depends on and that is not part of an atomicity group MAY be executed asynchronously when the service answers with a JSON batch response. Its response object then has status `202`, a `location` header pointing to that request's own status monitor and optionally a `retry-after` header, while the batch itself may complete synchronously. ## A client's polling algorithm 1. Send the batch with `Prefer: respond-async`. Optionally add `wait`, which on a batch bounds how long the client will wait for the entire batch before the service switches to an asynchronous answer. 2. If the answer is `200 OK`, the batch ran synchronously: read it as usual. If it is `202 Accepted`, poll the `Location`, honouring `Retry-After`. 3. When the monitor returns `200 OK`, read the batch response inside it and check for a continuation: a trailing `202` part (multipart) or `nextLink` (JSON). 4. Follow continuations until none remains, correlating results by `Content-ID` or `id`. 5. In a JSON batch, poll each per-request `202` object's own `location` separately. ## Pitfalls - Assuming `respond-async` on the batch makes every inner request asynchronous. - Treating a trailing `202` part as the last request's failure, or as the end of the batch. - Expecting a change set or atomicity group to report `202` and finish later. - Sending `respond-async` on inner requests of a multipart batch and expecting per-request monitors.

  • How do the respond-async and wait preferences combine on an OData $batch request?
    On a batch, `wait` sets the maximum time the client is prepared to wait for the entire batch. With `respond-async` as well, the client asks the service to switch to an asynchronous `202 Accepted` answer once that time has passed, so a batch that completes sooner can still be answered synchronously.
  • Why may individual requests in an OData multipart batch not run asynchronously on their own?
    In the multipart format, a `202 Accepted` as the last part already means the batch continues and the client should poll its `Location`. The protocol draws the consequence: `respond-async` MUST NOT be applied to individual requests when the batch response is multipart, and the service MUST ignore it on them. The JSON format, which correlates by `id`, allows it for independent requests.

saying these in an interview costs you the question

  • respond-async on the batch also makes every inner request asynchronous.
  • A change set can answer 202 Accepted and finish its writes later.
  • A trailing 202 part means the last request in the batch failed.
  • A JSON batch must finish every request before returning anything.
  • Any request in a JSON batch may run asynchronously, even inside an atomicity group.