Как выполнить клиентский код по результату серверного вызова в 1С 8.3

· 10 мин

Содержание

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

Способ выбирают по ситуации. Нужен один ответ после короткой операции — возвращают значение из функции &НаСервере. Сервер должен решить, какие формы открыть и что показать, — возвращают список команд. Операция длинная — запускают фоновое задание и опрашивают его с клиента обработчиком ожидания. Всё это работает одинаково в тонком клиенте, веб-клиенте и в файловом режиме.

Почему сервер 1С не видит клиентские процедуры

Модуль управляемой формы компилируется дважды: одна часть на клиенте, другая на сервере. Директива перед процедурой говорит платформе, в какую часть её включить. Процедура &НаКлиенте просто не существует в серверной сборке модуля, поэтому сервер «не знает» её имени.

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

Что где доступно:

  • 🖥️ Только клиент: диалоги (ПоказатьВопрос, ПоказатьПредупреждение), ОткрытьФорму, Оповестить, ПоказатьОповещениеПользователя, ПодключитьОбработчикОжидания, работа с файлами на компьютере пользователя.
  • 🗄️ Только сервер: запросы, объекты базы (ДокументОбъект, СправочникОбъект), наборы записей регистров, РеквизитФормыВЗначение, ФоновыеЗадания.
  • 🔁 И там и там: примитивные типы, Структура, Массив, Соответствие, ссылки на объекты базы, СообщениеПользователю.

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

Сообщения, выведенные на сервере через Сообщить() или СообщениеПользователю, клиент покажет после возврата из серверного вызова. Это единственное «клиентское» действие, которое сервер делает сам, и то с задержкой.

Директивы компиляции и асинхронные вызовы

Директива определяет, где выполняется процедура и что она тащит с собой на сервер.

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

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

Если модуль собран и для клиента, и для сервера, разные ветки разделяют инструкциями препроцессора:

#Если Сервер Тогда
	// код только для серверной сборки
#Иначе
	// код для клиентской сборки
#КонецЕсли

Асинхронность — про клиент, а не про сервер

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

&НаКлиенте
Асинх Процедура ОчиститьТабличнуюЧасть(Команда)
	Ответ = Ждать ВопросАсинх("Удалить все строки?", РежимДиалогаВопрос.ДаНет);
	Если Ответ = КодВозвратаДиалога.Да Тогда
		ОчиститьНаСервере();
	КонецЕсли;
КонецПроцедуры

&НаСервере
Процедура ОчиститьНаСервере()
	Объект.Товары.Очистить();
КонецПроцедуры

На более старых релизах то же делают через ОписаниеОповещения и ПоказатьВопрос: обработчик ответа — отдельная экспортная процедура &НаКлиенте, и уже она вызывает сервер.

Способы выполнить клиентское действие после работы сервера

Ниже схемы, которые закрывают почти все реальные задачи. Сравнение — в конце раздела.

1. Вернуть результат из серверной функции

Самый простой и частый вариант. Сервер делает работу и возвращает структуру, клиент читает её и решает, что показать.

&НаКлиенте
Процедура ПровестиИЗакрыть(Команда)
	Результат = ПровестиНаСервере();
	Если Результат.Успех Тогда
		ПоказатьОповещениеПользователя("Документ проведён");
		Оповестить("ЗаписьДокумента", , Объект.Ссылка);
		Закрыть();
	Иначе
		ПоказатьПредупреждение(, Результат.ТекстОшибки);
	КонецЕсли;
КонецПроцедуры

&НаСервере
Функция ПровестиНаСервере()
	Результат = Новый Структура("Успех, ТекстОшибки", Истина, "");
	Попытка
		ДокументОбъект = РеквизитФормыВЗначение("Объект");
		ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
		ЗначениеВРеквизитФормы(ДокументОбъект, "Объект");
	Исключение
		Результат.Успех = Ложь;
		Результат.ТекстОшибки = КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
	КонецПопытки;
	Возврат Результат;
КонецФункции

Возвращайте только то, что можно передать на клиент: примитивы, структуры, массивы, ссылки. ДокументОбъект или ТаблицаЗначений на клиент не попадут.

2. Вернуть список команд для клиента

Когда сервер по данным решает, что делать дальше — открыть одну форму, другую, задать вопрос, — удобно вернуть массив «заказов». Клиент разбирает его в цикле.

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

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

Так сервер фактически «вызывает» клиентские процедуры, но через посредника. Вся решающая логика остаётся на сервере, а клиентский код сводится к диспетчеру.

3. Фоновое задание и обработчик ожидания

Если операция идёт минуты, держать клиента в синхронном вызове плохо: интерфейс замирает. Запустите фоновое задание и опрашивайте его с клиента.

&НаКлиенте
Перем ИдентификаторЗадания;

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

&НаСервереБезКонтекста
Функция ЗапуститьНаСервере()
	Задание = ФоновыеЗадания.Выполнить("МойСерверныйМодуль.ДлиннаяОбработка");
	Возврат Задание.УникальныйИдентификатор;
КонецФункции

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

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

Процедура ДлиннаяОбработка должна лежать в общем серверном модуле и быть экспортной. Сообщения, которые она выводит, можно забрать методом Задание.ПолучитьСообщенияПользователю(Истина) и показать на клиенте.

В типовых конфигурациях на БСП не пишите это вручную: там есть ДлительныеОперации.ВыполнитьВФоне на сервере и ДлительныеОперацииКлиент.ОжидатьЗавершение на клиенте. Они сами показывают индикатор и вызывают ваш клиентский обработчик через ОписаниеОповещения.

4. Оповещение других форм

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

Какой способ выбрать

