Сервер не вызывает клиент: рабочие схемы для управляемых форм 1С

· 9 мин

Содержание

Запустить клиентскую процедуру с сервера в 1С напрямую нельзя: процедура с директивой &НаКлиенте из серверного кода не видна, а платформа не даёт серверу инициировать вызов к клиенту. Рабочий способ один по сути: сервер возвращает клиенту данные или «команду», а клиент сам решает, какую процедуру выполнить.

Ниже схемы, которыми это делают на практике в управляемых формах платформы 8.3: от простого возврата значения до фонового задания с опросом и серверных оповещений БСП. Всё, что описано, относится к платформе в целом и не зависит от конфигурации.

Почему серверный код не видит процедуры с директивой &НаКлиенте

В управляемом приложении код формы физически живёт в двух местах. Часть компилируется и выполняется на компьютере пользователя (тонкий клиент, веб-клиент), часть — на сервере 1С. Директива перед процедурой говорит компилятору, где её собрать.

ДирективаГде выполняетсяДоступ к данным формы
&НаКлиентена клиентеполный, к реквизитам и элементам
&НаСерверена серверек реквизитам формы (контекстный вызов)
&НаСервереБезКонтекстана серверенет, только параметры
&НаКлиентеНаСервереБезКонтекстаи там, и тамнет, только параметры

Вызов идёт только в одну сторону: клиент → сервер. Клиент отправляет запрос, сервер выполняет код и возвращает результат. Обратного канала, по которому сервер мог бы посреди своей работы «дёрнуть» клиентскую процедуру, у платформы нет.

Если всё же написать в процедуре &НаСервере вызов клиентской процедуры, модуль не скомпилируется. Конфигуратор сообщит, что процедура или функция с таким именем не определена: в серверной версии модуля её просто нет.

С общими модулями то же самое. Модуль с флажком «Клиент (управляемое приложение)» не виден из серверного кода. Модуль с флажками «Сервер» и «Вызов сервера» виден клиенту, но не наоборот.

Отсюда главное правило проектирования: вызов всегда начинает клиент. Сервер готовит решение, клиент его исполняет.

Сервер возвращает команду, клиент её выполняет

Встроенного метода вроде «ВыполнитьНаКлиенте» в платформе нет. Его роль играет обычная серверная функция, которая возвращает описание того, что нужно сделать. Клиент разбирает ответ и вызывает свои процедуры.

Это самый частый приём у разработчиков: сервер проверяет данные, записывает объект и складывает в результат список действий. Клиент проходит по списку.

&НаКлиенте
Процедура ВыполнитьРасчет(Команда)
	Ответ = ВыполнитьРасчетНаСервере();
	ВыполнитьДействияКлиента(Ответ);
КонецПроцедуры

&НаСервере
Функция ВыполнитьРасчетНаСервере()
	Ответ = Новый Структура("Действия", Новый Массив);
	// ... серверная логика: запросы, запись, проверки
	Если Объект.Товары.Количество() = 0 Тогда
		Ответ.Действия.Добавить(Новый Структура("Имя, Параметр",
			"Предупреждение", "Табличная часть пуста"));
		Возврат Ответ;
	КонецЕсли;
	Ответ.Действия.Добавить(Новый Структура("Имя, Параметр",
		"ОбновитьСписки", Объект.Ссылка));
	Возврат Ответ;
КонецФункции

&НаКлиенте
Процедура ВыполнитьДействияКлиента(Ответ)
	Для Каждого Действие Из Ответ.Действия Цикл
		Если Действие.Имя = "Предупреждение" Тогда
			ПоказатьПредупреждение(, Действие.Параметр);
		ИначеЕсли Действие.Имя = "ОбновитьСписки" Тогда
			ОповеститьОбИзменении(Действие.Параметр);
		ИначеЕсли Действие.Имя = "ОткрытьФорму" Тогда
			ОткрытьФорму(Действие.Параметр);
		КонецЕсли;
	КонецЦикла;
КонецПроцедуры

Чем удобна такая схема:

  • 🧩 сервер не знает, как именно клиент покажет сообщение, и не зависит от формы;
  • 🔁 один серверный вызов вместо нескольких: меньше обращений по сети;
  • 🧪 серверную функцию легко проверить отдельно — она просто возвращает структуру.

Имена действий держите в одном месте, например в клиентском общем модуле. Опечатка в строке «Предупреждение» не даст ошибки компиляции, действие молча не выполнится.

Если имя процедуры приходит с сервера строкой, соблазнительно вызвать её через Выполнить(). Делать так не стоит: код из строки трудно отлаживать, а в веб-клиенте и при включённом безопасном режиме это источник проблем. Явная ветка Если … ИначеЕсли надёжнее.

Связка серверного и клиентского события формы

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

