Как организовать опрос асинхронного API в 1С без зависания интерфейса и ошибок 429 Too Many Requests?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

В современной практике интеграции 1С с внешними сервисами (будь то маркетплейсы, государственные информационные системы вроде «Меркурия» и «Честного Знака», логистические компании или нейросети) разработчики все чаще сталкиваются с асинхронным API. Классическая схема взаимодействия выглядит так:

  1. 1С отправляет первичный запрос (например, на генерацию тяжелого отчета или проведение транзакции) и мгновенно получает в ответ уникальный идентификатор задачи (Task ID).
  2. Сервис начинает выполнять задачу на своей стороне.
  3. 1С должна периодически опрашивать сервис, посылая GET-запрос с полученным идентификатором, чтобы узнать статус готовности.
  4. Как только статус меняется на «Выполнено», 1С отправляет финальный запрос на получение готового результата.

Главная сложность заключается в организации процесса ожидания статуса. Если запустить обычный цикл опроса на клиенте, интерфейс программы намертво «зависнет», и пользователь не сможет продолжить работу. Если выполнять опрос в цикле на сервере без задержек, процессор сервера будет нагружен на 100%, а внешняя система мгновенно заблокирует ваши запросы, вернув ошибку HTTP 429 Too Many Requests (Превышен лимит запросов). Метод ПодключитьОбработчикОжидания(), который часто используют для задержек, работает исключительно на клиенте и недоступен в серверных процедурах.

В этой статье мы подробно разберем, как правильно и технологично решить эту задачу на платформе 1С:Предприятие, используя современные паттерны проектирования, механизмы фоновых заданий, новые возможности платформы и возможности БСП.

---

Решение 1. Проверенный паттерн: Очередь задач в регистре сведений и фоновое/регламентное задание

Это классический и самый надежный архитектурный подход, который работает на любых версиях платформы 1С:Предприятие 8. Он полностью переносит логику ожидания на сервер, не блокируя работу пользователя.

Суть метода: Вместо того чтобы ждать ответ «здесь и сейчас», мы фиксируем необходимость проверки во вспомогательном регистре сведений, а обработку этой очереди доверяем независимому фоновому или регламентному заданию.

Разберем реализацию этого паттерна по шагам:

Шаг 1. Создание регистра сведений

Создадим непериодический независимый регистр сведений ОчередьЗадачAPI со следующей структурой:

Шаг 2. Регистрация задачи при отправке

При отправке первичного запроса мы записываем полученный идентификатор в наш регистр:


Процедура ЗарегистрироватьЗадачуВAPI(ДокументСсылка, ДанныеЗапроса)
    
    // 1. Отправляем первичный запрос к API
    ИдентификаторЗадачи = ОтправитьПервичныйЗапросВСервис(ДанныеЗапроса);
    
    Если ЗначениеЗаполнено(ИдентификаторЗадачи) Тогда
        // 2. Записываем задачу в очередь для последующего опроса
        МенеджерЗаписи = РегистрыСведений.ОчередьЗадачAPI.СоздатьМенеджерЗаписи();
        МенеджерЗаписи.ИдентификаторЗадачи = ИдентификаторЗадачи;
        МенеджерЗаписи.Статус = "Создана";
        МенеджерЗаписи.ДатаПоследнейПроверки = ТекущаяДатаСеанса();
        МенеджерЗаписи.КоличествоПопыток = 0;
        МенеджерЗаписи.ПараметрыЗапроса = Новый ХранилищеЗначения(ДокументСсылка);
        МенеджерЗаписи.Записать(Истина);
        
        // 3. Опционально: динамически запускаем фоновое задание проверки
        ФоновыеЗадания.Создать("ИнтеграцияAPI.ПроверитьСтатусыЗадач");
    КонецЕсли;
    
КонецПроцедуры

Шаг 3. Обработка очереди на сервере

На сервере создается процедура общего модуля (например, в модуле ИнтеграцияAPI с флагами «Сервер», «Вызов сервера»), которая считывает все незавершенные задачи из регистра, опрашивает API и обрабатывает результаты. Для исключения перегрузки внешнего сервиса мы проверяем только те задачи, с момента последней проверки которых прошло, например, более 10 секунд.