СпособКогда подходитОграничения
Возврат значениякороткая операция, один результатинтерфейс ждёт ответа
Список командсервер решает, какие формы и диалоги показатьнужен диспетчер на клиенте
Фоновое задание + опросоперация дольше 5–10 секундзадержка до интервала опроса
Оповестить после ответаобновить другие открытые формыработает только в рамках одного сеанса

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

Как передать параметры и контекст формы на сервер

Каждый серверный вызов — это пересылка данных по сети. Чем больше передаёте, тем медленнее форма, особенно в веб-клиенте и через интернет .

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

&НаКлиенте
Процедура СкладПриИзменении(Элемент)
	Остаток = ОстатокНаСклад(Объект.Склад, Объект.Номенклатура, Объект.Дата);
	Объект.Количество = Мин(Объект.Количество, Остаток);
КонецПроцедуры

&НаСервереБезКонтекста
Функция ОстатокНаСклад(Склад, Номенклатура, Дата)
	// запрос к регистру остатков по параметрам
	Возврат 0;
КонецФункции

Правила передачи:

  • 📦 Структуру и массив передавайте, только если внутри — сериализуемые значения.
  • 🔗 Вместо объекта передавайте ссылку, а объект получайте уже на сервере.
  • 📋 Табличную часть формы на сервере превращайте в таблицу значений через РеквизитФормыВЗначение, обратно — через ЗначениеВРеквизитФормы.
  • 🧩 Для тяжёлых данных, которые нужны между вызовами, используйте временное хранилище: ПоместитьВоВременноеХранилище возвращает адрес-строку, её и гоняйте между клиентом и сервером.

Элементы формы в процедуре &НаСервере тоже доступны: можно поменять видимость, заголовок, доступность. Изменения вернутся на клиент вместе с контекстом.

Клиентский и серверный код в управляемых формах

В управляемой форме событие сначала срабатывает на клиенте. Обработчик &НаКлиенте решает: хватит ли клиентских данных или нужен сервер. Хорошая форма почти не ходит на сервер по пустякам.

Что держать на клиенте:

  • ✏️ проверку заполнения полей, которые уже есть в форме;
  • 🧮 простые расчёты: сумма = количество × цена;
  • 💬 все вопросы и предупреждения пользователю;
  • 🪟 открытие форм выбора и подчинённых форм.

Что отправлять на сервер:

  • 🔍 запросы к базе и получение реквизитов по ссылке;
  • 💾 запись и проведение объектов;
  • 🔐 действия, требующие прав, — через привилегированный общий модуль, если это оправдано.

Отдельный случай — серверные события формы: ПриСозданииНаСервере, ПередЗаписьюНаСервере, ПриЗаписиНаСервере. Из них тоже нельзя открыть форму или задать вопрос. Если в ПриСозданииНаСервере выяснилось, что форму открывать нельзя, установите Отказ = Истина и выведите сообщение — пользователь увидит его на клиенте. Если нужно что-то сделать сразу после открытия, сохраните признак в реквизите формы, а в клиентском ПриОткрытии проверьте его и выполните действие.

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

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

Здесь НужноПредупредить — реквизит формы с типом Булево, а ЕстьНезакрытыеЗаявки — серверная функция модуля формы.

Обработка ошибок и отладка клиент-серверного кода

Исключение, не перехваченное на сервере, приходит на клиент и прерывает клиентскую процедуру. Пользователь видит окно с текстом ошибки, а код после серверного вызова не выполняется.

Как обращаться с ошибками:

  1. На сервере оборачивайте опасный участок в Попытка … Исключение.
  2. Подробности пишите в журнал регистрации: ЗаписьЖурналаРегистрации с текстом ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()).
  3. На клиент возвращайте короткий понятный текст — КраткоеПредставлениеОшибки — в поле структуры результата.
  4. Если операцию нужно прервать целиком, вызовите ВызватьИсключение с понятным текстом: клиент получит его как обычную ошибку.

Не глушите исключения пустым блоком Исключение: транзакция может остаться в неконсистентном состоянии, а ошибка всплывёт в другом месте без следа.

Отладка

  • 🐞 В файловой базе точки останова срабатывают и в клиентском, и в серверном коде без дополнительной настройки.
  • 🖧 В клиент-серверной базе сервер 1С должен быть запущен с ключом отладки -debug. Включить его — значит перезапустить службу агента сервера, а это оборвёт сеансы всех пользователей: делайте в нерабочее время или на тестовом сервере.
  • 🔌 В конфигураторе в настройках подключения к предметам отладки включите автоматическое подключение серверных сеансов и фоновых заданий, иначе точки в серверных процедурах и фоновой обработке не сработают.
  • ⏱️ Замер производительности в меню отладки покажет, сколько раз форма вызывает сервер и где тратится время.
Диагностика: выберите симптом

Ошибки проектирования, которые повторяются

  • ❌ Серверный вызов в цикле на клиенте. Обработка 300 строк табличной части по одной — 300 обращений к серверу. Отправьте все строки одним вызовом.
  • ❌ Вопрос пользователю посреди серверной логики. Сервер не может спросить и подождать. Разбейте операцию: проверка на сервере → вопрос на клиенте → основная работа на сервере.
  • ❌ Клиентский флажок у общего модуля «на всякий случай». Модуль с флажками Клиент и Сервер без нужды компилируется дважды и путает разработчика. Разделите на МойМодульКлиент и МойМодульСервер.
  • ❌ Длинная операция в синхронном вызове. Пользователь видит зависшую форму и закрывает 1С из диспетчера задач. Всё, что дольше нескольких секунд, — в фоновое задание.
  • ❌ Передача объекта целиком. Ссылки достаточно в 9 случаях из 10 — объект получайте на сервере методом ПолучитьОбъект().

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

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