Как использовать конструкции Асинх и Ждать для типовой функции печати нефискального текста в 1С?

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

В мире разработки на 1С, как и во многих других современных платформах, асинхронное программирование становится все более актуальным. С появлением конструкций Асинх и Ждать в платформе 1С:Предприятие, начиная с версии 8.3.18, разработчики получили мощный инструмент для упрощения работы с асинхронным кодом, делая его более линейным и читаемым. Однако, при переходе от старых механизмов, таких как ОписаниеОповещения (callbacks), к новым, мы можем столкнуться с определенными трудностями, особенно когда речь идет о типовых функциях платформы.

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

Суть проблемы: От оповещений к ожиданию

Изначально асинхронные операции в 1С часто реализовывались с использованием механизма ОписаниеОповещения. Это означало, что мы вызывали некоторую функцию, передавали ей описание процедуры, которая должна быть вызвана после завершения асинхронной операции (т.е. callback), и продолжали выполнение основного кода. Когда асинхронная операция завершалась, платформа вызывала нашу процедуру оповещения.

Например, в представленном коде для печати нефискального текста используется именно такой подход:


ОповещениеПриЗавершении = Новый ОписаниеОповещения("ПечатьТекстаЗавершение", ЭтотОбъект, ДП);
// ...
ОборудованиеЧекопечатающиеУстройстваКлиент.НачатьПечатьТекста(ОповещениеПриЗавершении, Форма.УникальныйИдентификатор, Неопределено, ПараметрыОперации);

Такой подход, хотя и функционален, может привести к так называемому "аду коллбэков" (callback hell), когда вложенные асинхронные вызовы делают код трудночитаемым и сложным для поддержки. В ответ на это, 1С представила конструкции Асинх и Ждать, которые позволяют писать асинхронный код, выглядящий и читающийся почти как синхронный. Разработчики, стремясь улучшить свой код, естественно хотят использовать Ждать для таких операций.

Основной вопрос заключается в том, как "обернуть" существующую типовую функцию ОборудованиеЧекопечатающиеУстройстваКлиент.НачатьПечатьТекста в конструкцию Ждать, чтобы не получать оповещение, а дождаться ее выполнения, как если бы она возвращала Обещание (Promise).

Основы Асинхронного Программирования с Асинх и Ждать в 1С

Прежде чем углубляться в специфику типовых функций, давайте разберем общие принципы работы Асинх и Ждать в 1С.

  1. Что такое Асинх функция?

    Функция, помеченная ключевым словом Асинх, всегда выполняется асинхронно и возвращает объект типа Обещание (Promise). Это "обещание" представляет собой результат асинхронной операции, который может быть еще не известен. Обещание может находиться в трех состояниях:

    • Ожидание (Pending): Операция еще выполняется.
    • Успех (Fulfilled): Операция успешно завершилась и содержит результат.
    • Провал (Rejected): Произошла ошибка или исключение в ходе выполнения операции.

    Важно помнить, что Асинх функции в 1С (подобно JavaScript) не обеспечивают многопоточность. Они лишь позволяют не блокировать основной поток выполнения пользовательского интерфейса, что критично для поддержания отзывчивости приложения.

  2. Что делает оператор Ждать?

    Оператор Ждать используется исключительно внутри Асинх функций. Он принимает в качестве аргумента объект Обещание и приостанавливает выполнение Асинх функции до тех пор, пока это Обещание не будет разрешено (т.е. перейдет в состояние "Успех" или "Провал"). Как только Обещание разрешается, выполнение функции возобновляется, и Ждать возвращает результат, если обещание было выполнено успешно, или генерирует исключение, если обещание завершилось ошибкой.

    Рассмотрим условный пример, как мы могли бы написать свою асинхронную функцию, используя Ждать:

    
    // Этот код предполагает, что "АсинхФункцияДающаяРезультат" возвращает Обещание
    Асинх Функция ЛюбаяАсинхфункция(Параметры) Экспорт
        // Какой-то код до асинхронной операции
        Сообщить("Начало асинхронной операции...");
        Результат = ЖДАТЬ АсинхФункцияДающаяРезультат(РандомныеПараметры);
        // Код, который выполнится после завершения асинхронной операции
        Сообщить("Асинхронная операция завершена. Результат: " + Результат);
        Возврат Результат;
    КонецФункции
    

    Этот пример наглядно демонстрирует, как Ждать позволяет нам писать асинхронный код последовательно, избегая сложных вложений коллбэков.

  3. Ограничения Асинх/Ждать:
    • Работают только на клиенте. На сервере по-прежнему используются синхронные методы.
    • Все параметры передаются по значению.

