Как выполнить клиентский код по результату серверного вызова в 1С 8.3
Содержание
Напрямую — никак: в 1С 8.3 сервер не может вызвать процедуру с директивой &НаКлиенте, такой вызов не пройдёт даже проверку модуля. Рабочий приём другой: серверная функция возвращает результат или «заказ» на действие, а клиентская процедура, которая её вызвала, выполняет нужный клиентский код сама.
Способ выбирают по ситуации. Нужен один ответ после короткой операции — возвращают значение из функции &НаСервере. Сервер должен решить, какие формы открыть и что показать, — возвращают список команд. Операция длинная — запускают фоновое задание и опрашивают его с клиента обработчиком ожидания. Всё это работает одинаково в тонком клиенте, веб-клиенте и в файловом режиме.
Почему сервер 1С не видит клиентские процедуры
Модуль управляемой формы компилируется дважды: одна часть на клиенте, другая на сервере. Директива перед процедурой говорит платформе, в какую часть её включить. Процедура &НаКлиенте просто не существует в серверной сборке модуля, поэтому сервер «не знает» её имени.
Обмен идёт по одной схеме: клиент делает запрос — сервер отвечает. Сервер не держит канал, по которому мог бы сам постучаться к клиенту посреди своего выполнения. Пока серверная процедура работает, клиент ждёт её завершения.
Что где доступно:
- 🖥️ Только клиент: диалоги (
ПоказатьВопрос,ПоказатьПредупреждение),ОткрытьФорму,Оповестить,ПоказатьОповещениеПользователя,ПодключитьОбработчикОжидания, работа с файлами на компьютере пользователя. - 🗄️ Только сервер: запросы, объекты базы (
ДокументОбъект,СправочникОбъект), наборы записей регистров,РеквизитФормыВЗначение,ФоновыеЗадания. - 🔁 И там и там: примитивные типы,
Структура,Массив,Соответствие, ссылки на объекты базы,СообщениеПользователю.
Если серверный код попытается обратиться к клиентской процедуре формы, при проверке модуля вы получите ошибку вида Процедура или функция с указанным именем не определена с именем процедуры в скобках. Это не баг, а следствие архитектуры.
Сообщения, выведенные на сервере через Сообщить() или СообщениеПользователю, клиент покажет после возврата из серверного вызова. Это единственное «клиентское» действие, которое сервер делает сам, и то с задержкой.
Директивы компиляции и асинхронные вызовы
Директива определяет, где выполняется процедура и что она тащит с собой на сервер.
| Директива | Где выполняется | Контекст формы | Когда применять |
|---|---|---|---|
&НаКлиенте | клиент | весь, локально | диалоги, открытие форм, реакция на действия пользователя |
&НаСервере | сервер | передаётся туда и обратно | нужно прочитать или изменить реквизиты формы, объект формы |
&НаСервереБезКонтекста | сервер | не передаётся | расчёт по параметрам, запрос, проверка по ссылке |
&НаКлиентеНаСервереБезКонтекста | там, откуда вызвали | не передаётся | общая логика, не зависящая от формы |
В общих модулях директив нет, их заменяют флажки свойств: Клиент (управляемое приложение), Сервер, Вызов сервера, Внешнее соединение. Серверный общий модуль без флажка Вызов сервера из клиентского кода вызвать нельзя.
Если модуль собран и для клиента, и для сервера, разные ветки разделяют инструкциями препроцессора:
#Если Сервер Тогда
// код только для серверной сборки
#Иначе
// код для клиентской сборки
#КонецЕсли
Асинхронность — про клиент, а не про сервер
Сам вызов сервера всегда синхронный: клиентская процедура останавливается до ответа. Асинхронными бывают только клиентские операции — вопросы, предупреждения, выбор файла. Начиная с платформы 8.3.18 для них есть ключевые слова Асинх и Ждать:
&НаКлиенте
Асинх Процедура ОчиститьТабличнуюЧасть(Команда)
Ответ = Ждать ВопросАсинх("Удалить все строки?", РежимДиалогаВопрос.ДаНет);
Если Ответ = КодВозвратаДиалога.Да Тогда
ОчиститьНаСервере();
КонецЕсли;
КонецПроцедуры
&НаСервере
Процедура ОчиститьНаСервере()
Объект.Товары.Очистить();
КонецПроцедуры
На более старых релизах то же делают через ОписаниеОповещения и ПоказатьВопрос: обработчик ответа — отдельная экспортная процедура &НаКлиенте, и уже она вызывает сервер.
Способы выполнить клиентское действие после работы сервера
Ниже схемы, которые закрывают почти все реальные задачи. Сравнение — в конце раздела.
1. Вернуть результат из серверной функции
Самый простой и частый вариант. Сервер делает работу и возвращает структуру, клиент читает её и решает, что показать.
&НаКлиенте
Процедура ПровестиИЗакрыть(Команда)
Результат = ПровестиНаСервере();
Если Результат.Успех Тогда
ПоказатьОповещениеПользователя("Документ проведён");
Оповестить("ЗаписьДокумента", , Объект.Ссылка);
Закрыть();
Иначе
ПоказатьПредупреждение(, Результат.ТекстОшибки);
КонецЕсли;
КонецПроцедуры
&НаСервере
Функция ПровестиНаСервере()
Результат = Новый Структура("Успех, ТекстОшибки", Истина, "");
Попытка
ДокументОбъект = РеквизитФормыВЗначение("Объект");
ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
ЗначениеВРеквизитФормы(ДокументОбъект, "Объект");
Исключение
Результат.Успех = Ложь;
Результат.ТекстОшибки = КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
КонецПопытки;
Возврат Результат;
КонецФункции
Возвращайте только то, что можно передать на клиент: примитивы, структуры, массивы, ссылки. ДокументОбъект или ТаблицаЗначений на клиент не попадут.
2. Вернуть список команд для клиента
Когда сервер по данным решает, что делать дальше — открыть одну форму, другую, задать вопрос, — удобно вернуть массив «заказов». Клиент разбирает его в цикле.
&НаКлиенте
Процедура Обработать(Команда)
Действия = ОбработатьНаСервере();
Для Каждого Действие Из Действия Цикл
Если Действие.Вид = "ОткрытьФорму" Тогда
ОткрытьФорму(Действие.ИмяФормы, Действие.Параметры, ЭтотОбъект);
ИначеЕсли Действие.Вид = "Оповестить" Тогда
Оповестить(Действие.Событие, Действие.Параметры);
ИначеЕсли Действие.Вид = "Предупреждение" Тогда
ПоказатьПредупреждение(, Действие.Текст);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
&НаСервереБезКонтекста
Функция ОбработатьНаСервере()
Действия = Новый Массив;
// ... серверная логика ...
Действие = Новый Структура("Вид, Текст", "Предупреждение", "Остаток меньше нуля");
Действия.Добавить(Действие);
Возврат Действия;
КонецФункции
Так сервер фактически «вызывает» клиентские процедуры, но через посредника. Вся решающая логика остаётся на сервере, а клиентский код сводится к диспетчеру.
3. Фоновое задание и обработчик ожидания
Если операция идёт минуты, держать клиента в синхронном вызове плохо: интерфейс замирает. Запустите фоновое задание и опрашивайте его с клиента.
&НаКлиенте
Перем ИдентификаторЗадания;
&НаКлиенте
Процедура Запустить(Команда)
ИдентификаторЗадания = ЗапуститьНаСервере();
ПодключитьОбработчикОжидания("ПроверитьЗадание", 2, Истина);
КонецПроцедуры
&НаСервереБезКонтекста
Функция ЗапуститьНаСервере()
Задание = ФоновыеЗадания.Выполнить("МойСерверныйМодуль.ДлиннаяОбработка");
Возврат Задание.УникальныйИдентификатор;
КонецФункции
&НаКлиенте
Процедура ПроверитьЗадание()
Состояние = СостояниеЗаданияНаСервере(ИдентификаторЗадания);
Если Состояние = "Активно" Тогда
ПодключитьОбработчикОжидания("ПроверитьЗадание", 2, Истина);
ИначеЕсли Состояние = "Завершено" Тогда
ПоказатьОповещениеПользователя("Обработка завершена");
Иначе
ПоказатьПредупреждение(, "Задание завершилось с ошибкой");
КонецЕсли;
КонецПроцедуры
&НаСервереБезКонтекста
Функция СостояниеЗаданияНаСервере(Идентификатор)
Задание = ФоновыеЗадания.НайтиПоУникальномуИдентификатору(Идентификатор);
Если Задание = Неопределено Тогда
Возврат "Ошибка";
ИначеЕсли Задание.Состояние = СостояниеФоновогоЗадания.Активно Тогда
Возврат "Активно";
ИначеЕсли Задание.Состояние = СостояниеФоновогоЗадания.Завершено Тогда
Возврат "Завершено";
КонецЕсли;
Возврат "Ошибка";
КонецФункции
Процедура ДлиннаяОбработка должна лежать в общем серверном модуле и быть экспортной. Сообщения, которые она выводит, можно забрать методом Задание.ПолучитьСообщенияПользователю(Истина) и показать на клиенте.
В типовых конфигурациях на БСП не пишите это вручную: там есть ДлительныеОперации.ВыполнитьВФоне на сервере и ДлительныеОперацииКлиент.ОжидатьЗавершение на клиенте. Они сами показывают индикатор и вызывают ваш клиентский обработчик через ОписаниеОповещения.
4. Оповещение других форм
Сервер не умеет вызывать Оповестить, но клиент, получив ответ, может разослать событие всем открытым формам. Форма-получатель ловит его в ОбработкаОповещения и перечитывает данные. Так обновляют список после проведения документа в другой форме.
Какой способ выбрать
| Способ | Когда подходит | Ограничения |
|---|---|---|
| Возврат значения | короткая операция, один результат | интерфейс ждёт ответа |
| Список команд | сервер решает, какие формы и диалоги показать | нужен диспетчер на клиенте |
| Фоновое задание + опрос | операция дольше 5–10 секунд | задержка до интервала опроса |
Оповестить после ответа | обновить другие открытые формы | работает только в рамках одного сеанса |
Если нужно доставить сообщение в сеанс другого пользователя, вызов процедуры не поможет вообще. Для этого используют систему взаимодействия платформы — она требует отдельной регистрации и настройки, и это уже задача обмена сообщениями, а не вызова кода.
Как передать параметры и контекст формы на сервер
Каждый серверный вызов — это пересылка данных по сети. Чем больше передаёте, тем медленнее форма, особенно в веб-клиенте и через интернет .
С директивой &НаСервере платформа отправляет на сервер данные формы и возвращает изменённые обратно. На большой форме с табличными частями на 2 000 строк это ощутимо. Если процедуре нужен только склад и дата, используйте &НаСервереБезКонтекста и передайте их параметрами:
&НаКлиенте
Процедура СкладПриИзменении(Элемент)
Остаток = ОстатокНаСклад(Объект.Склад, Объект.Номенклатура, Объект.Дата);
Объект.Количество = Мин(Объект.Количество, Остаток);
КонецПроцедуры
&НаСервереБезКонтекста
Функция ОстатокНаСклад(Склад, Номенклатура, Дата)
// запрос к регистру остатков по параметрам
Возврат 0;
КонецФункции
Правила передачи:
- 📦 Структуру и массив передавайте, только если внутри — сериализуемые значения.
- 🔗 Вместо объекта передавайте ссылку, а объект получайте уже на сервере.
- 📋 Табличную часть формы на сервере превращайте в таблицу значений через
РеквизитФормыВЗначение, обратно — черезЗначениеВРеквизитФормы. - 🧩 Для тяжёлых данных, которые нужны между вызовами, используйте временное хранилище:
ПоместитьВоВременноеХранилищевозвращает адрес-строку, её и гоняйте между клиентом и сервером.
Элементы формы в процедуре &НаСервере тоже доступны: можно поменять видимость, заголовок, доступность. Изменения вернутся на клиент вместе с контекстом.
Клиентский и серверный код в управляемых формах
В управляемой форме событие сначала срабатывает на клиенте. Обработчик &НаКлиенте решает: хватит ли клиентских данных или нужен сервер. Хорошая форма почти не ходит на сервер по пустякам.
Что держать на клиенте:
- ✏️ проверку заполнения полей, которые уже есть в форме;
- 🧮 простые расчёты: сумма = количество × цена;
- 💬 все вопросы и предупреждения пользователю;
- 🪟 открытие форм выбора и подчинённых форм.
Что отправлять на сервер:
- 🔍 запросы к базе и получение реквизитов по ссылке;
- 💾 запись и проведение объектов;
- 🔐 действия, требующие прав, — через привилегированный общий модуль, если это оправдано.
Отдельный случай — серверные события формы: ПриСозданииНаСервере, ПередЗаписьюНаСервере, ПриЗаписиНаСервере. Из них тоже нельзя открыть форму или задать вопрос. Если в ПриСозданииНаСервере выяснилось, что форму открывать нельзя, установите Отказ = Истина и выведите сообщение — пользователь увидит его на клиенте. Если нужно что-то сделать сразу после открытия, сохраните признак в реквизите формы, а в клиентском ПриОткрытии проверьте его и выполните действие.
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
НужноПредупредить = ЕстьНезакрытыеЗаявки();
КонецПроцедуры
&НаКлиенте
Процедура ПриОткрытии(Отказ)
Если НужноПредупредить Тогда
ПоказатьПредупреждение(, "Есть незакрытые заявки");
КонецЕсли;
КонецПроцедуры
Здесь НужноПредупредить — реквизит формы с типом Булево, а ЕстьНезакрытыеЗаявки — серверная функция модуля формы.
Обработка ошибок и отладка клиент-серверного кода
Исключение, не перехваченное на сервере, приходит на клиент и прерывает клиентскую процедуру. Пользователь видит окно с текстом ошибки, а код после серверного вызова не выполняется.
Как обращаться с ошибками:
- На сервере оборачивайте опасный участок в
Попытка … Исключение. - Подробности пишите в журнал регистрации:
ЗаписьЖурналаРегистрациис текстомПодробноеПредставлениеОшибки(ИнформацияОбОшибке()). - На клиент возвращайте короткий понятный текст —
КраткоеПредставлениеОшибки— в поле структуры результата. - Если операцию нужно прервать целиком, вызовите
ВызватьИсключениес понятным текстом: клиент получит его как обычную ошибку.
Не глушите исключения пустым блоком Исключение: транзакция может остаться в неконсистентном состоянии, а ошибка всплывёт в другом месте без следа.
Отладка
- 🐞 В файловой базе точки останова срабатывают и в клиентском, и в серверном коде без дополнительной настройки.
- 🖧 В клиент-серверной базе сервер 1С должен быть запущен с ключом отладки
-debug. Включить его — значит перезапустить службу агента сервера, а это оборвёт сеансы всех пользователей: делайте в нерабочее время или на тестовом сервере. - 🔌 В конфигураторе в настройках подключения к предметам отладки включите автоматическое подключение серверных сеансов и фоновых заданий, иначе точки в серверных процедурах и фоновой обработке не сработают.
- ⏱️
Замер производительностив меню отладки покажет, сколько раз форма вызывает сервер и где тратится время.
Ошибки проектирования, которые повторяются
- ❌ Серверный вызов в цикле на клиенте. Обработка 300 строк табличной части по одной — 300 обращений к серверу. Отправьте все строки одним вызовом.
- ❌ Вопрос пользователю посреди серверной логики. Сервер не может спросить и подождать. Разбейте операцию: проверка на сервере → вопрос на клиенте → основная работа на сервере.
- ❌ Клиентский флажок у общего модуля «на всякий случай». Модуль с флажками
КлиентиСервербез нужды компилируется дважды и путает разработчика. Разделите наМойМодульКлиентиМойМодульСервер. - ❌ Длинная операция в синхронном вызове. Пользователь видит зависшую форму и закрывает 1С из диспетчера задач. Всё, что дольше нескольких секунд, — в фоновое задание.
- ❌ Передача объекта целиком. Ссылки достаточно в 9 случаях из 10 — объект получайте на сервере методом
ПолучитьОбъект().
Частые вопросы
Можно ли из модуля объекта документа показать вопрос пользователю?
ОбработкаПроверкиЗаполнения оставьте только отказ и сообщение.Работает ли вызов сервера из обработчика ожидания?
Вызов сервера.Почему сообщение с сервера появилось только после завершения процедуры?
Можно ли передать на сервер ОписаниеОповещения, чтобы сервер его выполнил?
ВыполнитьОбработкуОповещения работает только на клиенте. Верните с сервера данные и вызовите обработчик на клиенте.