При переходе на работу в веб-клиенте или при обновлении платформы «1С:Предприятие» разработчики часто сталкиваются с критической ошибкой: «Использование синхронных методов на клиенте запрещено». Данная ситуация возникает при попытке вызвать модальные окна или синхронные диалоги взаимодействия с файловой системой (например, при сохранении табличных документов, отчетов или обработок на локальный компьютер пользователя).
В этой статье мы подробно разберем, почему возникает эта проблема, как переписать устаревший код под требования современных стандартов разработки 1С и рассмотрим три практических сценария решения: от классического механизма оповещений до современного синтаксиса асинхронного программирования.
В современных веб-технологиях использование синхронных вызовов считается крайне нежелательной практикой. Когда веб-клиент 1С выполняет синхронную операцию (например, метод Выбрать() объекта ДиалогВыбораФайла), поток выполнения JavaScript в браузере полностью блокируется. Пользователь видит «застывший» интерфейс, вкладка браузера не реагирует на действия, а сама система может аварийно завершить работу по таймауту безопасности.
Для предотвращения подобных ситуаций фирма 1С ввела жесткое ограничение на уровне платформы. В свойствах конфигурации по умолчанию устанавливается режим использования синхронных вызовов в значение «Не использовать». Это принуждает разработчиков переходить на асинхронные аналоги, которые возвращают управление веб-клиенту во время ожидания действий пользователя.
Рассмотрим исходный проблемный код, который стабильно работает в тонком клиенте на ПК, но вызывает ошибку в браузере:
ДиалогВыбораФайла = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Сохранение);
ДиалогВыбораФайла.Расширение = ".xls";
СтрокаДатаОтчета = Формат(ДатаОтчета,"ДФ=dd.MM.yyyy");
ПолноеИмяФайла = Форма.НаименованиеОтчета + "_" + СтрокаДатаОтчета;
ДиалогВыбораФайла.ПолноеИмяФайла = ПолноеИмяФайла;
ДиалогВыбораФайла.ПроверятьСуществованиеФайла = Истина;
ТабДок = Новый ТабличныйДокумент;
Если ДиалогВыбораФайла.Выбрать() Тогда // Здесь происходит падение в веб-клиенте!
СформироватьНаСервереТабДок(ТабДок);
КонецЕсли;
Попытка
ТабДок.Записать(ДиалогВыбораФайла.ПолноеИмяФайла,ТипФайлаТабличногоДокумента.XLS);
Сообщить("Отчет сохранен " + ДиалогВыбораФайла.Каталог);
Исключение
КонецПопытки;
Метод Выбрать() пытается остановить выполнение кода до тех пор, пока пользователь не выберет путь для сохранения файла. Нам необходимо переработать этот алгоритм.
Этот способ является базовым стандартом для платформы 1С версии 8.3 (начиная с 8.3.3 и до 8.3.15). Суть метода заключается в разделении исходного кода на две части:
Показать().Проанализируем по шагам, как правильно переписать код:
Шаг 1. В основной процедуре создаем объект типа ОписаниеОповещения. В конструктор этого объекта передаем имя процедуры-обработчика, которая будет вызвана после закрытия диалога, и контекст формы (обычно это ЭтотОбъект).
Шаг 2. Заменяем метод Выбрать() на Показать(), передавая туда созданное описание оповещения.
Шаг 3. Создаем экспортную процедуру на клиенте, которая примет результаты выбора. Важно помнить, что объект ДиалогВыбораФайла больше не доступен напрямую внутри процедуры-обработчика как локальная переменная. Выбранные пути к файлам передаются платформой в параметре Результат в виде массива строк.
Посмотрим на готовый исправленный код:
&НаКлиенте
Процедура СохранитьОтчет(Команда)
ДиалогВыбораФайла = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Сохранение);
ДиалогВыбораФайла.Расширение = ".xls";
СтрокаДатаОтчета = Формат(ДатаОтчета, "ДФ=dd.MM.yyyy");
ПолноеИмяФайла = Форма.НаименованиеОтчета + "_" + СтрокаДатаОтчета;
ДиалогВыбораФайла.ПолноеИмяФайла = ПолноеИмяФайла;
ДиалогВыбораФайла.ПроверятьСуществованиеФайла = Истина;
// Создаем обработчик завершения
ОповещениеЗавершения = Новый ОписаниеОповещения("ПослеВыбораФайла", ЭтотОбъект);
// Показываем диалог асинхронно
ДиалогВыбораФайла.Показать(ОповещениеЗавершения);
КонецПроцедуры
&НаКлиенте
Процедура ПослеВыбораФайла(Результат, ДополнительныеПараметры) Экспорт
// Если пользователь закрыл диалог кнопкой Отмена
Если Результат = Неопределено Тогда
Возврат;
КонецЕсли;
// Массив Результат хранит выбранные пути к файлам
ВыбранныйПуть = Результат[0];
ТабДок = Новый ТабличныйДокумент;
СформироватьНаСервереТабДок(ТабДок);
Попытка
ТабДок.Записать(ВыбранныйПуть, ТипФайлаТабличногоДокумента.XLS);
Сообщить("Отчет успешно сохранен: " + ВыбранныйПуть);
Исключение
ВызватьИсключение "Ошибка сохранения файла! Проверьте права доступа к выбранному каталогу.";
КонецПопытки;
КонецПроцедуры
Обратите внимание на ключевой момент в процедуре ПослеВыбораФайла: мы обязательно пишем ключевое слово Экспорт, иначе платформа не сможет вызвать этот метод из ядра системы при получении события от браузера.
Использование ОписаниеОповещения часто приводит к так называемому «адскому коллбэку» (Callback Hell), когда логика одного процесса разрывается и разносится по разным процедурам модуля, что затрудняет чтение кода. В современных релизах платформы (начиная с версии 8.3.18) разработчики 1С получили мощный инструмент — ключевые слова Асинх и Ждать.
Этот подход позволяет писать асинхронный код последовательно, без создания вспомогательных процедур. Платформа сама приостановит исполнение на строке ожидания и продолжит его, когда придет ответ.
Рассмотрим, как красиво выглядит решение нашей задачи с использованием нового метода диалога ВыбратьАсинх():
&НаКлиенте
Асинх Процедура СохранитьОтчетСовременно(Команда)
ДиалогВыбораФайла = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Сохранение);
ДиалогВыбораФайла.Расширение = ".xls";
СтрокаДатаОтчета = Формат(ДатаОтчета, "ДФ=dd.MM.yyyy");
ПолноеИмяФайла = Форма.НаименованиеОтчета + "_" + СтрокаДатаОтчета;
ДиалогВыбораФайла.ПолноеИмяФайла = ПолноеИмяФайла;
ДиалогВыбораФайла.ПроверятьСуществованиеФайла = Истина;
// Ждем выбора пользователя прямо в текущей процедуре без блокировки интерфейса
РезультатВыбора = Ждать ДиалогВыбораФайла.ВыбратьАсинх();
Если РезультатВыбора = Неопределено Тогда
Возврат; // Пользователь отказался от выбора
КонецЕсли;
ВыбранныйПуть = РезультатВыбора[0];
ТабДок = Новый ТабличныйДокумент;
СформироватьНаСервереТабДок(ТабДок);
Попытка
ТабДок.Записать(ВыбранныйПуть, ТипФайлаТабличногоДокумента.XLS);
ПоказатьПредупреждениеАсинх("Отчет сохранен по пути: " + ВыбранныйПуть);
Исключение
Сообщить("Не удалось сохранить файл.");
КонецПопытки;
КонецПроцедуры
Здесь модификатор Асинх указывает платформе, что процедура содержит неблокирующие ожидания, а оператор Ждать временно замораживает выполнение до момента возврата результата обещания (Promise) из метода ВыбратьАсинх().
Даже если мы перепишем код с использованием асинхронных диалогов, при работе в браузере конечный пользователь может столкнуться с другой проблемой: требованием установить расширение для работы с файлами 1С. В корпоративных средах с жесткими политиками безопасности или при работе с мобильных устройств установить такое расширение невозможно.
Наиболее надежный, современный архитектурный паттерн для веб-клиента заключается в том, чтобы формировать файл целиком на стороне сервера, помещать его во временное хранилище и затем отправлять на скачивание клиенту с помощью стандартных средств браузера (подобно скачиванию файлов с любых сайтов).
Для этого в современных версиях платформы используется метод ПолучитьФайлССервераАсинх(). Он работает абсолютно во всех браузерах и не требует установки расширения работы с файлами.
Разберем данный паттерн по шагам:
Шаг 1. Серверная часть. Формируем табличный документ, записываем во временный файл на сервере, преобразуем в двоичные данные и помещаем во временное хранилище. Возвращаем клиенту адрес этого хранилища.
Шаг 2. Клиентская часть. Вызываем серверную функцию получения адреса и скачиваем файл на компьютер пользователя стандартным методом платформы.
Проанализируем реализацию в коде:
&НаКлиенте
Асинх Процедура СформироватьИСкачатьОтчет(Команда)
// 1. Получаем подготовленный файл из временного хранилища на сервере
АдресХранилища = ПолучитьФайлОтчетаНаСервере();
Если ПустаяСтрока(АдресХранилища) Тогда
ПоказатьПредупреждениеАсинх("Не удалось сформировать отчет на сервере.");
Возврат;
КонецЕсли;
// 2. Задаем параметры скачивания
ПараметрыДиалога = Новый ПараметрыДиалогаПолученияФайлов;
ИмяФайлаДляСохранения = Форма.НаименованиеОтчета + "_" + Формат(ДатаОтчета, "ДФ=dd.MM.yyyy") + ".xls";
// 3. Отправляем файл пользователю без использования расширения файлов
Ждать ПолучитьФайлССервераАсинх(АдресХранилища, ИмяФайлаДляСохранения, ПараметрыДиалога);
КонецПроцедуры
&НаСервере
Функция ПолучитьФайлОтчетаНаСервере()
ТабДок = Новый ТабличныйДокумент;
// Наполняем табличный документ данными на сервере
СформироватьНаСервереТабДок(ТабДок);
// Создаем имя уникального временного файла на сервере
ИмяВременногоФайла = ПолучитьИмяВременногоФайла("xls");
Попытка
// Записываем табличный документ во временный файл
ТабДок.Записать(ИмяВременногоФайла, ТипФайлаТабличногоДокумента.XLS);
// Помещаем файл в хранилище, привязывая его к уникальному идентификатору формы
ДвоичныеДанныеФайла = Новый ДвоичныеДанные(ИмяВременногоФайла);
АдресХранилища = ПоместитьВоВременноеХранилище(ДвоичныеДанныеФайла, УникальныйИдентификатор);
// Удаляем временный файл на сервере, так как данные уже сохранены в СУБД/ОЗУ временного хранилища
УдалитьФайлы(ИмяВременногоФайла);
Возврат АдресХранилища;
Исключение
// В случае ошибки гарантируем удаление временного файла
Попытка
УдалитьФайлы(ИмяВременногоФайла);
Исключение
КонецПопытки;
Возврат "";
КонецПопытки;
КонецФункции
Этот способ является наиболее предпочтительным для кроссплатформенной разработки. Он обеспечивает стабильное поведение системы как в тонком клиенте Windows, так и в браузере Safari на iPad, Chrome под Linux или Android, избавляя системных администраторов от необходимости настраивать браузеры пользователей.
Используйте рассмотренные паттерны для перевода ваших конфигураций 1С на асинхронную модель работы. Это повысит общую отзывчивость интерфейса, гарантирует совместимость с веб-клиентом и защитит ваши решения от будущих ограничений безопасности веб-браузеров. Для быстрой вставки типовых конструкций по правилам платформы подойдут шаблоны кода для применения стандартов разработки 1С.