В мире разработки на 1С, как и во многих других современных платформах, асинхронное программирование становится все более актуальным. С появлением конструкций Асинх и Ждать в платформе 1С:Предприятие, начиная с версии 8.3.18, разработчики получили мощный инструмент для упрощения работы с асинхронным кодом, делая его более линейным и читаемым. Однако, при переходе от старых механизмов, таких как ОписаниеОповещения (callbacks), к новым, мы можем столкнуться с определенными трудностями, особенно когда речь идет о типовых функциях платформы.
Рассмотрим подробнее проблему, с которой сталкиваются многие разработчики, пытаясь применить современный подход к уже существующему функционалу, в частности, к печати нефискального текста.
Изначально асинхронные операции в 1С часто реализовывались с использованием механизма ОписаниеОповещения. Это означало, что мы вызывали некоторую функцию, передавали ей описание процедуры, которая должна быть вызвана после завершения асинхронной операции (т.е. callback), и продолжали выполнение основного кода. Когда асинхронная операция завершалась, платформа вызывала нашу процедуру оповещения.
Например, в представленном коде для печати нефискального текста используется именно такой подход:
ОповещениеПриЗавершении = Новый ОписаниеОповещения("ПечатьТекстаЗавершение", ЭтотОбъект, ДП);
// ...
ОборудованиеЧекопечатающиеУстройстваКлиент.НачатьПечатьТекста(ОповещениеПриЗавершении, Форма.УникальныйИдентификатор, Неопределено, ПараметрыОперации);
Такой подход, хотя и функционален, может привести к так называемому "аду коллбэков" (callback hell), когда вложенные асинхронные вызовы делают код трудночитаемым и сложным для поддержки. В ответ на это, 1С представила конструкции Асинх и Ждать, которые позволяют писать асинхронный код, выглядящий и читающийся почти как синхронный. Разработчики, стремясь улучшить свой код, естественно хотят использовать Ждать для таких операций.
Основной вопрос заключается в том, как "обернуть" существующую типовую функцию ОборудованиеЧекопечатающиеУстройстваКлиент.НачатьПечатьТекста в конструкцию Ждать, чтобы не получать оповещение, а дождаться ее выполнения, как если бы она возвращала Обещание (Promise).
Прежде чем углубляться в специфику типовых функций, давайте разберем общие принципы работы Асинх и Ждать в 1С.
Асинх функция?
Функция, помеченная ключевым словом Асинх, всегда выполняется асинхронно и возвращает объект типа Обещание (Promise). Это "обещание" представляет собой результат асинхронной операции, который может быть еще не известен. Обещание может находиться в трех состояниях:
Важно помнить, что Асинх функции в 1С (подобно JavaScript) не обеспечивают многопоточность. Они лишь позволяют не блокировать основной поток выполнения пользовательского интерфейса, что критично для поддержания отзывчивости приложения.
Ждать?
Оператор Ждать используется исключительно внутри Асинх функций. Он принимает в качестве аргумента объект Обещание и приостанавливает выполнение Асинх функции до тех пор, пока это Обещание не будет разрешено (т.е. перейдет в состояние "Успех" или "Провал"). Как только Обещание разрешается, выполнение функции возобновляется, и Ждать возвращает результат, если обещание было выполнено успешно, или генерирует исключение, если обещание завершилось ошибкой.
Рассмотрим условный пример, как мы могли бы написать свою асинхронную функцию, используя Ждать:
// Этот код предполагает, что "АсинхФункцияДающаяРезультат" возвращает Обещание
Асинх Функция ЛюбаяАсинхфункция(Параметры) Экспорт
// Какой-то код до асинхронной операции
Сообщить("Начало асинхронной операции...");
Результат = ЖДАТЬ АсинхФункцияДающаяРезультат(РандомныеПараметры);
// Код, который выполнится после завершения асинхронной операции
Сообщить("Асинхронная операция завершена. Результат: " + Результат);
Возврат Результат;
КонецФункции
Этот пример наглядно демонстрирует, как Ждать позволяет нам писать асинхронный код последовательно, избегая сложных вложений коллбэков.
Асинх/Ждать:
Ключевая проблема, с которой столкнулся автор темы, заключается в том, что функция ОборудованиеЧекопечатающиеУстройстваКлиент.НачатьПечатьТекста является типовой и разработана с использованием старого механизма ОписаниеОповещения. Как следует из анализа, не все старые асинхронные методы платформы 1С получили прямые аналоги в виде асинхронных функций, возвращающих Обещание.
Платформа 1С действительно постепенно переводит свои асинхронные вызовы на новый лад. Например, для интерактивных запросов вместо ПоказатьВопрос теперь есть ВопросАсинх, который возвращает Обещание. Однако для функций, подобных ОборудованиеЧекопечатающиеУстройстваКлиент.НачатьПечатьТекста, прямого аналога типа ПечатьТекстаАсинх, который бы возвращал Обещание, в данный момент не существует.
Это означает, что мы не можем просто применить ЖДАТЬ к вызову ОборудованиеЧекопечатающиеУстройстваКлиент.НачатьПечатьТекста, потому что эта функция не возвращает объект Обещание. Она ожидает в качестве первого параметра ОписаниеОповещения. Таким образом, попытка написать что-то вроде Результат = ЖДАТЬ ОборудованиеЧекопечатающиеУстройстваКлиент.НачатьПечатьТекста(...) приведет к ошибке, так как возвращаемое значение не является Обещанием.
Поскольку прямое применение Ждать к обсуждаемой типовой функции невозможно, нам приходится рассматривать альтернативные решения. Рассмотрим несколько вариантов, которые могут помочь в этой ситуации, исходя из обсуждения и общей практики.
ОписаниеОповещения с блокировкой формыЭто тот подход, к которому в итоге склонился автор сообщения, и который является наиболее прямым способом достижения желаемого эффекта "ожидания" без изменения типового кода. Суть метода заключается в следующем:
ОборудованиеЧекопечатающиеУстройстваКлиент.НачатьПечатьТекста), передавая ей ОписаниеОповещения.Пример реализации:
// Модуль формы или общий клиентский модуль
&НаКлиенте
Процедура НапечататьНефискальныйТекст()
// Параметры для печати
ПараметрыОперации = Новый Структура;
ПараметрыОперации.Вставить("ТекстДляПечати", "Это нефискальный текст для печати.");
ПараметрыОперации.Вставить("ИдентификаторУстройства", "МоеУстройствоПечати"); // Здесь должен быть актуальный ИД устройства
// Создаем описание оповещения для завершения печати
ОповещениеПриЗавершении = Новый ОписаниеОповещения("ПослеЗавершенияПечатиТекста", ЭтотОбъект);
// Вызываем типовую функцию печати
// В данном случае мы НЕ можем использовать ЖДАТЬ напрямую
ОборудованиеЧекопечатающиеУстройстваКлиент.НачатьПечатьТекста(ОповещениеПриЗавершении, УникальныйИдентификатор, Неопределено, ПараметрыОперации);
// Блокируем интерфейс до получения оповещения
ПоказатьНадпись("Печать нефискального текста...", "Идет печать...", 0); // Показать модальное окно/надпись
// Можно также использовать ПоказатьОкноПредупреждения или любую другую блокировку
// или создать свою форму ожидания
КонецПроцедуры
&НаКлиенте
Процедура ПослеЗавершенияПечатиТекста(РезультатПечати, ДополнительныеПараметры) Экспорт
// Снимаем блокировку интерфейса
ЗакрытьНадпись(); // Если использовали ПоказатьНадпись
Если РезультатПечати Тогда
Сообщить("Печать нефискального текста успешно завершена.");
Иначе
Сообщить("Ошибка при печати нефискального текста. Подробности: " + ДополнительныеПараметры.ОписаниеОшибки, СтатусСообщения.Важное);
КонецЕсли;
// Здесь можно продолжить выполнение логики, которая зависела от завершения печати
Сообщить("Продолжение работы после печати.");
КонецПроцедуры
Этот подход позволяет имитировать ожидание, но требует разделения логики на две части (до вызова и после получения оповещения) и управления блокировкой интерфейса. Это именно тот "ад коллбэков", от которого Асинх/Ждать призваны спасти, но в данном случае он является наиболее доступным решением для типовых функций без Promise-аналогов.
Как было упомянуто в сообщениях, существует вероятность, что на более низком уровне, ближе к объекту драйвера оборудования, могут существовать асинхронные методы, которые возвращают Обещание. Например, гипотетический метод ОбъектДрайвера.НапечататьТекстовыйДокументАсинх, который автор упоминал как потенциально существующий:
РезультатВызова = Ждать ОбъектДрайвера.НапечататьТекстовыйДокументАсинх(ИДУстройства, ТекстПечати);
Если бы такой метод действительно существовал и был доступен, то можно было бы использовать его напрямую. Однако, как отмечает автор, добраться до объекта драйвера через "100500 вызовов стека 1С" может быть нетривиальной задачей, особенно если требуется соблюсти всю логику выбора устройства и обработки ошибок, предусмотренную типовыми библиотеками.
Варианты действий в этом случае:
Обещание. Это потребует глубокого понимания архитектуры типового решения.Ждать, вам придется создать собственную асинхронную функцию-обертку, которая будет внутренне использовать ОписаниеОповещения, но внешне возвращать Обещание. Рассмотрим пример того, как это могло бы выглядеть:
// Общий клиентский модуль (или модуль формы)
Асинх Функция ПечатьТекстаАсинх(ИдентификаторУстройства, ТекстДляПечати) Экспорт
Возврат Новый Обещание(Новый ОписаниеОповещения("ВыполнитьПечатьТекстаНаДрайвере", ЭтотОбъект), ИдентификаторУстройства, ТекстДляПечати);
КонецФункции
// Процедура, которая будет вызвана Обещанием
Процедура ВыполнитьПечатьТекстаНаДрайвере(ПроцедураЗавершения, ИдентификаторУстройства, ТекстДляПечати) Экспорт
// Здесь мы имитируем вызов к драйверу, который использует коллбэк
// В реальной ситуации здесь будет код вызова метода драйвера
// Например:
// ОбъектДрайвера = ПолучитьОбъектДрайвера(ИдентификаторУстройства);
// Если ОбъектДрайвера Тогда
// ОписаниеОповещенияДрайвера = Новый ОписаниеОповещения("ЗавершениеДрайвераПечати", ЭтотОбъект, ПроцедураЗавершения);
// ОбъектДрайвера.НапечататьТекст(ОписаниеОповещенияДрайвера, ТекстДляПечати);
// Иначе
// ВызватьПроцедуруЗавершения(Ложь, Новый Структура("ОписаниеОшибки", "Не удалось получить объект драйвера."));
// КонецЕсли;
// Для примера - имитация задержки и результата
Таймаут(5000, Новый ОписаниеОповещения("ИмитацияЗавершенияПечати", ЭтотОбъект, ПроцедураЗавершения, ИдентификаторУстройства, ТекстДляПечати));
КонецПроцедуры
Процедура ИмитацияЗавершенияПечати(ПроцедураЗавершения, ИдентификаторУстройства, ТекстДляПечати) Экспорт
// Здесь мы получаем результат из условного драйвера
Если СлучайноеЧисло(0, 1) = 1 Тогда // Имитация успешной или неуспешной печати
ВызватьПроцедуруЗавершения(Истина, Новый Структура()); // Успех
Иначе
ВызватьПроцедуруЗавершения(Ложь, Новый Структура("ОписаниеОшибки", "Имитация ошибки печати для устройства " + ИдентификаторУстройства + ".")); // Ошибка
КонецЕсли;
КонецПроцедуры
// Вспомогательная функция для вызова процедуры завершения Обещания
Процедура ВызватьПроцедуруЗавершения(Успех, ПараметрыРезультата) Экспорт
Если Успех Тогда
ПроцедураЗавершения.Выполнить(ПараметрыРезультата);
Иначе
ПроцедураЗавершения.Выполнить(Новый Ошибка(ПараметрыРезультата.ОписаниеОшибки));
КонецЕсли;
КонецПроцедуры
// Использование новой асинхронной функции
Асинх Процедура ТестПечатиСАсинх() Экспорт
Сообщить("Начинаем печать с Асинх/Ждать...");
Попытка
РезультатПечати = ЖДАТЬ ПечатьТекстаАсинх("МойДрайверПринтера", "Тестовый текст через Асинх");
Сообщить("Печать успешно завершена: " + РезультатПечати);
Исключение
Сообщить("Ошибка при печати: " + ОписаниеОшибки(), СтатусСообщения.Важное);
КонецПопытки;
Сообщить("Завершили печать с Асинх/Ждать.");
КонецПроцедуры
Этот подход позволяет нам "обернуть" старый callback-ориентированный код в новый Обещание, которое затем может быть использовано с Ждать. Однако, это требует значительных доработок и понимания внутренней работы механизмов печати, а также поддержания собственной обертки, что может быть нецелесообразно для типовых решений.
Как показывают "Данные поиска" и развитие платформы (например, асинхронные конструкторы объектов внешних компонент в 8.3.27), 1С последовательно развивает асинхронные механизмы. Вполне вероятно, что в будущих версиях платформы или конфигураций появятся прямые аналоги для таких функций, как ОборудованиеЧекопечатающиеУстройстваКлиент.НачатьПечатьТекста, которые будут возвращать Обещание. До этого момента нам придется использовать либо коллбэки с блокировкой интерфейса, либо создавать собственные, более сложные обертки.
Переход от традиционных коллбэков к современным конструкциям Асинх и Ждать в 1С значительно упрощает написание и поддержку асинхронного кода. Однако, когда мы сталкиваемся с типовыми функциями, которые были реализованы по старому принципу (например, ОборудованиеЧекопечатающиеУстройстваКлиент.НачатьПечатьТекста), прямая обертка с использованием Ждать оказывается невозможной из-за отсутствия аналога, возвращающего Обещание.
В текущей ситуации разработчикам приходится выбирать между несколькими подходами:
ОписаниеОповещения, дополняя его логикой блокировки пользовательского интерфейса для имитации ожидания. Это наиболее прямой и наименее трудоемкий путь для типовых функций.Promise-аналоги, или создавать собственные Асинх-обертки над существующими коллбэк-методами, что требует значительных усилий и поддержания собственного кода.Асинх-аналоги для всех часто используемых типовых асинхронных операций.Каждый из этих подходов имеет свои преимущества и недостатки, и выбор зависит от конкретных требований проекта, уровня сложности и готовности к доработкам типового функционала.