Процедура ПроверитьСтатусыЗадач() Экспорт
    
    // Получаем незавершенные задачи, по которым еще не превышен лимит попыток
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   Очередь.ИдентификаторЗадачи КАК ИдентификаторЗадачи,
        |   Очередь.КоличествоПопыток КАК КоличествоПопыток,
        |   Очередь.ПараметрыЗапроса КАК ПараметрыЗапроса
        |ИЗ
        |   РегистрСведений.ОчередьЗадачAPI КАК Очередь
        |ГДЕ
        |   Очередь.Статус В (""Создана"", ""ВРаботе"")
        |   И РАЗНОСТЬДАТ(Очередь.ДатаПоследнейПроверки, &ТекущееВремя, СЕКУНДА) >= 10
        |   И Очередь.КоличествоПопыток < 20";
        
    Запрос.УстановитьПараметр("ТекущееВремя", ТекущаяДатаСеанса());
    РезультатЗапроса = Запрос.Выполнить();
    
    Если РезультатЗапроса.Пустой() Тогда
        Возврат;
    КонецЕсли;
    
    Выборка = РезультатЗапроса.Выбрать();
    Пока Выборка.Следующий() Цикл
        
        ИдентификаторЗадачи = Выборка.ИдентификаторЗадачи;
        НовыйСтатус = ПолучитьСтатусЗадачиИзAPI(ИдентификаторЗадачи);
        
        МенеджерЗаписи = РегистрыСведений.ОчередьЗадачAPI.СоздатьМенеджерЗаписи();
        МенеджерЗаписи.ИдентификаторЗадачи = ИдентификаторЗадачи;
        МенеджерЗаписи.Прочитать();
        
        Если МенеджерЗаписи.Выбран() Тогда
            МенеджерЗаписи.ДатаПоследнейПроверки = ТекущаяДатаСеанса();
            МенеджерЗаписи.КоличествоПопыток = МенеджерЗаписи.КоличествоПопыток + 1;
            
            Если НовыйСтатус = "Выполнена" Тогда
                // Получаем результат и обрабатываем его
                РезультатВыполнения = ПолучитьРезультатЗадачиИзAPI(ИдентификаторЗадачи);
                ДокументСсылка = МенеджерЗаписи.ПараметрыЗапроса.Получить();
                
                ОбработатьРезультатИнтеграции(ДокументСсылка, РезультатВыполнения);
                
                // Удаляем задачу из очереди или закрываем статус
                МенеджерЗаписи.Статус = "Выполнена";
                МенеджерЗаписи.Записать(Истина);
                
            ИначеЕсли НовыйСтатус = "Ошибка" Тогда
                МенеджерЗаписи.Статус = "Ошибка";
                МенеджерЗаписи.Записать(Истина);
                ЗаписатьВЖурналРегистрации("Интеграция API", УровеньЖурналаРегистрации.Ошибка, , , 
                    "Ошибка выполнения задачи на стороне сервиса: " + ИдентификаторЗадачи);
            Иначе
                // Если статус "ВРаботе" - просто обновляем параметры в регистре
                МенеджерЗаписи.Статус = "ВРаботе";
                МенеджерЗаписи.Записать(Истина);
            КонецЕсли;
        КонецЕсли;
        
    КонецЦикла;
    
КонецПроцедуры

Для регулярного запуска этой проверки мы можем настроить стандартное Регламентное задание в конфигураторе, указав расписание (например, «каждые 15 секунд»). Это гарантирует, что даже если пользователь закроет программу, данные все равно будут успешно получены и обработаны сервером.

---

Решение 2. Использование серверных пауз (Метод `ВызватьПаузу` в платформе 8.3.25+)

Если логика бизнес-процесса требует запустить проверку статуса в рамках одного сеанса (например, в контексте фонового задания, запущенного под конкретную интерактивную операцию пользователя), нам необходим инструмент для безопасной приостановки выполнения серверного кода.

Раньше разработчики использовали сомнительные «костыли» (вызов внешней команды ping с таймаутом через ЗапуститьПриложение, бесконечные циклы с проверкой времени, сильно нагружающие процессор, или внешние компоненты). Начиная с версии платформы 8.3.25, в платформе появился штатный, элегантный механизм.

Новая возможность: Метод глобального контекста ВызватьПаузу(<Интервал>).

Этот метод временно приостанавливает поток выполнения текущего сеанса на указанный интервал (в миллисекундах). Важно отметить, что во время паузы процессорные ресурсы сервера практически не расходуются.

Рассмотрим пример использования метода в фоновом задании, запущенном из клиентской формы:


// Функция выполняется на сервере в фоновом задании
Функция ОпроситьAPIССервернойПаузой(ИдентификаторЗадачи) Экспорт
    
    МаксимальноеКоличествоПопыток = 10;
    ИнтервалОжиданияСекунд = 5;
    ТекущаяПопытка = 0;
    
    Пока ТекущаяПопытка < МаксимальноеКоличествоПопыток Цикл
        
        Статус = ПолучитьСтатусЗадачиИзAPI(ИдентификаторЗадачи);
        
        Если Статус = "Выполнена" Тогда
            Возврат ПолучитьРезультатЗадачиИзAPI(ИдентификаторЗадачи);
        ИначеЕсли Статус = "Ошибка" Тогда
            ВызватьИсключение "Ошибка выполнения задачи на стороне API!";
        КонецЕсли;
        
        ТекущаяПопытка = ТекущаяПопытка + 1;
        
        // Предотвращаем ошибку 429 Too Many Requests, делая паузу.
        // Переводим секунды в миллисекунды: 5 * 1000 = 5000 мс.
        ВызватьПаузу(ИнтервалОжиданияСекунд * 1000);
        
    КонецЦикла;
    
    ВызватьИсключение "Превышено время ожидания готовности задачи.";
    
