API вернул 202 без результата: состояния асинхронных заданий, опрос и повторная отправка
Различайте приём задания, успешную проверку статуса и завершение работы. Сохраняйте ID, ограничивайте опрос и разбирайте 404, истечение срока результата и неизвестный исход отправки без повторного создания задания.
API вернул HTTP 202, но в теле есть только идентификатор задания, без результата генерации. Затем запросы статуса получают 200, а интерфейс продолжает показывать ожидание. Не отправляйте исходное задание повторно: успешная отправка, успешное чтение статуса и успешное выполнение — три разных факта.
Статья относится к API с явно описанным протоколом асинхронных заданий, например для обработки документов или пакетных операций. Она не предполагает, что такой режим поддерживает любая модель или сервис-посредник. Источники проверены 2026-11-09; код проверялся только на искусственных локальных данных, без реальных платформ и платных API.
1. Ответ 202 не является результатом и не превратится в 200 на том же соединении
В разделе 15.3.3 RFC 9110 сказано: 202 означает, что запрос принят для обработки, но обработка ещё не завершена. В итоге операция может даже не состояться.[1] После завершения первоначального HTTP-ответа сервер не отправит автоматически новый «окончательный код статуса» в рамках этого ответа. Клиент должен по протоколу API запросить состояние, получить результат или дождаться настроенного уведомления.
Найдите в официальной документации четыре вещи: адрес и метод отправки, идентификатор задания, адрес проверки состояния, конечные состояния и способ получения результата. В архитектурном шаблоне Microsoft Location указывает на ресурс статуса, а Retry-After помогает выбрать интервал проверки. Это шаблон проектирования, а не требование наличия обоих заголовков в любом ответе 202.[2]
| Что получено | Что можно подтвердить | Что пока не подтверждено |
|---|---|---|
| Ответ 202 на отправку и ID задания | Интерфейс сообщает, что запрос принят | Работа уже началась, обязательно завершится успешно или будет бесплатной |
| Ответ 200 на проверку статуса | Эта проверка успешно вернула представление ресурса | Само задание завершено |
| Успешное конечное состояние | Сервер сообщает о завершении задания | Ссылка на скачивание ещё действует и результат отвечает требованиям |
| Исчерпан бюджет ожидания клиента | Текущий сеанс наблюдения закончился | Удалённое задание завершилось ошибкой или отменено |
Если документация описывает только синхронный ответ и не определяет ресурс задания, не придумывайте пути /jobs или /status. Идентификатор HTTP-запроса тоже не обязательно является ID задания, доступным для поиска: сначала установите назначение каждого номера.
2. Сохраните идентичность одной отправки и проверяйте то же задание
Храните отдельно идентификатор бизнес-операции, номер фактической попытки отправки, ID запроса платформы и ID задания. Свяжите задание с провайдером, аккаунтом или проектом и окружением: одинаковая строка в разных средах не обязательно обозначает одну работу. При восстановлении страницы загружайте эту связь из контролируемого хранилища, а не сохраняйте лишь анимацию ожидания.
Например, учебный протокол может вернуть job_demo, а затем последовательность queued, running, succeeded. queued и running — промежуточные состояния. failed и canceled тоже конечные, но не успешные. Названия полей и регистр определяет конкретный протокол; у него могут быть состояния истечения срока или частичного успеха. Не трактуйте любое незнакомое значение как «продолжать ждать».
До проверки статуса проверьте и адрес:
- Используйте адрес из проверенного протокола; уточните HTTPS, хост, порт, путь и область действия учётных данных.
- Location может указывать на другой хост. Обращайтесь к нему только в рамках заранее разрешённого протокола; не пересылайте исходный API Key на произвольный адрес или на каждую цель перенаправления.
- Ссылка на результат с параметрами подписи может сама давать доступ к данным. Ей не место в открытых логах, обращениях в поддержку и скриншотах браузера.
- Адрес статуса и адрес скачивания могут различаться. Следуйте процедуре получения результата; перенаправление на скачивание не является командой повторить исходный POST.
Проверка состояния и создание задания должны иметь разные пути в коде. Тайм-аут проверки, ограничение частоты или обновление страницы не должны по умолчанию вызывать создание новой работы.
3. Локальный пример: сначала идентификатор и состояние, затем результат
Ниже интерпретатор снимка состояния для собственного учебного протокола: id должен совпадать, status принимает только пять перечисленных значений, а при успехе result.text должен быть строкой, включая пустую. Пустая строка здесь допустима по формату; её пригодность для бизнеса проверяется отдельно. Адаптер реального сервиса должен преобразовывать поля и конечные состояния по его официальным правилам, а не предполагать совместимость.
class JobProtocolError(ValueError):
pass
def interpret_status(snapshot, expected_id):
if not isinstance(expected_id, str) or not expected_id.strip():
raise ValueError("expected_id must be a non-empty string")
if not isinstance(snapshot, dict):
raise JobProtocolError("NOT_AN_OBJECT")
if snapshot.get("id") != expected_id:
raise JobProtocolError("JOB_ID_MISMATCH")
state = snapshot.get("status")
if not isinstance(state, str):
raise JobProtocolError("INVALID_STATUS")
if state in ("queued", "running"):
return "wait", None
if state in ("failed", "canceled"):
return "stop", state
if state == "succeeded":
result = snapshot.get("result")
if not isinstance(result, dict) or not isinstance(result.get("text"), str):
raise JobProtocolError("INVALID_RESULT")
return "done", result["text"]
raise JobProtocolError("UNKNOWN_STATUS")
Функция принимает уже разобранный словарь. Она не отправляет HTTP-запросов, не читает секреты, не ждёт, не создаёт и не отменяет задания. Вызывающий код сначала ограничивает размер тела, проверяет HTTP-статус и тип содержимого и лишь затем разбирает данные. Не превращайте HTML-страницу ошибки или HTTP 401 в обычный снимок задания.
wait означает только, что этот снимок ещё не завершён; это не разрешение опрашивать сервер бесконечно. stop обозначает конечную ошибку или отмену: сохраните состояние и обезличенную диагностическую информацию и закончите наблюдение. Нельзя незаметно заменить неуспешное задание новой отправкой. done возвращается лишь при совпадении ID, успешном состоянии и корректной структуре результата примера.
UNKNOWN_STATUS должен вести к проверке протокола. INVALID_RESULT означает несоответствие заявления об успехе требуемой структуре, а не основание автоматически запускать модель ещё раз. Функция не хранит историю, не обрабатывает конкурентные изменения, ответы не по порядку или восстановление между процессами. Приложение всё ещё должно атомарно сохранять результат с проверкой ID задания и локальной версии. Чтобы ответы опроса не приходили в обратном порядке, можно разрешать не более одного незавершённого запроса статуса для каждого задания.
4. Установите три отдельных бюджета проверки
Первый предел — тайм-аут одной проверки, второй — общий срок текущего наблюдения, третий — максимальное число проверок. Паузы, сетевое время и повторы после ошибок входят в общий бюджет. Тайм-аут отдельного чтения не заменяет общий срок наблюдения за заданием.
Для измерения прошедшего времени внутри процесса подходит time.monotonic(): по документации Python эти часы не зависят от изменения системного времени. Их точка отсчёта не имеет бизнес-смысла, использовать следует разность показаний.[3] Для восстановления в другом процессе сохраняйте бизнес-дедлайн с часовым поясом и пересчитывайте бюджет; не считайте старое значение таймера переносимой временной меткой.
Если протокол предусматривает Retry-After, планируйте следующую проверку согласно его значению. Заголовок может содержать неотрицательное целое число секунд или HTTP-дату. Он не обещает, что задание обязательно завершится к этому времени.[1] При некорректном значении применяйте явно заданную политику клиента, а не немедленный цикл без пауз. Если заголовка нет, используйте рекомендованный протоколом интервал, ограниченное увеличение задержки и случайный разброс; не проверяйте множество заданий каждую миллисекунду.
Допустим, у клиента осталось 20 секунд на наблюдение, а сервер требует ждать не менее 60 секунд до следующей проверки. Сохраните ID и состояние «проверить позже», затем завершите текущий сеанс. Нельзя сокращать ожидание до 20 секунд и обращаться раньше. Числа здесь иллюстративные, не рекомендуемая конфигурация. При возобновлении проверяйте прежнее задание, не создавайте новое.
Допустимое число проверок, их возможная тарификация и срок хранения ресурса состояния определяются текущими условиями сервиса. Прекращение опроса останавливает только наблюдение клиента. Если пользователь действительно хочет отменить удалённую работу, нужен поддерживаемый процесс отмены; см. границы отмены и фоновых задач.
5. Обрабатывайте сбой на его этапе и не записывайте неизвестность как ошибку задания
| Наблюдение | Рекомендуемое действие | Чего избегать |
|---|---|---|
| Соединение оборвалось при отправке, ID не получен | Сохранить номер попытки; проверить приём через документированный поиск, механизм идемпотентности или поддержку | Решить, что без ID задание точно не создано, и сразу повторить |
| Проверка возвращает 401 или 403 | Уточнить аккаунт, проект, окружение и права на ресурс статуса | Сменить аккаунт и угадывать результат по старому ID |
| Проверка возвращает 404 | Проверить адрес, ID, видимость и срок хранения; ждать появления ресурса лишь когда это допускает протокол | Всегда считать 404 очередью или доказательством отсутствия приёма |
| Проверка возвращает 429 или временный 5xx | Повторять проверку в пределах бюджета, сохраняя исходное задание | Превращать повтор проверки в новую отправку |
| Состояние долго не меняется | Сопоставить время отправки, обновления статуса, объявления сервиса и оговорённый срок | Ждать бесконечно после одного running |
| После успеха ссылка на результат перестала работать | Выяснить, можно ли получить новый адрес для того же задания; проверить срок хранения | Из-за ошибки скачивания повторно запускать всю платную работу |
Microsoft отмечает, что некоторые реализации возвращают 404 до создания ресурса результата, а отдельный ресурс статуса устраняет эту неоднозначность.[2] Это не основание повторять любой 404. Отсутствие записи на платформе тоже не доказывает отсутствие задания: логи и индексы могут обновляться с задержкой.
Применяйте идемпотентную отправку только при явной поддержке сервиса и с соблюдением области ключа, срока действия и требований к одинаковому содержимому для одного ключа. Добавленный клиентом заголовок Idempotency-Key сам по себе не обеспечивает дедупликацию. Если судьба первой отправки неизвестна, сохраните «результат отправки не подтверждён», а не меняйте его на определённую ошибку ради повторного запуска.
Асинхронный интерфейс может также присылать Webhook. Уведомление и опрос способны сообщить об одном завершении, поэтому запись бизнес-результата всё равно требует дедупликации. Проверка подписи уведомлений здесь не реализована; она разобрана в руководстве по Webhook и повторной обработке. Статус задания не равен статусу счёта; для расходов используйте сверку запросов и начислений.
6. Перед запуском проверьте восстановление на искусственных заданиях
На Python 3.12.10 выполнена 21 локальная проверка: очередь без результата, частичный результат во время выполнения, успешный китайский и русский текст и пустая строка, конечная ошибка и отмена, другой ID задания, некорректный снимок и статус, неизвестное состояние, отсутствие или неверный тип результата при заявленном успехе, а также ограниченные последовательности состояний.
Последовательность queued → running → succeeded должна отдавать результат лишь в конечном состоянии. Если лимит проверок исчерпан при running, следует сохранить pending, а не failed. После failed наблюдение не продолжается автоматически, даже если в тестовой очереди есть следующий успешный снимок. Проверялись интерпретатор и локальный тестовый драйвер, а не сетевая реализация опроса.
Перед внедрением в свой клиент также смоделируйте обновление страницы, исчерпание бюджета наблюдения, истечение срока ресурса, ошибку скачивания, дублирующее уведомление и одновременное завершение нескольких наблюдателей. Проверьте продолжение того же задания после восстановления и единственную запись бизнес-результата. HTTP-тайм-ауты, передача учётных данных при перенаправлении, конкуренция в постоянном хранилище и реальная тарификация в этом примере не тестировались; не отмечайте их как проверенные.
Для поддержки подготовьте время отправки с часовым поясом, обезличенные ID задания и запроса, коды каждой проверки, последнее состояние и сведения об автоматической повторной отправке. Не прикладывайте секреты или полные подписанные URL; используйте шаблон минимального воспроизведения и обезличенного обращения.
7. Первичные источники
- [1] RFC 9110: HTTP Semantics: семантика 202 Accepted в разделе 15.3.3 и формат Retry-After в разделе 10.2.3.
- [2] Microsoft Azure Architecture: Asynchronous Request-Reply: шаблон ресурса статуса, опроса, конечных состояний, хранения результатов и дедупликации отправки. Он не подтверждает наличие всех возможностей у сторонней платформы.
- [3] Python 3.12: time.monotonic: измерение прошедшего времени и ограничения точки отсчёта.
Источники проверены 2026-11-09. Примерные поля состояния и бюджеты не являются обещаниями продукта; при подключении следуйте актуальному протоколу нужного API.