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

· 10 мин

Содержание

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

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

СпособКогда выбиратьОграничения
Возвращаемое значение функцииНужно одно значение или структураТолько сериализуемые типы
Реквизиты формыСервер меняет данные формыРаботает только с контекстом формы
ИсключениеНужно прервать операцию с текстом ошибкиТранзакция откатывается
Фоновое заданиеОперация дольше нескольких секундРезультат забирают опросом
Временное хранилищеБольшие таблицы, двоичные данные, файлыДанные живут ограниченное время

Как устроен переход между клиентом и сервером в 1С 8.3

Код модуля управляемой формы разделён директивами компиляции. Каждая говорит платформе, где выполнять процедуру.

  • 🖥️ &НаКлиенте — выполняется в клиентском приложении: тонкий клиент, веб-клиент, мобильный клиент.
  • 🗄️ &НаСервере — выполняется на сервере, получает копию данных формы и возвращает её обратно.
  • ⚡ &НаСервереБезКонтекста — выполняется на сервере, но данные формы не передаются. Дешевле по трафику.
  • 🔁 &НаКлиентеНаСервереБезКонтекста — общий код, который можно вызвать с любой стороны.

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

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

Сообщения, созданные на сервере через Сообщить() или объект СообщениеПользователю, платформа накапливает и показывает на клиенте после возврата из серверного вызова. Для простого текста ошибки этого хватает без дополнительного кода.

Общие модули работают по той же схеме. Чтобы клиент мог вызвать процедуру общего модуля, у модуля в свойствах должны стоять флажки Сервер и Вызов сервера, а сама процедура — объявлена с Экспорт.

Способ 1. Вернуть результат через значение функции и параметры

Самый частый вариант. Серверную часть оформляют не процедурой, а функцией, и возвращают ответ оператором Возврат.

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

&НаСервереБезКонтекста
Функция РассчитатьНаСервере(Ссылка)
    Результат = Новый Структура("Успешно, ТекстОшибки, Сумма", Ложь, "", 0);
    // ... расчёт
    Результат.Успешно = Истина;
    Результат.Сумма = 1500;
    Возврат Результат;
КонецФункции

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

Возврат через параметры

Параметры серверной процедуры по умолчанию передаются по ссылке. Если внутри процедуры присвоить параметру новое значение, клиент увидит его после возврата. Параметр, объявленный с Знач, передаётся по значению, и изменения на клиент не вернутся.

&НаСервереБезКонтекста
Процедура ПроверитьОстаток(Номенклатура, Остаток, ЕстьОшибка)
    Остаток = 25;
    ЕстьОшибка = Ложь;
КонецПроцедуры

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

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

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

Способ 2. «Пустой» возврат: функции ВозвратНеопределено в языке нет

В обсуждениях встречается «функция ВозвратНеопределённо()». Такого метода во встроенном языке 1С нет. Под этим обычно имеют в виду два обычных приёма.

  1. Функция возвращает Неопределено как сигнал «результата нет». Клиент проверяет: Если Результат = Неопределено Тогда.
  2. Досрочный выход оператором Возврат без значения в процедуре. Серверный код прекращается, управление уходит на клиент.
&НаСервереБезКонтекста
Функция НайтиДоговор(Контрагент)
    Если Не ЗначениеЗаполнено(Контрагент) Тогда
        Возврат Неопределено;
    КонецЕсли;
    // ... поиск договора
    Возврат НайденныйДоговор;
КонецФункции

Такой «пустой» возврат хорош для поиска и проверок. Но у него есть слабое место: клиент не знает почему результата нет. Если причин несколько, верните структуру с полем Причина или используйте исключение.

Не возвращайте Неопределено там, где клиент обращается к полям результата без проверки. Строка Результат.Сумма при Результат = Неопределено вызовет ошибку уже на клиенте.

Способ 3. Длительные операции: фоновое задание и опрос с клиента

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

Схема из трёх шагов:

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

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

Внутри МойМодуль.ВыполнитьРасчет результат пишут в хранилище по переданному адресу через ПоместитьВоВременноеХранилище(Данные, Адрес).

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

Асинх и Ждать

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

Способ 4. Исключение как сигнал прервать операцию

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

Дальше два варианта:

  • 🔴 Клиент не обрабатывает исключение — пользователь увидит стандартное окно ошибки, клиентский код после вызова не выполнится.
  • 🟢 Клиент перехватывает через Попытка ... Исключение и показывает понятный текст.