КонецФункции

Важные ограничения метода `ВызватьПаузу`:

---

Решение 3. Использование современных асинхронных вызовов (Платформа 8.3.18+)

Если вам необходимо организовать неблокирующий опрос с клиента, платформа предлагает использовать современную асинхронную модель на базе ключевых слов Асинх и Ждать.

Проанализируем ситуацию: мы запускаем фоновое задание для опроса API на сервере, а на клиенте асинхронно ждем его завершения, сохраняя полную отзывчивость интерфейса (пользователь может открывать другие вкладки, проводить документы и общаться в чате).


&НаКлиенте
Асинх Процедура ЗапуститьПолучениеДанныхАсинхронно(ИдентификаторЗадачи)
    
    // Запускаем на сервере фоновое задание, которое будет делать проверку API 
    // и записывать результат во временное хранилище (или регистр).
    ПараметрыЗадания = Новый Массив;
    ПараметрыЗадания.Добавить(ИдентификаторЗадачи);
    АдресХранилища = ПоместитьВоВременноеХранилище(Неопределено, УникальныйИдентификатор);
    ПараметрыЗадания.Добавить(АдресХранилища);
    
    Задание = ЗапуститьФоновоеЗаданиеНаСервере("ИнтеграцияAPI.ОпроситьAPIИЗаписатьРезультат", ПараметрыЗадания);
    
    // Асинхронно ждем завершения фонового задания без блокировки UI.
    // Метод "ОжидатьЗавершенияВыполненияАсинх" возвращает обещание (Promise).
    Попытка
        РезультатОжидания = Ждать ФоновыеЗадания.ОжидатьЗавершенияВыполненияАсинх(Задание, 60); // таймаут 60 сек
        
        // Получаем результат из временного хранилища
        ДанныеРезультата = ПолучитьРезультатИзХранилища(АдресХранилища);
        ПоказатьПредупреждение(, "Данные успешно получены и обработаны!");
        ОбновитьДанныеФормы(ДанныеРезультата);
        
    Исключение
        ПоказатьПредупреждение(, "Произошла ошибка при получении данных: " + ОписаниеОшибки());
    КонецПопытки;
    
КонецПроцедуры

Такой подход обеспечивает максимальный комфорт для конечного пользователя, полностью исключая эффект «зависания» программы.

---

Решение 4. Использование готовых механизмов БСП

Если вы разрабатываете решение на базе типовой конфигурации (например, 1С:ERP, УТ, КА, УНФ, БП), вам не нужно писать код для реализации пауз вручную. В Библиотеке Стандартных Подсистем (БСП) уже встроены надежные кроссплатформенные обертки.

Рассмотрим метод ОбщегоНазначения.Пауза(<Секунды>):

Пример вызова из общего модуля БСП:


// Приостанавливаем выполнение потока на сервере на 5 секунд
ОбщегоНазначения.Пауза(5);

---

Решение 5. Смена парадигмы: Переход на Вебхуки (Webhooks)

Если вы имеете возможность влиять на настройки интеграции со стороны внешнего сервиса или разрабатываете собственное API, постоянный опрос (Polling) — это архитектурный тупик. Наиболее современный подход — использование вебхуков (Webhooks).

Как это работает:

  1. В 1С создается собственный **HTTP-сервис** (например, с шаблоном пути /v1/api_callback) и публикуется на веб-сервере (IIS или Apache).
  2. При отправке первичного запроса к API, в теле запроса (или в заголовках) передается параметр Callback_URL, указывающий на ваш опубликованный HTTP-сервис 1С.
  3. Вместо того чтобы 1С опрашивала сервис, сервис сам присылает POST-запрос на указанный URL в момент готовности задачи.
  4. 1С мгновенно обрабатывает входящий HTTP-запрос на сервере, считывает результат и обновляет данные в базе.

Этот метод полностью устраняет проблему ошибок 429, не требует настройки фоновых процессов ожидания и гарантирует моментальное получение результата без лишней нагрузки на сетевой трафик.

---

Резюме и рекомендации

Для выбора оптимальной стратегии работы с асинхронным API воспользуйтесь следующей памяткой:

  1. Если требуется **фоновый импорт данных**, который не критичен к сиюминутному отображению пользователю: используйте схему с регистром сведений и регламентным заданием. Это гарантирует доставку данных даже при сбоях сети или закрытии программы пользователем.
  2. Если пользователь **ждет результат на форме** прямо сейчас: используйте связку Фонового задания на сервере (где опрос выполняется с паузами через ВызватьПаузу или ОбщегоНазначения.Пауза) и асинхронного метода ожидания на клиенте Ждать ФоновыеЗадания.ОжидатьЗавершенияВыполненияАсинх.
  3. Если внешняя система поддерживает отправку уведомлений: всегда отдавайте приоритет **Вебхукам (HTTP-сервисам 1С)**. Это самый производительный и современный стандарт интеграции корпоративных систем.
← На главную