Почему прямая обертка типовой функции не всегда возможна?

Ключевая проблема, с которой столкнулся автор темы, заключается в том, что функция ОборудованиеЧекопечатающиеУстройстваКлиент.НачатьПечатьТекста является типовой и разработана с использованием старого механизма ОписаниеОповещения. Как следует из анализа, не все старые асинхронные методы платформы 1С получили прямые аналоги в виде асинхронных функций, возвращающих Обещание.

Платформа 1С действительно постепенно переводит свои асинхронные вызовы на новый лад. Например, для интерактивных запросов вместо ПоказатьВопрос теперь есть ВопросАсинх, который возвращает Обещание. Однако для функций, подобных ОборудованиеЧекопечатающиеУстройстваКлиент.НачатьПечатьТекста, прямого аналога типа ПечатьТекстаАсинх, который бы возвращал Обещание, в данный момент не существует.

Это означает, что мы не можем просто применить ЖДАТЬ к вызову ОборудованиеЧекопечатающиеУстройстваКлиент.НачатьПечатьТекста, потому что эта функция не возвращает объект Обещание. Она ожидает в качестве первого параметра ОписаниеОповещения. Таким образом, попытка написать что-то вроде Результат = ЖДАТЬ ОборудованиеЧекопечатающиеУстройстваКлиент.НачатьПечатьТекста(...) приведет к ошибке, так как возвращаемое значение не является Обещанием.

Возможные подходы и обходные пути

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

1. Использование механизма ОписаниеОповещения с блокировкой формы

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

  1. Вызываем асинхронную функцию (например, ОборудованиеЧекопечатающиеУстройстваКлиент.НачатьПечатьТекста), передавая ей ОписаниеОповещения.
  2. Сразу после вызова асинхронной функции, мы блокируем пользовательский интерфейс (например, модальным окном или другим способом), чтобы пользователь не мог совершать дальнейшие действия.
  3. В процедуре оповещения (callback), которая будет вызвана после завершения асинхронной операции, мы снимаем блокировку пользовательского интерфейса и продолжаем выполнение логики.

Пример реализации:


// Модуль формы или общий клиентский модуль
&НаКлиенте
Процедура НапечататьНефискальныйТекст()
    // Параметры для печати
    ПараметрыОперации = Новый Структура;
    ПараметрыОперации.Вставить("ТекстДляПечати", "Это нефискальный текст для печати.");
    ПараметрыОперации.Вставить("ИдентификаторУстройства", "МоеУстройствоПечати"); // Здесь должен быть актуальный ИД устройства

    // Создаем описание оповещения для завершения печати
    ОповещениеПриЗавершении = Новый ОписаниеОповещения("ПослеЗавершенияПечатиТекста", ЭтотОбъект);

    // Вызываем типовую функцию печати
    // В данном случае мы НЕ можем использовать ЖДАТЬ напрямую
    ОборудованиеЧекопечатающиеУстройстваКлиент.НачатьПечатьТекста(ОповещениеПриЗавершении, УникальныйИдентификатор, Неопределено, ПараметрыОперации);

    // Блокируем интерфейс до получения оповещения
    ПоказатьНадпись("Печать нефискального текста...", "Идет печать...", 0); // Показать модальное окно/надпись
    // Можно также использовать ПоказатьОкноПредупреждения или любую другую блокировку
    // или создать свою форму ожидания
КонецПроцедуры