Серверное событиеКлиентское событие после него
ПриСозданииНаСервереПриОткрытии
ПередЗаписьюНаСервере, ПослеЗаписиНаСервереПослеЗаписи
ПриЧтенииНаСервереПриОткрытии (при открытии формы)

Флаг в реквизите формы

Заведите реквизит формы, например ПоказатьПодсказку с типом «Булево». В ПриСозданииНаСервере установите его, в ПриОткрытии проверьте и вызовите нужную клиентскую процедуру.

&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
	ПоказатьПодсказку = НЕ ЗначениеЗаполнено(Объект.Ответственный);
КонецПроцедуры

&НаКлиенте
Процедура ПриОткрытии(Отказ)
	Если ПоказатьПодсказку Тогда
		ПоказатьОповещениеПользователя("Не указан ответственный");
	КонецЕсли;
КонецПроцедуры

Параметры записи

Структура ПараметрыЗаписи проходит через всю цепочку записи объекта из формы. Ключ, добавленный в ПослеЗаписиНаСервере, доступен в клиентском ПослеЗаписи.

&НаСервере
Процедура ПослеЗаписиНаСервере(ТекущийОбъект, ПараметрыЗаписи)
	ПараметрыЗаписи.Вставить("НужноОбновитьОстатки", Истина);
КонецПроцедуры

&НаКлиенте
Процедура ПослеЗаписи(ПараметрыЗаписи)
	Если ПараметрыЗаписи.Свойство("НужноОбновитьОстатки") Тогда
		Оповестить("ОбновитьОстатки");
	КонецЕсли;
КонецПроцедуры

Это чистый способ «запустить клиентский код по итогам серверной записи» без дополнительных реквизитов формы.

Оповещения: Оповестить, ОписаниеОповещения и серверные оповещения БСП

Механизм оповещений в платформе клиентский. Методы Оповестить() и ОповеститьОбИзменении() вызываются только на клиенте. Поэтому сервер снова возвращает признак, а клиент рассылает оповещение другим открытым формам.

Получают его в событии формы ОбработкаОповещения:

&НаКлиенте
Процедура ОбработкаОповещения(ИмяСобытия, Параметр, Источник)
	Если ИмяСобытия = "ОбновитьОстатки" Тогда
		ОбновитьОстаткиНаСервере();
	КонецЕсли;
КонецПроцедуры

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

Когда клиент должен узнать о событии сам

Бывает, что событие происходит вне сеанса пользователя: регламентное задание завершило загрузку, другой пользователь согласовал документ. Здесь нужен канал «сервер → клиент» без действия пользователя.

Типовые конфигурации на свежих версиях БСП решают это подсистемой «Серверные оповещения». Сервер ставит оповещение в очередь для нужных пользователей, клиент получает его и вызывает обработчик. Если в базе настроена система взаимодействия, доставка почти мгновенная. Без неё клиент сам периодически запрашивает сервер.

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

Что можно передать между сервером и клиентом

Всё, что уходит на сервер и обратно, платформа сериализует. Отсюда ограничения на типы.

✅ Передаются без проблем:

  • примитивы: Число, Строка, Дата, Булево, Неопределено;
  • ссылки на объекты: СправочникСсылка, ДокументСсылка и другие;
  • Структура, Массив, Соответствие, СписокЗначений, ФиксированнаяСтруктура;
  • УникальныйИдентификатор, ДвоичныеДанные.

❌ Не передаются или недоступны на клиенте:

  • ТаблицаЗначений, ДеревоЗначений, Запрос, объекты (ДокументОбъект, СправочникОбъект);
  • сама форма, её элементы, ОписаниеОповещения — живут только на клиенте.

Как передать таблицу или объект

  • 📋 Таблицу значений превратите в массив структур или в реквизит формы с типом «ТаблицаЗначений» — на клиенте он виден как ДанныеФормыКоллекция.
  • 🧾 Объект передавайте ссылкой, а на сервере получайте заново: Ссылка.ПолучитьОбъект().
  • 📦 Крупные данные (файлы, большие выборки) кладите во временное хранилище: ПоместитьВоВременноеХранилище() на сервере, а клиенту возвращайте только адрес. Клиент забирает данные через ПолучитьИзВременногоХранилища() или передаёт адрес дальше.

Контекстный и безконтекстный вызов

При вызове &НаСервере платформа гоняет на сервер и обратно все данные формы. Для формы документа с табличной частью на тысячу строк это заметная задержка на каждый клик.

Если серверному коду реквизиты формы не нужны, ставьте &НаСервереБезКонтекста и передавайте только нужные значения параметрами. Изменения параметров при таком вызове обратно не возвращаются: всё, что нужно клиенту, отдавайте возвращаемым значением функции.

Длительная операция: фоновое задание и опрос с клиента

