Сервер не вызывает клиент: рабочие схемы для управляемых форм 1С
Содержание
Запустить клиентскую процедуру с сервера в 1С напрямую нельзя: процедура с директивой &НаКлиенте из серверного кода не видна, а платформа не даёт серверу инициировать вызов к клиенту. Рабочий способ один по сути: сервер возвращает клиенту данные или «команду», а клиент сам решает, какую процедуру выполнить.
Ниже схемы, которыми это делают на практике в управляемых формах платформы 8.3: от простого возврата значения до фонового задания с опросом и серверных оповещений БСП. Всё, что описано, относится к платформе в целом и не зависит от конфигурации.
Почему серверный код не видит процедуры с директивой &НаКлиенте
В управляемом приложении код формы физически живёт в двух местах. Часть компилируется и выполняется на компьютере пользователя (тонкий клиент, веб-клиент), часть — на сервере 1С. Директива перед процедурой говорит компилятору, где её собрать.
| Директива | Где выполняется | Доступ к данным формы |
|---|---|---|
&НаКлиенте | на клиенте | полный, к реквизитам и элементам |
&НаСервере | на сервере | к реквизитам формы (контекстный вызов) |
&НаСервереБезКонтекста | на сервере | нет, только параметры |
&НаКлиентеНаСервереБезКонтекста | и там, и там | нет, только параметры |
Вызов идёт только в одну сторону: клиент → сервер. Клиент отправляет запрос, сервер выполняет код и возвращает результат. Обратного канала, по которому сервер мог бы посреди своей работы «дёрнуть» клиентскую процедуру, у платформы нет.
Если всё же написать в процедуре &НаСервере вызов клиентской процедуры, модуль не скомпилируется. Конфигуратор сообщит, что процедура или функция с таким именем не определена: в серверной версии модуля её просто нет.
С общими модулями то же самое. Модуль с флажком «Клиент (управляемое приложение)» не виден из серверного кода. Модуль с флажками «Сервер» и «Вызов сервера» виден клиенту, но не наоборот.
Отсюда главное правило проектирования: вызов всегда начинает клиент. Сервер готовит решение, клиент его исполняет.
Сервер возвращает команду, клиент её выполняет
Встроенного метода вроде «ВыполнитьНаКлиенте» в платформе нет. Его роль играет обычная серверная функция, которая возвращает описание того, что нужно сделать. Клиент разбирает ответ и вызывает свои процедуры.
Это самый частый приём у разработчиков: сервер проверяет данные, записывает объект и складывает в результат список действий. Клиент проходит по списку.
&НаКлиенте
Процедура ВыполнитьРасчет(Команда)
Ответ = ВыполнитьРасчетНаСервере();
ВыполнитьДействияКлиента(Ответ);
КонецПроцедуры
&НаСервере
Функция ВыполнитьРасчетНаСервере()
Ответ = Новый Структура("Действия", Новый Массив);
// ... серверная логика: запросы, запись, проверки
Если Объект.Товары.Количество() = 0 Тогда
Ответ.Действия.Добавить(Новый Структура("Имя, Параметр",
"Предупреждение", "Табличная часть пуста"));
Возврат Ответ;
КонецЕсли;
Ответ.Действия.Добавить(Новый Структура("Имя, Параметр",
"ОбновитьСписки", Объект.Ссылка));
Возврат Ответ;
КонецФункции
&НаКлиенте
Процедура ВыполнитьДействияКлиента(Ответ)
Для Каждого Действие Из Ответ.Действия Цикл
Если Действие.Имя = "Предупреждение" Тогда
ПоказатьПредупреждение(, Действие.Параметр);
ИначеЕсли Действие.Имя = "ОбновитьСписки" Тогда
ОповеститьОбИзменении(Действие.Параметр);
ИначеЕсли Действие.Имя = "ОткрытьФорму" Тогда
ОткрытьФорму(Действие.Параметр);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Чем удобна такая схема:
- 🧩 сервер не знает, как именно клиент покажет сообщение, и не зависит от формы;
- 🔁 один серверный вызов вместо нескольких: меньше обращений по сети;
- 🧪 серверную функцию легко проверить отдельно — она просто возвращает структуру.
Имена действий держите в одном месте, например в клиентском общем модуле. Опечатка в строке «Предупреждение» не даст ошибки компиляции, действие молча не выполнится.
Если имя процедуры приходит с сервера строкой, соблазнительно вызвать её через Выполнить(). Делать так не стоит: код из строки трудно отлаживать, а в веб-клиенте и при включённом безопасном режиме это источник проблем. Явная ветка Если … ИначеЕсли надёжнее.
Связка серверного и клиентского события формы
У многих событий формы есть пара: серверная часть и клиентская. Платформа сама вызывает их по порядку, поэтому сервер может оставить «записку», а клиент прочитать её в своём событии.
| Серверное событие | Клиентское событие после него |
|---|---|
ПриСозданииНаСервере | ПриОткрытии |
ПередЗаписьюНаСервере, ПослеЗаписиНаСервере | ПослеЗаписи |
ПриЧтенииНаСервере | ПриОткрытии (при открытии формы) |
Флаг в реквизите формы
Заведите реквизит формы, например ПоказатьПодсказку с типом «Булево». В ПриСозданииНаСервере установите его, в ПриОткрытии проверьте и вызовите нужную клиентскую процедуру.
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
ПоказатьПодсказку = НЕ ЗначениеЗаполнено(Объект.Ответственный);
КонецПроцедуры
&НаКлиенте
Процедура ПриОткрытии(Отказ)
Если ПоказатьПодсказку Тогда
ПоказатьОповещениеПользователя("Не указан ответственный");
КонецЕсли;
КонецПроцедуры
Параметры записи
Структура ПараметрыЗаписи проходит через всю цепочку записи объекта из формы. Ключ, добавленный в ПослеЗаписиНаСервере, доступен в клиентском ПослеЗаписи.
&НаСервере
Процедура ПослеЗаписиНаСервере(ТекущийОбъект, ПараметрыЗаписи)
ПараметрыЗаписи.Вставить("НужноОбновитьОстатки", Истина);
КонецПроцедуры
&НаКлиенте
Процедура ПослеЗаписи(ПараметрыЗаписи)
Если ПараметрыЗаписи.Свойство("НужноОбновитьОстатки") Тогда
Оповестить("ОбновитьОстатки");
КонецЕсли;
КонецПроцедуры
Это чистый способ «запустить клиентский код по итогам серверной записи» без дополнительных реквизитов формы.
Оповещения: Оповестить, ОписаниеОповещения и серверные оповещения БСП
Механизм оповещений в платформе клиентский. Методы Оповестить() и ОповеститьОбИзменении() вызываются только на клиенте. Поэтому сервер снова возвращает признак, а клиент рассылает оповещение другим открытым формам.
Получают его в событии формы ОбработкаОповещения:
&НаКлиенте
Процедура ОбработкаОповещения(ИмяСобытия, Параметр, Источник)
Если ИмяСобытия = "ОбновитьОстатки" Тогда
ОбновитьОстаткиНаСервере();
КонецЕсли;
КонецПроцедуры
Объект ОписаниеОповещения решает другую задачу: он указывает, какую клиентскую процедуру вызвать после немодального диалога или асинхронной операции. Процедура-обработчик должна быть экспортной (Экспорт), иначе платформа её не найдёт. Вызывает её клиент, сервер здесь не участвует.
Когда клиент должен узнать о событии сам
Бывает, что событие происходит вне сеанса пользователя: регламентное задание завершило загрузку, другой пользователь согласовал документ. Здесь нужен канал «сервер → клиент» без действия пользователя.
Типовые конфигурации на свежих версиях БСП решают это подсистемой «Серверные оповещения». Сервер ставит оповещение в очередь для нужных пользователей, клиент получает его и вызывает обработчик. Если в базе настроена система взаимодействия, доставка почти мгновенная. Без неё клиент сам периодически запрашивает сервер.
В самописной конфигурации без БСП работает тот же принцип вручную: сервер пишет событие в регистр сведений , клиент раз в N секунд проверяет его через обработчик ожидания (схема ниже).
Что можно передать между сервером и клиентом
Всё, что уходит на сервер и обратно, платформа сериализует. Отсюда ограничения на типы.
✅ Передаются без проблем:
- примитивы:
Число,Строка,Дата,Булево,Неопределено; - ссылки на объекты:
СправочникСсылка,ДокументСсылкаи другие; Структура,Массив,Соответствие,СписокЗначений,ФиксированнаяСтруктура;УникальныйИдентификатор,ДвоичныеДанные.
❌ Не передаются или недоступны на клиенте:
ТаблицаЗначений,ДеревоЗначений,Запрос, объекты (ДокументОбъект,СправочникОбъект);- сама форма, её элементы,
ОписаниеОповещения— живут только на клиенте.
Как передать таблицу или объект
- 📋 Таблицу значений превратите в массив структур или в реквизит формы с типом «ТаблицаЗначений» — на клиенте он виден как
ДанныеФормыКоллекция. - 🧾 Объект передавайте ссылкой, а на сервере получайте заново:
Ссылка.ПолучитьОбъект(). - 📦 Крупные данные (файлы, большие выборки) кладите во временное хранилище:
ПоместитьВоВременноеХранилище()на сервере, а клиенту возвращайте только адрес. Клиент забирает данные черезПолучитьИзВременногоХранилища()или передаёт адрес дальше.
Контекстный и безконтекстный вызов
При вызове &НаСервере платформа гоняет на сервер и обратно все данные формы. Для формы документа с табличной частью на тысячу строк это заметная задержка на каждый клик.
Если серверному коду реквизиты формы не нужны, ставьте &НаСервереБезКонтекста и передавайте только нужные значения параметрами. Изменения параметров при таком вызове обратно не возвращаются: всё, что нужно клиенту, отдавайте возвращаемым значением функции.
Длительная операция: фоновое задание и опрос с клиента
Серверный вызов синхронный: пока он идёт, клиент ждёт. Если расчёт длится минуту, форма «висит». Правильная схема — запустить работу фоновым заданием и периодически спрашивать о его состоянии. Когда задание закончилось, клиент вызывает свою процедуру с результатом.
// ИдентификаторЗадания, АдресРезультата — реквизиты формы
&НаКлиенте
Процедура Пересчитать(Команда)
ЗапуститьЗаданиеНаСервере();
ПодключитьОбработчикОжидания("ПроверитьЗадание", 2);
КонецПроцедуры
&НаСервере
Процедура ЗапуститьЗаданиеНаСервере()
АдресРезультата = ПоместитьВоВременноеХранилище(Неопределено, УникальныйИдентификатор);
Параметры = Новый Массив;
Параметры.Добавить(АдресРезультата);
Задание = ФоновыеЗадания.Выполнить("МойСерверныйМодуль.Пересчитать", Параметры);
ИдентификаторЗадания = Задание.УникальныйИдентификатор;
КонецПроцедуры
&НаКлиенте
Процедура ПроверитьЗадание()
Состояние = СостояниеЗадания(ИдентификаторЗадания);
Если Состояние = "Активно" Тогда
Возврат;
КонецЕсли;
ОтключитьОбработчикОжидания("ПроверитьЗадание");
ПослеПересчета(Состояние); // клиентская процедура «по сигналу сервера»
КонецПроцедуры
&НаСервереБезКонтекста
Функция СостояниеЗадания(Идентификатор)
Задание = ФоновыеЗадания.НайтиПоУникальномуИдентификатору(Идентификатор);
Если Задание = Неопределено Тогда
Возврат "НеНайдено";
ИначеЕсли Задание.Состояние = СостояниеФоновогоЗадания.Активно Тогда
Возврат "Активно";
ИначеЕсли Задание.Состояние = СостояниеФоновогоЗадания.Завершено Тогда
Возврат "Завершено";
КонецЕсли;
Возврат "Ошибка";
КонецФункции
В процедуре МойСерверныйМодуль.Пересчитать задание в конце кладёт результат по полученному адресу: ПоместитьВоВременноеХранилище(Результат, Адрес). Клиент в ПослеПересчета забирает его по АдресРезультата.
Что учесть:
- ⏱ интервал опроса 1-3 секунды; чаще — лишняя нагрузка на сервер от каждой открытой формы;
- 💬 сообщения, которые задание вывело через
СообщениеПользователю, клиент получит методом заданияПолучитьСообщенияПользователю()— вызывать его нужно на сервере; - 🧰 в конфигурациях на БСП не пишите это вручную: модули
ДлительныеОперациииДлительныеОперацииКлиентделают то же самое с индикатором ожидания и обработкой ошибок.
Файловая база выполняет фоновые задания иначе, чем клиент-серверная, и в старых версиях платформы могла выполнять их не параллельно. Проверяйте схему на той же архитектуре, что у пользователей.
Асинх и Ждать
С платформы 8.3.18 в языке есть Асинх и Ждать. Они упрощают клиентский код: вместо цепочки ОписаниеОповещения пишется последовательный текст с Ждать ВопросАсинх(...). Но асинхронные функции работают только на клиенте. Серверный вызов от этого не становится асинхронным, и обратного канала к клиенту тоже не появляется.
Ошибки и отладка при вызове между клиентом и сервером
Как ловить исключения с сервера
Исключение, вызванное на сервере через ВызватьИсключение, доходит до клиента. Его можно перехватить на клиенте конструкцией Попытка … Исключение, а текст получить через ИнформацияОбОшибке().
&НаКлиенте
Процедура Провести(Команда)
Попытка
ПровестиНаСервере();
Исключение
Инфо = ИнформацияОбОшибке();
ПоказатьПредупреждение(, КраткоеПредставлениеОшибки(Инфо));
КонецПопытки;
КонецПроцедуры
Для пользователя показывайте краткое представление, а подробное (ПодробноеПредставлениеОшибки) пишите в журнал регистрации на сервере. Там видны модуль и строка, где упал код.
Отладка серверной части
- 🐞 В файловой базе точки останова в серверных процедурах срабатывают без дополнительных настроек при запуске из Конфигуратора.
- 🖥 В клиент-серверной базе отладку нужно разрешить на сервере 1С: агент сервера запускается с ключом
-debug. Без этого точки останова в процедурах&НаСервереигнорируются, а клиентские работают. - 🔍 Фоновое задание выполняется в своём сеансе. Чтобы остановиться внутри него, подключите в Конфигураторе отладку фоновых заданий (команды подключения находятся в меню «Отладка»).
Перезапуск службы агента с ключом -debug обрывает сеансы всех пользователей этого сервера. Делайте это на тестовом сервере или вне рабочего времени.