&НаКлиенте
Процедура Провести(Команда)
    Попытка
        ПровестиНаСервере();
    Исключение
        ПоказатьПредупреждение(, ОписаниеОшибки());
        Возврат;
    КонецПопытки;
    Закрыть();
КонецПроцедуры

У исключения есть побочный эффект, о котором часто забывают: открытая на сервере транзакция откатывается. Для записи документа это правильно — половина изменений не сохранится. Для массовой обработки — нет: одна ошибка в сотом документе отменит предыдущие 99, если они были в одной транзакции.

Исключения оставляйте для действительно исключительных ситуаций. Штатный результат вроде «остатка не хватает» возвращайте структурой: исключения дороже и засоряют журнал регистрации.

Способ 5. Временное хранилище для больших данных и файлов

Временное хранилище — область на сервере, куда кладут значение и получают строковый адрес. На клиент передаётся только адрес, а сами данные забирают, когда они понадобятся.

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

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

Где хранилище незаменимо:

  • 📄 Файлы. Сервер формирует ДвоичныеДанные (например, выгрузку в Excel), кладёт в хранилище, клиент сохраняет файл. В БСП для этого есть ФайловаяСистемаКлиент.СохранитьФайл.
  • 📊 Таблицы значений. Сама ТаблицаЗначений на клиент не передаётся, а через хранилище её можно вернуть на сервер в следующем вызове без повторного запроса.
  • ⏳ Результат фонового задания — см. способ 3.

Хранилище не очищает себя мгновенно. Не кладите в него гигабайтные данные циклом без удаления — используйте УдалитьИзВременногоХранилища(Адрес), когда данные больше не нужны.

Возврат через данные формы и события записи

Шестой способ работает сам, без кода передачи. Процедура &НаСервере получает копию реквизитов формы. Всё, что она изменила в Объект и реквизитах формы, после возврата автоматически появится на экране.

&НаСервере
Процедура ЗаполнитьНаСервере()
    ДокументОбъект = РеквизитФормыВЗначение("Объект");
    ДокументОбъект.Заполнить(Неопределено);
    ЗначениеВРеквизитФормы(ДокументОбъект, "Объект");
КонецПроцедуры

Та же логика в событиях записи. Цепочка такая: ПередЗаписью (клиент) → ПередЗаписьюНаСервере → ПриЗаписиНаСервере → ПослеЗаписиНаСервере → ПослеЗаписи (клиент). Если после записи на клиенте нужно что-то сделать, например оповестить другие формы через Оповестить(), кладите этот код в ПослеЗаписи, а не пытайтесь вызвать клиент из серверного обработчика.

Передать флаг из серверного события в клиентское можно через параметр ПараметрыЗаписи: он есть и в ПослеЗаписиНаСервере, и в ПослеЗаписи.

Как вернуть результат с сервера в вашей задаче?

Частые ошибки при возврате с сервера на клиент

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

Скорость: меньше серверных вызовов — быстрее форма

Каждый переход клиент → сервер → клиент — это сетевой запрос. В локальной сети он почти незаметен. Через интернет, веб-клиент или медленный VPN десять лишних вызовов при открытии формы превращаются в секунды ожидания.

Что реально ускоряет:

  1. Объединяйте вызовы. Вместо трёх функций «получить цену», «получить остаток», «получить единицу» — одна функция, возвращающая структуру со всеми тремя полями.
  2. Выбирайте &НаСервереБезКонтекста. Если серверу не нужны данные формы, передавайте в параметрах только то, что нужно: ссылку, дату, количество.
  3. Не вызывайте сервер в ПриИзменении каждой строки, если можно пересчитать на клиенте. Цена × количество считается без сервера.
  4. Кешируйте неизменные данные. Общий модуль с флагом Вызов сервера и свойством Повторное использование возвращаемых значений = На время сеанса вернёт значение второй раз без обращения к серверу, например настройки учёта или константы.
  5. Передавайте на клиент только нужное. Не тащите на клиент таблицу на 10 000 строк, чтобы показать в ней одну сумму.

Как увидеть число серверных вызовов

Запустите тонкий клиент с параметром командной строки /DisplayPerformance. В окне появится индикатор производительности: число серверных вызовов последней операции, их длительность и объём переданных данных. Откройте проблемную форму, нажмите кнопку — и сразу видно, где три вызова превратились в двадцать.

Для детального разбора в конфигураторе включите замер производительности и повторите действие в отладке: замер покажет строки кода и время каждой.

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