Серверный вызов синхронный: пока он идёт, клиент ждёт. Если расчёт длится минуту, форма «висит». Правильная схема — запустить работу фоновым заданием и периодически спрашивать о его состоянии. Когда задание закончилось, клиент вызывает свою процедуру с результатом.

// ИдентификаторЗадания, АдресРезультата — реквизиты формы

&НаКлиенте
Процедура Пересчитать(Команда)
	ЗапуститьЗаданиеНаСервере();
	ПодключитьОбработчикОжидания("ПроверитьЗадание", 2);
КонецПроцедуры

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

&НаКлиенте
Процедура ПроверитьЗадание()
	Состояние = СостояниеЗадания(ИдентификаторЗадания);
	Если Состояние = "Активно" Тогда
		Возврат;
	КонецЕсли;
	ОтключитьОбработчикОжидания("ПроверитьЗадание");
	ПослеПересчета(Состояние);   // клиентская процедура «по сигналу сервера»
КонецПроцедуры

&НаСервереБезКонтекста
Функция СостояниеЗадания(Идентификатор)
	Задание = ФоновыеЗадания.НайтиПоУникальномуИдентификатору(Идентификатор);
	Если Задание = Неопределено Тогда
		Возврат "НеНайдено";
	ИначеЕсли Задание.Состояние = СостояниеФоновогоЗадания.Активно Тогда
		Возврат "Активно";
	ИначеЕсли Задание.Состояние = СостояниеФоновогоЗадания.Завершено Тогда
		Возврат "Завершено";
	КонецЕсли;
	Возврат "Ошибка";
КонецФункции

В процедуре МойСерверныйМодуль.Пересчитать задание в конце кладёт результат по полученному адресу: ПоместитьВоВременноеХранилище(Результат, Адрес). Клиент в ПослеПересчета забирает его по АдресРезультата.

Что учесть:

  • ⏱ интервал опроса 1-3 секунды; чаще — лишняя нагрузка на сервер от каждой открытой формы;
  • 💬 сообщения, которые задание вывело через СообщениеПользователю, клиент получит методом задания ПолучитьСообщенияПользователю() — вызывать его нужно на сервере;
  • 🧰 в конфигурациях на БСП не пишите это вручную: модули ДлительныеОперации и ДлительныеОперацииКлиент делают то же самое с индикатором ожидания и обработкой ошибок.

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

Асинх и Ждать

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

Ошибки и отладка при вызове между клиентом и сервером

Диагностика: выберите симптом

Как ловить исключения с сервера

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

&НаКлиенте
Процедура Провести(Команда)
	Попытка
		ПровестиНаСервере();
	Исключение
		Инфо = ИнформацияОбОшибке();
		ПоказатьПредупреждение(, КраткоеПредставлениеОшибки(Инфо));
	КонецПопытки;
КонецПроцедуры

Для пользователя показывайте краткое представление, а подробное (ПодробноеПредставлениеОшибки) пишите в журнал регистрации на сервере. Там видны модуль и строка, где упал код.

Отладка серверной части

  • 🐞 В файловой базе точки останова в серверных процедурах срабатывают без дополнительных настроек при запуске из Конфигуратора.
  • 🖥 В клиент-серверной базе отладку нужно разрешить на сервере 1С: агент сервера запускается с ключом -debug. Без этого точки останова в процедурах &НаСервере игнорируются, а клиентские работают.
  • 🔍 Фоновое задание выполняется в своём сеансе. Чтобы остановиться внутри него, подключите в Конфигураторе отладку фоновых заданий (команды подключения находятся в меню «Отладка»).

Перезапуск службы агента с ключом -debug обрывает сеансы всех пользователей этого сервера. Делайте это на тестовом сервере или вне рабочего времени.

Частые вопросы

Можно ли вызвать клиентскую процедуру из общего модуля с флажком «Вызов сервера»?
Нет. Такой модуль выполняется на сервере и видит только серверные процедуры. Верните результат в клиентский код и вызовите процедуру там.
Работает ли Сообщить() из серверной процедуры?
Да. Сообщения, выведенные на сервере, накапливаются и показываются пользователю, когда серверный вызов вернётся на клиент. Для привязки к полю формы используйте объект СообщениеПользователю.
Как открыть форму по результату серверной проверки?
ОткрытьФорму() доступен только на клиенте. Сервер возвращает имя формы и параметры, клиент вызывает ОткрытьФорму или ОткрытьФормуАсинх.
Можно ли из регламентного задания показать сообщение пользователю?
Напрямую нет: у регламентного задания нет клиентского сеанса. Нужен канал доставки — серверные оповещения БСП, система взаимодействия или запись в регистр с опросом из клиента.
Директива &НаКлиентеНаСервереБезКонтекста решает задачу?
Нет. Такая процедура компилируется в обе части модуля, но вызванная с сервера выполняется на сервере. Клиентские методы вроде ПоказатьПредупреждение в ней недоступны.