&НаКлиенте
Процедура ПослеЗавершенияПечатиТекста(РезультатПечати, ДополнительныеПараметры) Экспорт
    // Снимаем блокировку интерфейса
    ЗакрытьНадпись(); // Если использовали ПоказатьНадпись

    Если РезультатПечати Тогда
        Сообщить("Печать нефискального текста успешно завершена.");
    Иначе
        Сообщить("Ошибка при печати нефискального текста. Подробности: " + ДополнительныеПараметры.ОписаниеОшибки, СтатусСообщения.Важное);
    КонецЕсли;

    // Здесь можно продолжить выполнение логики, которая зависела от завершения печати
    Сообщить("Продолжение работы после печати.");
КонецПроцедуры

Этот подход позволяет имитировать ожидание, но требует разделения логики на две части (до вызова и после получения оповещения) и управления блокировкой интерфейса. Это именно тот "ад коллбэков", от которого Асинх/Ждать призваны спасти, но в данном случае он является наиболее доступным решением для типовых функций без Promise-аналогов.

2. Поиск или создание собственной обертки на более низком уровне

Как было упомянуто в сообщениях, существует вероятность, что на более низком уровне, ближе к объекту драйвера оборудования, могут существовать асинхронные методы, которые возвращают Обещание. Например, гипотетический метод ОбъектДрайвера.НапечататьТекстовыйДокументАсинх, который автор упоминал как потенциально существующий:


РезультатВызова = Ждать ОбъектДрайвера.НапечататьТекстовыйДокументАсинх(ИДУстройства, ТекстПечати);

Если бы такой метод действительно существовал и был доступен, то можно было бы использовать его напрямую. Однако, как отмечает автор, добраться до объекта драйвера через "100500 вызовов стека 1С" может быть нетривиальной задачей, особенно если требуется соблюсти всю логику выбора устройства и обработки ошибок, предусмотренную типовыми библиотеками.

Варианты действий в этом случае:

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

    Этот подход позволяет нам "обернуть" старый callback-ориентированный код в новый Обещание, которое затем может быть использовано с Ждать. Однако, это требует значительных доработок и понимания внутренней работы механизмов печати, а также поддержания собственной обертки, что может быть нецелесообразно для типовых решений.

3. Ожидание развития платформы 1С

Как показывают "Данные поиска" и развитие платформы (например, асинхронные конструкторы объектов внешних компонент в 8.3.27), 1С последовательно развивает асинхронные механизмы. Вполне вероятно, что в будущих версиях платформы или конфигураций появятся прямые аналоги для таких функций, как ОборудованиеЧекопечатающиеУстройстваКлиент.НачатьПечатьТекста, которые будут возвращать Обещание. До этого момента нам придется использовать либо коллбэки с блокировкой интерфейса, либо создавать собственные, более сложные обертки.

Заключение

Переход от традиционных коллбэков к современным конструкциям Асинх и Ждать в 1С значительно упрощает написание и поддержку асинхронного кода. Однако, когда мы сталкиваемся с типовыми функциями, которые были реализованы по старому принципу (например, ОборудованиеЧекопечатающиеУстройстваКлиент.НачатьПечатьТекста), прямая обертка с использованием Ждать оказывается невозможной из-за отсутствия аналога, возвращающего Обещание.

В текущей ситуации разработчикам приходится выбирать между несколькими подходами:

  1. Продолжать использовать механизм ОписаниеОповещения, дополняя его логикой блокировки пользовательского интерфейса для имитации ожидания. Это наиболее прямой и наименее трудоемкий путь для типовых функций.
  2. Исследовать возможность использования более низкоуровневых методов взаимодействия с драйвером, если таковые имеют Promise-аналоги, или создавать собственные Асинх-обертки над существующими коллбэк-методами, что требует значительных усилий и поддержания собственного кода.
  3. Ожидать, пока платформа 1С предоставит готовые Асинх-аналоги для всех часто используемых типовых асинхронных операций.

Каждый из этих подходов имеет свои преимущества и недостатки, и выбор зависит от конкретных требований проекта, уровня сложности и готовности к доработкам типового функционала.

← На главную