Заменить ОткрытьМодально() на ОткрытьФорму()
Не устроил ответ?
Зарегистрируйся и задай свой вопрос. Живое общение приносит результат намного быстрее.
Реклама
Новые вакансии
- Вакансия «Прораммист/разработчи 1С» Екатеринбург от Денис Спеиалист
Вчера в 13:10 - Вакансия Стажер Аналитик от Elizavetashkr
17 ноября 2023 г. - Вакансия Стажер Программист 1С от Elizavetashkr
17 ноября 2023 г.
Невозможно открыть форму в модальном режиме так как она уже открыта
Есть внешняя печатная форма счета. Как сделать, чтобы перед печатью появлялась форма, в которой можно выбирать дополнительные реквизиты. По типу ТТН. Я так понимаю это в Процедуре КнопкаВыполнитьНажатие?
Можно пример?
Пример в ТТН — не то?
ФормаРедактирования = ПолучитьФорму(«РедактированиеВыражения»);
ФормаРедактирования.РеквизитВыражение = Элемент;
ФормаРедактирования.ВладелецФормы = ЭтаФорма;
ФормаРедактирования.ОткрытьМодально();
//ФормаРедактирования.Открыть();
В открываемой форме делай то нужно.
При зкрытии в реквизит (в примере РеквизитВыражение) открываемой формы возвращаешь массив или структуру (согласно выбранного типа этого реквизита) по результату работы «дочки» и дальше с ней работаешь.
Спасибо. Но для меня это сложно.
Мне нужно на форму добавить реквизит Булево.
При Истине печатается одно, при Ложь другое
» Как сделать, чтобы перед печатью появлялась форма, в которой можно выбирать дополнительные реквизиты»
Процедура печати должна быть в модуле формы обязательно. Или в модуле документа?
(6) вопрос задай.
то есть объекта
Как сделать, чтобы при нажатии на кнопку печать внешней печатной формы сначала появилась форма, где можно заполнить доп.реквизиты
А? Что? Я не понял 🙁
Внешняя обработка печатной формы. Модуль Объекта:
Функция Печать()
ПолучитьФорму(«ИмяФормыИзЭтойОбработки»).Открыть();
ФормаИзДокумента = СсылкаНаОбъект.ПолучитьФорму(«ИмяФормыИзДокумента»)
формаИзДокумента.Открыть();
Сообщить(«Ученье свет? а не ученых — тьма. Serg_1960 :о)»);
(12)*
Форма = ПолучитьФорму(«ИмяФормыИзЭтойОбработки»);
Форма.ОткрытьМодально();
(12) + Зайка, Вы не обидились? Ну извините меня, пожалуйста!
(13) У меня на «Модально» аллергия. А у Вас? В смысле, зачем?
нет, всем спасибо. просто инет бархлит
как мне теперь обратиться к реквизиту на форме?
(14) Если не отрыть ее модально, то процедура печати продолжит выполнение. А нужно дождаться закрытия формы.
Или условие ставить, но это больше кода.
(17)Пишет: Невозможно открыть форму в модальном режиме, так как она уже открыта! И выводит мне печатную форму до открытия формы
(16) = либо по (5), через реквизит.
Либо: ЭтаФорма.ВладелецФормы
(12) ПолучитьФорму(«ИмяФормыИзЭтойОбработки»).Открыть(); убрала открыть. в процедуру КнопкаВыполнитьНажатие(Кнопка) добавляю ТабДокумент = Печать();
Ругается на открытьМодально
Спасибо (20)
к реквизиту вроде кк получается обратиться. но как с открытием форм ыбыть. Что не так?
Меня видит кто-нибудь? 🙂 У Вас «..инет барахлит. » а у меня, так просто, — отлетает.
Если Не МояФорма.Открыта() Тогда
МояФорма.Открыть();
КонецЕсли;
Напоминаю: ПолучитьФорму() возвращает форму. Если форма открыта — возвращается она. И потому перед открытием рекомендуется проверять «не открыта ли форма уже?».
Ура она заговорила!
Слушаю Ваши вопросы дальше.
Получилось. Большое спасибо! Только окно свернутое, но уж это я сама наверное смогу поправить)
Serg_1960, вы мне очень помогли. А можно в аську писать? Если будут вопросы. Я не наглая?
как сделать, чтобы форма после нажатия на кнопку закрывалась. И при закрытии еще раз формирует печатную форму(
Если в модуле формы ЭтаФорма.Закрыть() то сворачивается программа
Не аську не дам :о) Принципиально в аське не сижу. Мне скандалов с женой и так хватает. До сих пор пристает: «Оторвись от компа, в инет ушел с головой, вынеси елку наконец-то. «
Не стесняйся :(здесь тоже можно флиртовать:). Не я, так добрые люди помогут. Ты только КРИЧИ ПОГРОМЧЕ. Пример настойчивости смотри тут:
v8: Проверка в запросе на наличие субконто
Да, ветку развернули)) Я не стесняюсь, понимаю, что знаний не хватает. Я же бух, а не программист. Просто легче самой один раз разобраться, а потом это применять во многих местах, чем сидеть и ждать, когда твой программист из фирмы из отпуска вернется и найдет на тебя время
Программисты тоже люди. У них тоже свои проблемы 🙂 А вопросы можно даже не задавать, — достаточно «породниться» с кнопочкой Поиск на сайте. Все вопросы уже до нас заданы были.
Есть внешняя печатная форма ТТН для УТ 10.3. Как раз сделал там заполнение реквизитов (которые 1С предоставила на заполнение руками каждый раз пользователям) и дальнейшее использование этих реквизитов при печати. Скиньте «мыло» если нужна.
(35) поиском я пользуюсь часто. на мою тему может не верно формулировала вопрос, не нашла подобного.
(36)ТТН есть и в БП как обработка, но я не смогла разобраться.
Задавай вопросы в новой теме и начинай со слов: «Я блондинка — бух! Хочу знать как. » От желающих общаться — отбоя не будет :о)
По поводу (34) — у всех свои недостатки: ты бух, а я программист 🙂 На сайте расовой дискриминации к бухам нет 🙂
я не блондинка, в любом случае разберусь. спасибо за помощь
Досвидание!
так как же мне форму закрыть
с получением самой формы внешней обработки печати вы вроде выше разобрались.
Если НЕ ФлагЗакрытия = Истина Тогда
Возврат;
КонецЕсли;
Если МояФорма.РеквизитФлаг = Истина Тогда
ТекМакет = ПолучитьМакет(«МакетИстина»);
Иначе
ТекМакет = ПолучитьМакет(«МакетЛожь»);
КонецЕсли;
\\далее код вывода данных в табличный документ.
В самой форме, у которой уже «нарисован» реквизит «РеквизитФлаг» оставляете только кнопки «ОК» и «Закрыть». На кнопку «Закрыть» вешается действие «Закрыть». На кнопку «ОК» вешается процедура с единственной командой «ЗакрытьФорму(Истина);».
(43) спасибо, сейчас попробую
(43) что значит макетложь? макет истина-это мой макет?
А что вообще в открываемой форме нужно установить и что потом «с этим установленным» надо делать после закрытия формы?
дополнительный реквизит. с этим я разобралась. например бывает плательщик филиал, а бывает ГП. отгрузка в любом случае на филиал. при выписке счета выбираю галочку. у мен явсе получилось по методу (12). форма закрывается. Единственное печатная форма формируется теперь 2 раза.
как убрать одно окно
Можешь выложить весь код из «Функция Печать() Экспорт»?
Функция Печать() Экспорт
// ПолучитьФорму(«ФормаРедактирования»).Открыть();
ПолучитьФорму(«ФормаРедактирования»);
// ФормаИзДокумента = СсылкаНаОбъект.ПолучитьФорму(«ФормаРедактирования»);
ФормаИзДокумента = ПолучитьФорму(«ФормаРедактирования»);
Если Не ФормаИзДокумента.Открыта() Тогда
формаИзДокумента.ОткрытьМодально();
КонецЕсли;
Запрос = Новый Запрос;
Запрос.УстановитьПараметр(«ТекущийДокумент», СсылкаНаОбъект.Ссылка);
Запрос.Текст ;
Шапка = Запрос.Выполнить().Выбрать();
Шапка.Следующий();
//Форма = ЭтотОбъект.ПолучитьФорму(«ПечФорма»);
ТабДокумент = Новый ТабличныйДокумент;
//ТабДокумент.ИмяПараметровПечати = «ПАРАМЕТРЫ_ПЕЧАТИ_ЗаказПокупателя_СчетЗаказ»;
Запрос = Новый Запрос;
Запрос.УстановитьПараметр(«ТекущийДокумент», СсылкаНаОбъект.Ссылка);
Запрос.Текст =
«ВЫБРАТЬ
| ВложенныйЗапрос.Номенклатура КАК Номенклатура,
| ВЫРАЗИТЬ(ВложенныйЗапрос.Номенклатура.НаименованиеПолное КАК СТРОКА(1000)) КАК Товар,
| ВложенныйЗапрос.Номенклатура.Код КАК Код,
| ВложенныйЗапрос.Количество КАК Количество,
| ВложенныйЗапрос.ЕдиницаИзмерения,
| ВложенныйЗапрос.Цена КАК Цена,
| ВложенныйЗапрос.Сумма КАК Сумма,
| ВложенныйЗапрос.СуммаНДС КАК СуммаНДС,
| ВложенныйЗапрос.НомерСтроки КАК НомерСтроки,
| 1 КАК ID,
| ВложенныйЗапрос.Цвет,
| ВложенныйЗапрос.Размер,
| ВложенныйЗапрос.ДопЭл,
| ВложенныйЗапрос.СтавкаНДС
|ИЗ
| (ВЫБРАТЬ
| ЗаказПокупателя.Номенклатура КАК Номенклатура,
| ЗаказПокупателя.Номенклатура.БазоваяЕдиницаИзмерения.Наименование КАК ЕдиницаИзмерения,
| ЗаказПокупателя.Цена КАК Цена,
| ЗаказПокупателя.Количество КАК Количество,
| ЗаказПокупателя.Сумма КАК Сумма,
| ЗаказПокупателя.СуммаНДС КАК СуммаНДС,
| ЗаказПокупателя.НомерСтроки КАК НомерСтроки,
| ЗаказПокупателя.Цвет КАК Цвет,
| ЗаказПокупателя.Размер КАК Размер,
| ЗаказПокупателя.ДопЭл КАК ДопЭл,
| ЗаказПокупателя.СтавкаНДС КАК СтавкаНДС
| ИЗ
| Документ.СчетНаОплатуПокупателю.Товары КАК ЗаказПокупателя
| ГДЕ
| ЗаказПокупателя.Ссылка = &ТекущийДокумент) КАК ВложенныйЗапрос
|
| УПОРЯДОЧИТЬ ПО
| Номенклатура,
| Цвет,
| Размер,
| ДопЭл
| Автоупорядочивание
| » ;
//|ОБЪЕДИНИТЬ ВСЕ
//|
//|ВЫБРАТЬ
//| » + ЧастьЗапросаДляВыбораСодержанияУслуг + «,
//| » + ЧастьЗапросаДляВыбораСодержанияУслуг + «,
//| NULL,
//| ЗаказПокупателя.Количество,
//| ЗаказПокупателя.Номенклатура.БазоваяЕдиницаИзмерения.Наименование,
//| ЗаказПокупателя.Цена,
//| ЗаказПокупателя.Сумма,
//| ЗаказПокупателя.СуммаНДС,
//| ЗаказПокупателя.СтавкаНДС,
//| ЗаказПокупателя.НомерСтроки,
//| 2
//|ИЗ
//| Документ.СчетНаОплатуПокупателю.Услуги КАК ЗаказПокупателя
//|
//|ГДЕ
//| ЗаказПокупателя.Ссылка = &ТекущийДокумент
//|
//|УПОРЯДОЧИТЬ ПО
//| ID,
//| НомерСтроки»;
ЗапросТовары = Запрос.Выполнить().Выбрать();
Макет = ПолучитьМакет(«СчетЗаказ»);
// Выводим сведения об организации
СведенияОПоставщике = УправлениеКонтактнойИнформацией.СведенияОЮрФизЛице(Шапка.Поставщик, Шапка.Дата);
ОбластьМакета = Макет.ПолучитьОбласть(«Поставщик»);
ОбластьМакета.Параметры.Организация = СведенияОПоставщике.ПолноеНаименование;
ОбластьМакета.Параметры.Адрес = СведенияОПоставщике.ЮридическийАдрес;
//ОбластьМакета.Параметры.Телефон = СведенияОПоставщике.Телефоны;
ТабДокумент.Вывести(ОбластьМакета);
ОбластьМакета = Макет.ПолучитьОбласть(«ЗаголовокСчета»);
ОбластьМакета.Параметры.Заполнить(Шапка);
ОбластьМакета.Параметры.ИНН = СведенияОПоставщике.ИНН;
ОбластьМакета.Параметры.Банк = СведенияОПоставщике.Банк;
ОбластьМакета.Параметры.Получатель = СведенияОПоставщике.ПолноеНаименование;
ОбластьМакета.Параметры.КПП = СведенияОПоставщике.КПП;
ОбластьМакета.Параметры.БИК = СведенияОПоставщике.БИК;
ОбластьМакета.Параметры.СчетБанка = СведенияОПоставщике.КоррСчет;
ОбластьМакета.Параметры.СчетПолучателя = СведенияОПоставщике.НомерСчета;
ТабДокумент.Вывести(ОбластьМакета);
ОбластьМакета = Макет.ПолучитьОбласть(«Заголовок»);
ОбластьМакета.Параметры.ТекстЗаголовка = РаботаСДиалогами.СформироватьЗаголовокДокумента(Шапка, «СЧЕТ»);
ТабДокумент.Вывести(ОбластьМакета);
СведенияОПолучателе = УправлениеКонтактнойИнформацией.СведенияОЮрФизЛице(Шапка.Получатель, Шапка.Дата);
ОбластьМакета = Макет.ПолучитьОбласть(«Покупатель»);
ОбластьМакета.Параметры.Заполнить(Шапка);
//ОбластьМакета.Параметры.ПредставлениеПолучателя = ФормированиеПечатныхФорм.ОписаниеОрганизации(УправлениеКонтактнойИнформацией.СведенияОЮрФизЛице(Шапка.Получатель, Шапка.Дата), «ИНН,КПП,ПолноеНаименование,ЮридическийАдрес,Телефоны,»);
Отбор = Новый Структура;
Отбор.Вставить(«Объект»,Шапка.Получатель);
Отбор.Вставить(«Тип», Перечисления.ТипыКонтактнойИнформации.Другое);
Отбор.Вставить(«Вид»,Справочники.ВидыКонтактнойИнформации.НайтиПоКоду(«00033»).Ссылка);
///
//Если СокрЛП(Шапка.АдресДоставки) <> «» Тогда
// ПредставлениеПолучателяДоАдреса = ФормированиеПечатныхФорм.ОписаниеОрганизации(СведенияОПолучателе, «ПолноеНаименование,ИНН,»);
// ПредставлениеПолучателяПослеАдреса = ФормированиеПечатныхФорм.ОписаниеОрганизации(СведенияОПолучателе, «Телефоны,НомерСчета,Банк,БИК,КоррСчет,»);
// ОбластьМакета.Параметры.ПредставлениеПолучателя = ?(СокрЛП(ПредставлениепОЛУЧАТЕЛЯДоАдреса) = «», «», ПредставлениеПолучателяДоАдреса+», «) + ?(СокрЛП(ПредставлениеПолучателяПослеАдреса)=»», «», «, «+ПредставлениепОЛУЧАТЕЛЯПослеАдреса);
//Иначе
Если не РегистрыСведений.КонтактнаяИнформация.Получить(Отбор).Представление = «» Тогда
Отбор.Очистить();
Отбор.Вставить(«Объект»,Шапка.Получатель);
Отбор.Вставить(«Тип», Перечисления.ТипыКонтактнойИнформации.Адрес);
Отбор.Вставить(«Вид»,Справочники.ВидыКонтактнойИнформации.НайтиПоКоду(«00008»).Ссылка);
СведенияОПолучателе1 = РегистрыСведений.КонтактнаяИнформация.Получить(Отбор).Представление;
СведенияОПол=Лев(СведенияОполучателе1,Найти(СведенияОполучателе1,»,»)-1);
ОбластьМакета.Параметры.ПредставлениеПолучателя=СокрЛП(СведенияОПол)+», «+ФормированиеПечатныхФорм.ОписаниеОрганизации(СведенияОПолучателе,»Телефоны»);
// ОбластьМакета.Параметры.ПредставлениеПолучателя = ФормированиеПечатныхФорм.ОписаниеОрганизации(УправлениеКонтактнойИнформацией.СведенияОЮрФизЛице(Шапка.Получатель, Шапка.Дата), «ПолноеНаименование,Телефоны»);
ТабДокумент.Вывести(ОбластьМакета);
ОбластьШапкаТаблицы = Макет.ПолучитьОбласть(«ШапкаТаблицы»);
ТабДокумент.Вывести(ОбластьШапкаТаблицы);
ОбластьКолонкаТовар = Макет.Область(«Товар»);
ОбластьСтроки = Макет.ПолучитьОбласть(«Строка»);
ИтогоПоХарактеристикам = Макет.ПолучитьОбласть(«ИтогоПоХарактеристикам»);
Сумма = 0;
СуммаНДС = 0;
ВсегоСкидок = 0;
ВсегоБезСкидок = 0;
// ТабДокумент.НачатьАвтогруппировкуСтрок();
счетчик = 0;
СтароеНаименование = «»;
КоличествоСтарого = 0;
Количество = 0;
Наименование = «»;
СтавкаНДС = «»;
ЕдиницаИзмерения = «»;
Сумма = 0;
СуммаИтог = 0;
СуммаНДС = 0;
Пока ЗапросТовары.Следующий() Цикл
Если (СтароеНаименование <> СокрЛП(ЗапросТовары.Товар)) Тогда
Если (КоличествоСтарого > 1) Тогда
ИтогоПоХарактеристикам.Параметры.Товар = Наименование;
ИтогоПоХарактеристикам.Параметры.Количество = Количество;
ИтогоПоХарактеристикам.Параметры.СтавкаНДС = СтавкаНДС;
ИтогоПоХарактеристикам.Параметры.ЕдиницаИзмерения = ЕдиницаИзмерения;
ИтогоПоХарактеристикам.Параметры.Сумма = Сумма;
Сумма = 0;
Количество = 0;
КоличествоСтарого = 0;
ТабДокумент.Вывести(ИтогоПоХарактеристикам);
Иначе
КоличествоСтарого = 0;
Сумма = 0;
Количество = 0;
КонецЕсли;
КонецЕсли;
Отбор = Новый Структура;
Отбор.Вставить(«Контрагент»,СсылкаНаОбъект.Контрагент);
Отбор.Вставить(«Договор»,СсылкаНаОбъект.ДоговорКонтрагента);
Отбор.Вставить(«Номенклатура»,ЗапросТовары.Номенклатура);
Отбор.Вставить(«Размер»,ЗапросТовары.Размер);
Отбор.Вставить(«Цвет»,ЗапросТовары.Цвет);
Отбор.Вставить(«ДопЭл»,ЗапросТовары.ДопЭл);
Если РегистрыСведений.НаименованиеДляКонтрагента.Получить(Отбор).Количество() > 0 Тогда
Если РегистрыСведений.НаименованиеДляКонтрагента.Получить(Отбор).Наименование <> «» Тогда
Наименование = РегистрыСведений.НаименованиеДляКонтрагента.Получить(Отбор).Наименование;
Иначе
Наименование = СокрЛП(ЗапросТовары.Товар)+» «+СокрЛП(ЗапросТовары.Цвет)+» «+СокрЛП(ЗапросТовары.ДопЭл);
КонецЕсли;
КонецЕсли;
ОбластьСтроки.Параметры.НомерСтроки = счетчик + 1;
ОбластьСтроки.Параметры.Товар = Наименование;
ОбластьСтроки.Параметры.Код = ЗапросТовары.Код;
ОбластьСтроки.Параметры.Количество = ЗапросТовары.Количество;
ОбластьСтроки.Параметры.СтавкаНДС = ЗапросТовары.СтавкаНДС;
ОбластьСтроки.Параметры.ЕдиницаИзмерения = ЗапросТовары.ЕдиницаИзмерения;
ОбластьСтроки.Параметры.ЦенабезНДС =(ЗапросТовары.Сумма-ЗапросТовары.СуммаНДС)/?(ЗапросТОвары.Количество = 0, 1, ЗапросТовары.Количество);
ОбластьСтроки.Параметры.Сумма = ЗапросТовары.Сумма;
ОбластьСтроки.Параметры.Цена = ЗапросТовары.Цена;
ОбластьСтроки.Параметры.Размер = ЗапросТовары.Размер;
// ОбластьСтроки.Параметры.Цвет = ЗапросТовары.Цвет;
// ОбластьСтроки.Параметры.ДопЭл = ЗапросТовары.ДопЭл;
Наименование = СокрЛП(ЗапросТовары.Товар);
Количество = Количество + ЗапросТовары.Количество;
СтавкаНДС = ЗапросТовары.СтавкаНДС;
Сумма = Сумма + ОбластьСтроки.Параметры.Сумма;
СуммаИтог = СуммаИтог + ОбластьСтроки.Параметры.Сумма;
ЕдиницаИзмерения = ЗапросТовары.ЕдиницаИзмерения;
СуммаНДС = СуммаНДС + ЗапросТовары.СуммаНДС;
КоличествоСтарого = КоличествоСтарого + 1;
СтароеНаименование = Наименование;
ТабДокумент.Вывести(ОбластьСтроки);
счетчик = счетчик + 1;
КонецЦикла;
Если (КоличествоСтарого > 1) Тогда
ИтогоПоХарактеристикам.Параметры.Товар = Наименование;
ИтогоПоХарактеристикам.Параметры.Количество = Количество;
ИтогоПоХарактеристикам.Параметры.СтавкаНДС = СтавкаНДС;
ИтогоПоХарактеристикам.Параметры.ЕдиницаИзмерения = ЕдиницаИзмерения;
ИтогоПоХарактеристикам.Параметры.Сумма = Сумма;
Сумма = 0;
Количество = 0;
ТабДокумент.Вывести(ИтогоПоХарактеристикам);
КонецЕсли;
// Вывести Итого
ОбластьИтого = Макет.ПолучитьОбласть(«Итого»);
ОбластьИтого.Параметры.Всего =СуммаИтог;
ТабДокумент.Вывести(ОбластьИтого);
// Вывести ИтогоНДС
ОбластьИтогоНДС = Макет.ПолучитьОбласть(«ИтогоНДС»);
Если СуммаНДС <> 0 Тогда
ОбластьИтогоНДС.Параметры.НДС = ?(Шапка.СуммаВключаетНДС, «В том числе НДС:», «Сумма НДС:»);
ОбластьИтогоНДС.Параметры.ВсегоНДС = СуммаНДС;
Иначе
ОбластьИтогоНДС.Параметры.НДС = «Без налога (НДС)»;
ОбластьИтогоНДС.Параметры.ВсегоНДС = «-«;
КонецЕсли;
ТабДокумент.Вывести(ОбластьИтогоНДС);
// Вывести Сумму прописью
ОбластьМакета = Макет.ПолучитьОбласть(«СуммаПрописью»);
СуммаКПрописи = СуммаИтог;// + ?(Шапка.СуммаВключаетНДС, 0, СуммаНДС);
ОбластьМакета.Параметры.ИтоговаяСтрока =»Всего наименований » + счетчик
+ «, на сумму » + ФормированиеПечатныхФорм.ФорматСумм(СуммаКПрописи, Шапка.ВалютаДокумента);
ОбластьМакета.Параметры.СуммаПрописью = РаботаСДиалогами.СформироватьСуммуПрописью(СуммаКПрописи, Шапка.ВалютаДокумента);
ТабДокумент.Вывести(ОбластьМакета);
// Вывести подписи
//Если Тип = «Счет» Тогда
ОбластьМакета = Макет.ПолучитьОбласть(«ПодвалСчета»);
ОбластьМакета.Параметры.Заполнить(Шапка);
//ОбластьМакета.Параметры.ПредставлениеПолучателя = ФормированиеПечатныхФорм.ОписаниеОрганизации(УправлениеКонтактнойИнформацией.СведенияОЮрФизЛице(Шапка.Получатель, Шапка.Дата), «ИНН,КПП,ПолноеНаименование,ЮридическийАдрес,Телефоны,»);
Отбор = Новый Структура;
Отбор.Вставить(«Объект»,Шапка.Получатель);
Отбор.Вставить(«Тип», Перечисления.ТипыКонтактнойИнформации.Другое);
Отбор.Вставить(«Вид»,Справочники.ВидыКонтактнойИнформации.НайтиПоКоду(«00033»).Ссылка);
///
//Если СокрЛП(Шапка.АдресДоставки) <> «» Тогда
// ПредставлениеПолучателяДоАдреса = ФормированиеПечатныхФорм.ОписаниеОрганизации(СведенияОПолучателе, «ПолноеНаименование,ИНН,»);
// ПредставлениеПолучателяПослеАдреса = ФормированиеПечатныхФорм.ОписаниеОрганизации(СведенияОПолучателе, «Телефоны,НомерСчета,Банк,БИК,КоррСчет,»);
// ОбластьМакета.Параметры.ПредставлениеПолучателя = ?(СокрЛП(ПредставлениепОЛУЧАТЕЛЯДоАдреса) = «», «», ПредставлениеПолучателяДоАдреса+», «) + ?(СокрЛП(ПредставлениеПолучателяПослеАдреса)=»», «», «, «+ПредставлениепОЛУЧАТЕЛЯПослеАдреса);
//Иначе
Если не РегистрыСведений.КонтактнаяИнформация.Получить(Отбор).Представление = «» Тогда
Отбор.Очистить();
Отбор.Вставить(«Объект»,Шапка.Получатель);
Отбор.Вставить(«Тип», Перечисления.ТипыКонтактнойИнформации.Адрес);
Отбор.Вставить(«Вид»,Справочники.ВидыКонтактнойИнформации.НайтиПоКоду(«00008»).Ссылка);
СведенияОПолучателе3 = РегистрыСведений.КонтактнаяИнформация.Получить(Отбор).Представление;
// СведенияОПол1=Лев(СведенияОполучателе1,Найти(СведенияОполучателе3,»,»)-1);
ПредставлениеГрузополучателя=СокрЛП(СведенияОПолучателе3);
ПредставлениеПолучателя2 = ФормированиеПечатныхФорм.ОписаниеОрганизации(СведенияОПолучателе,»ПолноеНаименование»);
Адрес=ФормированиеПечатныхФорм.ОписаниеОрганизации(СведенияОПолучателе,»ЮридическийАдрес»);
ИНН=ФормированиеПечатныхФорм.ОписаниеОрганизации(СведенияОПолучателе,»ИНН»)+»/»+ФормированиеПечатныхФорм.ОписаниеОрганизации(СведенияОПолучателе,»КПП»);
ОбластьМакета.Параметры.Адрес =Адрес;
ОбластьМакета.Параметры.ПредставлениеГрузополучателя =ПредставлениеГрузополучателя;
ОбластьМакета.Параметры.ИНН =ИНН;
ОбластьМакета.Параметры.ПредставлениеПолучателя2 =ПредставлениеПолучателя2;
Руководители = РегламентированнаяОтчетность.ОтветственныеЛицаОрганизаций(Шапка.Руководители, Шапка.Дата,);
Руководитель = Руководители.Руководитель;
Бухгалтер = Руководители.ГлавныйБухгалтер;
ОбластьМакета.Параметры.Ответственный = Шапка.Ответственный;
//Дожность=Ответственный.Должность;
//ОбластьМакета.Параметры.Должность=Шапка.Должность;
ОбластьМакета.Параметры.Плательщик=Плательщик;
//ОбластьМакета.Параметры.ФИОРуководителя = Руководитель;
//ОбластьМакета.Параметры.ФИОБухгалтера = Бухгалтер;
// сли Тип = «Счет» Тогда
// ОбластьМакета = Макет.ПолучитьОбласть(«Ответственный»);
// Ответственный = ПараметрыСеанса.ТекущийПользователь;
//ОбластьМакета.Параметры.СОтветственный = Ответственный;
//Иначе
// ОбластьМакета = Макет.ПолучитьОбласть(«ПодвалЗаказа»);
//КонецЕсли;
//ОбластьМакета.Параметры.Заполнить(Шапка);
//ТабДокумент.Вывести(ОбластьМакета);
Если ФормаИзДокумента.Открыта() Тогда
формаИзДокумента.Закрыть();
КонецЕсли;
Возврат ТабДокумент;
Как в 1С вывести сообщение пользователю (бесплатная статья по Программированию в 1С)
из цикла статей «Первые шаги в разработке на 1С»
Статья продолжает цикл статей «Первые шаги в разработке на 1С».
В ней мы рассмотрим способы информирования пользователя, которые присутствуют в платформе «1С:Предприятие» 8, а также акцентируем ваше внимание на некоторых особенностях работы этих механизмов, эти особенности связаны с режимом использования модальности.
Применимость
В статье рассматривается функциональность:
- Интерфейса в варианте «Версии 8.2» для конфигурации, разработанной на платформе «1С:Предприятие» 8.2.19.130
- Интерфейса «Такси» для конфигурации, разработанной на платформе «1С:Предприятие» 8.3.4.496 до 8.3.9+
- Интерфейса «Такси» для конфигурации, разработанной на платформе «1С:Предприятие» 8.3.10-8.3.11
Как в 1С вывести сообщение пользователю
- Ознакомительные сообщения
- Механизм оповещений
- Терминирующие сообщения
- Особенности использования модальных окон в Платформе 8.3
- Класс СообщениеПользователю
- Уведомление о состоянии процесса
Вывод сообщений в пользовательском режиме решает ряд задач:
- отражение хода выполнения текущего процесса (показ стадии выполнения процесса; показ расчетных значений, полученных в ходе работы алгоритма);
- выдача ошибок пользователю для возможного их исправления;
- выдача рекомендаций;
- терминирующие, которые останавливают выполнение программы и не дают продолжить ее, пока пользователь не ознакомится с этим сообщением и не выполнит определенные действия. Например, на экран пользователю будет выдан вопрос, на который нужно будет ответить Да или Нет. Пока пользователь не ответит – программа не выполняет дальнейшие действия;
- ознакомительные сообщения, которые просто выводятся для пользователя и позволяют работать дальше (т.е. используются в режиме оповещения).
Терминирующими сообщениями должны быть сообщения об ошибках, а ознакомительными: рекомендации, сообщения о текущем этапе процесса и показ расчетных значений (отладочная печать).
Ознакомительные сообщения
Ознакомительные сообщения предназначены для того, чтобы выдать пользователю некоторую информацию.
Необходимо, чтобы пользователь с ней обязательно ознакомился и, возможно, предпринял какие-то действия, которые описаны в этом сообщении.
Очень важно, чтобы пользователь действительно читал эти сообщения, поэтому они должны содержать только важную информацию.
Тестовые и отладочные сообщения выдавать пользователю не стоит, т.к. рано или поздно он начнет игнорировать абсолютно все сообщения.
В концепции управляемого интерфейса несколько изменился подход к выдаче сообщения. Оно теперь привязано к форме, в которой возникло. Его уже нельзя закрыть так, чтобы текст было совсем невидно.
Открепить от формы окно с сообщением нельзя.
Т.е. первым параметром является сам текст.
Второй параметр (статус сообщения) является необязательным. Для статуса можно указывать значения: Обычное, Важное, ОченьВажное и т.д.
От данного значения зависит, какой значок будет расположен рядом с сообщением. Однако это работает только в обычном интерфейсе.
В концепции управляемого интерфейса значок всегда в виде восклицательного знака, переопределить его нельзя.

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

Тем не менее, функция Сообщить может использоваться для вывода информации о некоторых ошибках, например в момент проведения документа.
В этом случае системе можно сообщить, что форму закрывать не нужно, и показать пользователю, какие ошибки возникают при проведении документа.
Функция Сообщить полностью поддерживается в Платформе 8.3. Ее можно использовать, и она будет работать (и в файловом варианте, и в клиент-серверном).
Но также следует отметить, что у функции Сообщить есть дальнейшее развитие – это класс сообщения пользователю, который позволяет помимо того, что выводить сообщение, привязывать его контекстно к каким-либо элементам формы.
Например, сообщение об ошибке можно привязать к элементу формы, что для пользователя очень наглядно. Несколько позже к рассмотрению этого вопроса мы вернемся. У функции Сообщить есть интересная особенность.
Так, программный код в Платформе 8.3 может быть исполнен как на стороне Клиента, так и на стороне Сервера.
При этом клиентский программный код отвечает за взаимодействие с пользователем, т.е. на стороне клиента открываются формы, выводятся отчеты.
Различные диалоговые документы также отображаются только на клиенте. На сервере они не могут быть исполнены, поскольку сервер не имеет возможности взаимодействия с пользователями.
Но функция Сообщить может быть исполнена как на стороне Клиента, так и на стороне Сервера. При этом использование метода Сообщить на Сервере вовсе не означает, что сообщение будет выводиться именно на Сервере, там их просто некуда выводить.
Это означает, что если мы в серверной процедуре будем выводить сообщение с помощью этого метода, они будут накапливаться в некотором буфере и выведутся они на экран только тогда, когда серверная процедура закончится и произойдет возврат на Клиента.
В этот момент система запросит данные из буфера и выведет их на экран.
Эта же особенность касается и класса СообщениеПользователю. На рисунке приведен пример использования метода Сообщить на стороне Сервера.

В результате использования метода Сообщить на стороне Сервера вывелись сообщения на экран на стороне Клиента.

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

Кроме того, к оповещениям можно обратиться в соответствующей области информационной панели (кнопка “История” слева внизу формы приложения в варианте интерфейса «Версии 8.2»).
Чтобы создавать свои собственные оповещения, необходимо использовать метод глобального контекста ПоказатьОповещениеПользователя(). Его синтаксис до редакции 8.3.10 представлен ниже:
В первом параметре передается текст, который будет выводиться в оповещении.
Далее вторым параметром можно передать некую навигационную ссылку на какой-либо элемент информационной базы (тот элемент, который соответствует тексту нашего сообщения). При нажатии пользователем на оповещение будет выполнен переход по этой ссылке.
С помощью третьего параметра можно передать пояснение для сообщения, т.е. какое-то расширенное описание.
Также можно присвоить картинку, отображающую статус оповещения.
Следует отметить, что все эти параметры являются необязательными для заполнения. Ниже приведен пример использования данного метода (в конфигураторе и в пользовательском режиме в варианте интерфейса «Версии 8.2»).

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

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

После выполнения команды получим приблизительно такой вид окна приложения:

В панели инструментов появилась кнопка с пиктограммой звонка, по которой вызывается упомянутый выше Центр оповещений. В нем накапливаются новые важные оповещения, на которые пользователь пока никак не отреагировал.
Если в Центре есть какие-то оповещения, то рядом с ним появляется маленькая оранжевая точка, чтобы привлечь внимание пользователя. Пользователь может открыть Центр оповещений, прочитать текст и, если необходимо, выполнить какие-то действия.

Из Центра оповещение убирается нажатием на кнопку очистки, однако если с оповещением связано какое-то действие, то, как только пользователь щелкнет мышью по тексту сообщения, оно тоже пропадет.
И наконец, последним добавленным параметром стал КлючУникальности. С его помощью можно найти отображенное на экране оповещение и изменить его. Если же оповещения с таким параметром нет, то будет показано новое оповещение.
Как видим, возможностей, предоставляемых соответствующим методом, стало еще больше! Но это не все изменения в механизме оповещений.
Как вы уже, наверное, успели заметить, изменился их внешний вид. Теперь оповещения выглядят более современно и эргономично, но их нельзя перемещать по экрану и изменять их размер. Обратите внимание, в нашем примере, текст оповещения попросту не поместился целиком в самом окне, и прочитать его полностью пользователь сможет, только открыв Центр Оповещений. Поэтому не стоит в текст оповещения писать большое количество текста.
Также к новым возможностям относится и одновременное отображение на экране до трех оповещений.
На этом завершим наше знакомство с программным формированием оповещений. Однако вспомним, что оповещения формируются не только разработчиком программно, но и самой платформой в момент интерактивной записи или изменения объекта. И часто этот факт вызывает непонимание в первую очередь у начинающих пользователей: зачем нужны эти служебные оповещения, которые, кстати, нельзя отключить?
Давайте представим такую простую ситуацию: пользователь установил фильтр в каком-то списке для удобства. Допустим, он сделал это в форме списка справочника Номенклатуры. Потом, через какое-то время, решил ввести новый элемент с наименованием “Стул”, который не соответствует установленному ранее фильтру. Вводит его, записывает и…? И не видит его в списке. Что будет делать среднестатистический пользователь? Конечно, введет его второй раз, но опять не увидит. Дальше может последовать третий, четвертый, пятый раз. Когда ему надоест вводить одно и тоже, он, наконец, спросит у вас: а куда все пропадает?
Вот именно поэтому платформа и отображает эти служебные оповещения, информируя пользователя о том, что его действие выполнено. В нашем примере в момент интерактивной записи пользователь увидит следующее оповещение:

Терминирующие сообщения
Терминирующие сообщения – это те сообщения, которые не позволят работать, пока пользователь не произведет определенные действия, т.е. пока он не обработает сообщение.
О возможности использования терминирующих сообщений в Платформе 8.3 мы поговорим немного позже (в последнее время их стараются не использовать, поэтому рассмотренный пример больше касается Платформы 8.2).
Существуют два метода для выдачи терминирующих сообщений Предупреждение и Вопрос. Предупреждение отличается от Вопроса тем, что у него есть единственная кнопка ОК.
В вопросе могут определяться разные наборы вариантов ответов (ДаНет, ДаНетОтмена, ОК, ОКОтмена, ПовторитьОтмена, ПрерватьПовторитьПропустить), которые задаются с помощью параметра.
Выведем какое-нибудь предупреждение с помощью строки (например, в модуле управляемого приложения):
Предупреждение(“Сейчас будет открыта база”);
Чтобы открыть модуль управляемого приложения, следует в дереве конфигурации выбрать объект Конфигурация, вызвать контекстное меню и выбрать пункт Открыть модуль управляемого приложения.

В данном случае, при запуске приложения, будет выводиться окно, которое является модальным. Модальное окно перекрывает собой все окна, которые существуют в приложении. Пока мы не обработаем это окно, дальнейшие действия невозможны.

Аналогичным образом работает и функция Вопрос.
Обязательными являются только первые два параметра. Для второго параметра тип данных составной (РежимДиалогаВопрос или СписокЗначений). Третий параметр ( ) характеризует интервал времени в секундах, в течение которого система будет ожидать ответа пользователя.
По истечении интервала окно вопроса будет закрыто. Аналогичный параметр( ) есть и у функции Предупреждение.
В качестве примера использования функции Вопрос можно использовать следующий код, записанный в модуле управляемого приложения:


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

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


Класс СообщениеПользователю
Основное удобство класса сообщений СообщениеПользователю заключается в том, что это контекстное сообщение (в отличии от методов Предупреждение и Вопрос).
Сообщения могут быть привязаны к конкретному экранному элементу. Этот объект доступен и на Сервере.
Следует обратить внимание, что, во-первых, данный объект нужно создавать. Например: Сообщение = Новый СообщениеПользователю;
Таким образом мы создаем экземпляр данного объекта.
Во-вторых, нужно прописывать текст сообщения в отдельном свойстве.
В-третьих, в свойстве Поле можно указать, к какому элементу формы данное сообщение должно быть привязано.
Внимание! Для привязки к нужному полю формы обратите внимание на инициализацию свойств ПутьКДанным и КлючДанных. Применительно для документа при размещении кода в модуле объекта можно писать:
Сообщение.ПутьКДанным = “Объект”;
Сообщение.КлючДанных = ЭтотОбъект.Ссылка;
Чтобы открыть модуль документа, следует в окне редактирования объекта (документа) на закладке Прочее нажать на кнопку Модуль объекта.
Для эксперимента в модуле объекта какого-либо документа разместим код.

Ниже представлен полученный в пользовательском режиме результат для Платформы 8.3.

Следует отметить, что сообщения, выводимые с помощью нового объекта системы СообщениеПользователю в общем случае не являются терминирующими. Т.е. система позволит пользователю продолжить дальнейшие действия не отреагировав на выводимые сообщения.
Но, во-первых, данные сообщения достаточно заметны. Во-вторых, обычно сообщения пользователю выводятся в момент записи элементов справочников или проведения документов, т.е., когда выполняются какие-то проверки. И если были обнаружены ошибки, то пользователь увидит эти самые сообщения.
Соответственно, в момент обнаружения ошибок отменяется транзакция, т.е. запрещается запись элемента справочника, либо запрещается проведение документа.
Таким образом, происходит своего рода эмуляция терминирующего сообщения. Потому что действие отменяется, пока пользователь не отреагирует на вводимое сообщение, завершить действие, например, провести документ, будет нельзя.
Но, с другой стороны, есть возможность закрыть документ без проведения, никак не прореагировав на сообщение. Поэтому данные сообщения пользователю не являются терминирующими.
Уведомление о состоянии процесса
Существует специальная функция, с помощью которой можно отображать примерный ход выполнения какого-либо процесса.
Синтаксис: Состояние(, , , )
Параметры: и – не обязательные, тип – Строка.
Текст выводится на специальную панель состояния.
параметр тоже необязательный, но наглядный.
Тип: Число. Значение индикатора прогресса (от 1 до 100).
тоже необязательный параметр.
При обработке какого-либо события могут использоваться периодические вызовы функции типа:

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

Если вы хотите ознакомиться с механизмом уведомления более подробно, то прямо сейчас прервитесь и прочтите нашу новую статью Отображение прогресса длительных операций в 8.3.10. В ней уже не на уровне новичка объясняются все тонкости и подводные камни работы этого механизма.
Мы же завершаем знакомство со способами информирования пользователя. Надеемся, что у вас сложилось понимание, в каких ситуациях следует применять тот или иной способ.
Хочется еще раз акцентировать ваше внимание на том факте, что если ваша конфигурация (версии 8.3.3+) предполагает работу с помощью веб-клиента, то:
- на уровне конфигурации должна быть установлена настройка режима модальности «Не использовать»
- в коде должны использоваться методы асинхронной модели взаимодействия с пользователем. Такие методы начинаются со слов Показать или Начать.
Более подробно об отказе от использования модальных окон в платформе 1С:Предприятие 8.3 можно почитать в финальной статье цикла. А мы идем дальше и, наконец, приступаем к изучению долгожданного интерфейса «Такси», который уже не раз упоминался в наших материалах.

PDF-версия статьи для участников группы ВКонтакте
Если Вы еще не вступили в группу – сделайте это сейчас и в блоке ниже (на этой странице) появятся ссылка на скачивание материалов.
Статья в PDF-формате
Вы можете скачать эту статью в формате PDF по следующей ссылке:
Ссылка доступна для зарегистрированных пользователей)
Ссылка доступна для зарегистрированных пользователей)
Ссылка доступна для зарегистрированных пользователей)
Ссылка доступна для зарегистрированных пользователей)
Если Вы уже участник группы – нужно просто повторно авторизоваться в ВКонтакте, чтобы скрипт Вас узнал. В случае проблем решение стандартное: очистить кеш браузера или подписаться через другой браузер.
Комментарии / обсуждение (16):
Немного не в тему, но про сообщения. Есть ли возможность отправить сообщения активным пользователям в УПП 1.3 на платформе 8.2? Может какие обработки есть?
«Асинхронные методы в 1С», статья Василия Ханевича
На последней конференции Инфостарт в ноябре 2021 выступал с докладом по асинхронным методам наш автор Василий Ханевич (курсы по запросам, СКД, администрированию, расширениям и т.д.).
Считаем полезным поделиться детальным разбором их работы. Сядьте поудобнее, это реальный лонгрид на 53 страницы текста А4 🙂
Что здесь важно – у 1С давно ничего не менялось в языке 1С, но сейчас появился новый тип данных (“Обещания”), новые асинхронные функции – и это само по себе интересно.
В данной статье рассмотрим отличия синхронных вызовов от асинхронных, разберем, почему потребовалось отказываться от синхронных методов, а также изучим новшества технологии работы с асинхронными вызовами, которые реализованы в платформе 8.3.18.
Навигация
- Введение. Зачем вообще потребовались асинхронные методы в платформе “1С:Предприятие 8”?
- Отказ от модальности
- Асинхронные вызовы расширений и внешних компонент
- Принцип работы методов обратного вызова
- Пример 1 – Организация диалога с пользователем
- Пример 2 – Сохранение файла на клиентском компьютере
- Вложенные процедуры с асинхронными методами
- Сложности при использовании методов обратного вызова
- Создать обработку оповещения
- Преобразовать вызов
- Найти вызовы модуля
- Анализ нерекомендуемых синхронных вызовов конфигурации
- Новый тип – Обещание
- Асинхронные функции
- Работа с асинхронными функциями
- Модификатор Асинх
- Обработка исключений в асинхронных процедурах и функциях
- Примеры использования асинхронных функций
- Организация диалога с пользователем
- Копирование файла
- Проверка возможности работы с внешней системой
- Вопрос пользователю во время закрытия формы
- Ошибка 1 – Только клиентский контекст
- Ошибка 2 – Оператор Ждать только в асинхронных методах
- Ошибка 3 – Подходящие версии платформы

Введение. Зачем вообще потребовались асинхронные методы в платформе “1С:Предприятие 8”?
Конфигурации на платформе “1С:Предприятие 8” могут работать на различных клиентских устройствах, например, через тонкий клиент на компьютерах под управлением ОС Windows, Linux, MacOS или через веб-клиент через Internet Explorer, Edge, Mozilla Firefox, Google Chrome или Safari. Каждый из этих вариантов имеет свои особенности – значит со стороны платформы должна быть реализована поддержка большого количества вариантов работы.
Стоит выделить поддержку работы через веб-клиент – удобный для пользователей режим, поскольку им не нужно устанавливать дополнительные программы.
Через браузер можно работать с почтой, облачными хранилищами, банковскими системами и огромным количеством других приложений. Поскольку важно обеспечить безопасность пользователя, надежность и высокую скорость, разработчики браузеров предъявляют жесткие требования к веб-сайтам и технологиям, к тому же эти требования меняются с течением времени.
Фирма “1С” уделяет большое внимание поддержке современных версий браузеров, в платформу вносятся изменения, которые диктуются разработчиками браузеров.
Рассмотрим несколько примеров.
Отказ от модальности
В качестве примера можно привести отказ от модальности, которому посвящена отдельная статья на нашем сайте. Такой вариант работы интерфейса появился в платформе 8.3.3 (июль 2013 года). Для того, чтобы обеспечить работу с базами 1С через браузер, в том числе на мобильных устройствах, потребовалось отказаться в конфигурациях от использования модальных окон, которые при открытии блокируют весь интерфейс браузера, включая остальные открытые окна и вкладки.
В чем заключается недостаток модальных окон в браузерах? Дело в том, что всплывающие окна в Интернете часто используются для распространения нежелательной рекламы, поэтому браузеры содержат функции блокировки всплывающих окон.
Поэтому рекомендуется использовать блокирующие окна вместо модальных. Блокирующее окно как бы “рисуется” поверх главного окна, блокируется только текущая вкладка браузера, в которой открыта конфигурация, позволяя переключаться на другие вкладки, поскольку модальные окна браузера при этом не используются.

Для пользователя внешне все выглядит так же, как и с использованием модальных окон. Но разработчикам на платформе “1С:Предприятие” пришлось переработать программный код с использованием новых неблокирующих методов, чтобы конфигурация корректно работала в веб-клиенте. А фирме “1С” необходимо было реализовать соответствующие методы в платформе, учитывая требования, диктуемые разработчиками браузеров.
Асинхронные вызовы расширений и внешних компонент
Рассмотрим еще один пример, демонстрирующий, что требования браузеров могут влиять на программный код в конфигурациях на платформе “1С:Предприятие”.
В сентябре 2013 года было опубликовано, что с января 2014 года браузер Google Chrome по умолчанию будет блокировать плагины NPAPI, а в итоге поддержка NPAPI будет полностью удалена из Chrome. И действительно в сентябре 2015 года был выпущен Google Chrome (версия 45), который перестал поддерживать эту технологию на всех ОС. Аналогично поступили и разработчики других браузеров – Opera, Firefox, Safari, Internet Explorer теперь также не поддерживают NPAPI.
NPAPI (Netscape Plugin Application Programming Interface) – это технология разработки дополнительных модулей (плагинов) для различных браузеров. В качестве примеров можно привести Adobe Flash Player, Microsoft Silverlight, Java Runtime Environment.
В чем же причина отказа от поддержки этой технологии? Ее архитектура была разработана еще в 90-х годах XX века. На сегодняшний день она устарела, могла становиться причиной зависаний и сбоев, были выявлены проблемы, связанные с безопасностью. Кроме этого NPAPI не поддерживается на мобильных устройствах.
В качестве альтернативы компания Google предлагает применять технологию Native Messaging. Принципиально важной особенностью этой технологии является обязательное использование асинхронных методов.
Может возникнуть вопрос – какое отношение используемые в браузерах технологии могут иметь к программному коду на встроенном языке “1С:Предприятие”?
Дело в том, что при использовании веб-клиента для работы с файлами, криптографией, буфером обмена и внешними компонентами требуется установить расширение для браузера. Следовательно, это расширение должно поддерживать актуальные технологии. Поэтому фирма “1С” внесла изменения в платформу 8.3.5 и реализовала для методов встроенного языка, которые использовали технологию NPAPI, аналогичные асинхронные методы. Например, вместо метода ПолучитьФайлы теперь нужно применять НачатьПолучениеФайлов. А разработчикам конфигураций в очередной раз пришлось переработать программный код.
Таким образом, современные браузеры диктуют ряд изменений, которые приходится вносить в платформу. Также видно, что все чаще используются асинхронные методы. Поэтому теперь нужно детально разобраться, что же такое синхронный и асинхронный код, в чем их отличия, а также понять, почему возникла необходимость использовать именно асинхронные методы в конфигурациях.
Синхронные методы
С самых первых версий платформы “1С:Предприятие” в каждом отдельном сеансе программный код, написанный на встроенном языке, исполняется строго последовательно. Это и называется синхронным исполнением программного кода, а соответствующие методы встроенного языка – синхронными.
Интерпретатор встроенного языка получает очередную команду для исполнения, приступает к ее выполнению. Этот процесс занимает какое-то время, после чего возвращается результат. Затем интерпретатор может переходить к следующей команде. И так до тех пор, пока все команды не будут выполнены.
Рассмотрим пример программного кода:
Счетчик = 1; ТекстСообщения = "Сообщение"; Сообщить(ТекстСообщения);
Он выполняется пошагово:
- Первым делом будет выполнено присваивание переменной Счетчик значения 1.
- Осуществляется переход ко второй строке кода, выполняется операция присваивания.
- Осуществляется переход к третьей строке кода, выводится сообщение пользователю.
Таким образом, в каждый конкретный момент времени исполняется только одно действие, а несколько действий в рамках одного сеанса не могут выполняться одновременно.
Здесь можно привести аналогию с лентой конвейера, которая по одной, друг за другом, поставляет детали для обработки на оборудовании:

Аналогично интерпретатор встроенного языка поочередно выполняет поступающие команды:

Важно отметить, что выполняющийся синхронный метод блокирует основной поток выполнения кода до тех пор, пока этот метод не завершится. Если в ходе выполнения этот метод вызывает другие процедуры или функции, то необходимо будет дождаться их выполнения.
Поэтому такой код и называется синхронным – вызывающий и вызываемый код согласованы (синхронизированы) по времени, т.е. вызывающий метод не будет исполняться в то время, когда исполняется вызванный метод. Когда вызванный метод завершится, управление вернется к вызывающему методу, он может продолжить свое выполнение. В этом и заключается синхронизация.
В процессе выполнения метода могут использоваться сложные вычисления, занимающие много времени. Выход из этого метода не будет произведен до тех пор, пока работа этого метода не будет завершена. Для пользователя это будет выражаться в том, что приложение не будет реагировать на действия пользователя, курсор изменит свой вид:
Если в программном коде используется метод, который требует реакции пользователя (вопрос или предупреждение), то поток выполнения кода будет заблокирован до тех пор, пока пользователь не ответит на вопрос или не закроет окно с предупреждением.
Пусть есть следующий программный код:
Ответ = Вопрос("Выполнить загрузку?", РежимДиалогаВопрос.ДаНет); Сообщить("Начало загрузки");
Метод Вопрос является синхронным, т.е. блокирует поток выполнения команд. Значит, платформа не перейдет к выполнению следующей команды (не выведет сообщение) до тех пор, пока пользователь не ответит на вопрос.

При работе в тонком клиенте такое поведение не является проблемой, система будет ожидать ответа пользователя, после чего выполнение кода продолжится. Но при работе в веб-клиенте существует ряд ограничений, которые накладываются браузерами.
Веб-клиент написан на JavaScript. Движок JavaScript в браузере работает по следующей логике: он ожидает поступления задач к выполнению, поочередно, последовательно выполняет их и затем снова ожидает появления следующей задачи. Примерами задач может быть выполнение скрипта на веб-странице, выполнение обработчиков событий (нажатие пользователем кнопок на клавиатуре, движение мышью и т.д.). Подобный способ обработки задач называется событийным циклом (англ. – Event Loop).

JavaScript фактически является однопоточным. Если задача выполняется продолжительное время, браузер в это же время не сможет обрабатывать пользовательские события, возможно “зависание” браузера. Другими словами, в основном потоке может происходить что-то одно – или исполняется код на языке JavaScript, или браузер реагирует на действия пользователя.
Поэтому в браузерах существуют ограничения на время работы скриптов. Это своего рода защита от скриптов, которые выполняются слишком долго. С ее помощью браузеры предотвращают блокировку интерфейса.
Рассмотрим пример. Пусть есть такой HTML-документ:
html> body> button onclick="LongTermCalculation()">Длительная операцияbutton> script type="text/javascript"> function LongTermCalculation() var res = 0; for (var i = 0; i 10000000000; i++) res += Math.sin(i); > alert(res); > script> body> html>На странице располагается кнопка, при нажатии на которую исполняется код на JavaScript. В этом коде на каждой итерации цикла вычисляется синус от значения счетчика цикла. Это достаточно длительная операция, поскольку требуется выполнить 10 миллиардов итераций цикла. Поэтому браузер периодически проверяет, не завис ли выполняемый скрипт, задает пользователю вопрос, не следует ли прервать его выполнение. Например, в Mozilla Firefox такой вопрос выглядит следующим образом:

Одним из способов решения описанной проблемы является использованием асинхронных методов, которые предназначены для предотвращения блокировки основного потока. Асинхронные методы могут быть реализованы в JavaScript, а также в других языках – Python, C#.
Рассмотрим асинхронные методы подробнее.
Асинхронные методы
Синхронный метод блокирует основной поток выполнения кода до тех пор, пока этот метод не завершится, включая все процедуры и функции, которые из этого метода вызываются. Браузеры, как было рассмотрено выше, имеют ограничения на выполнение синхронных методов. Например, современные браузеры не позволяют выполнять операции с файловой системой клиентского компьютера в синхронном режиме.
Поэтому были придуманы асинхронные методы – это такие методы, выполнение которых не происходит синхронно с кодом, из которого они вызваны. В этом случае нет синхронизации по времени между вызывающим и вызываемым кодом. Вызывающий метод не дожидается завершения вызванного метода, а продолжает свое выполнение после вызова асинхронного метода. За счет этого не блокируется основной поток выполнения кода.
Перепишем программный код на встроенном языке из прошлой главы, заменив синхронный вызов на асинхронный:
ПоказатьВопрос(Новый ОписаниеОповещения("ПоказатьВопросЗавершение", ЭтотОбъект), "Выполнить загрузку?", РежимДиалогаВопрос.ДаНет); Сообщить("Начало загрузки");
Метод Вопрос является синхронным, а метод ПоказатьВопрос – асинхронным. После вызова асинхронного метода управление вернется к коду, вызвавшему этот метод, до того, как сам асинхронный метод ПоказатьВопрос отработает. Таким образом, асинхронный метод не блокирует основной поток. Поэтому сообщение о начале загрузки будет показано еще до того, как пользователь ответит на вопрос.
Когда вызванный асинхронный метод завершит свою работу (пользователь ответит на заданный вопрос), он оповестит о своем завершении и предоставит доступ к результату (в данном случае это будет информация о том, какую кнопку в диалоговом окне нажал пользователь: Да или Нет). Это событие можно обработать – в зависимости от нажатой пользователем кнопки выполнить то или иное действие.

Для того, чтобы система знала, что нужно сделать при завершении работы асинхронного метода, первым параметром метода ПоказатьВопрос указывается специальный объект встроенного языка ОписаниеОповещения, которое и представляет собой описание процедуры, которую нужно вызвать после того, как пользователь ответит на вопрос. Подробнее про этот объект поговорим в следующем разделе.
Асинхронные методы встроенного языка можно использовать, например, в следующих сценариях:
- Работа с блокирующими окнами:
- Задать вопрос пользователю;
- Показать предупреждение;
- Открыть окно, блокирующее интерфейс, и обработать результат закрытия этого окна
- Получить файл с сервера;
- Получить каталог временных файлов;
- Скопировать файлы;
- Удалить файлы
- Получить перечень сертификатов;
- Проверить сертификат;
- Проверить действительность подписи;
- Зашифровать данные;
- Расшифровать зашифрованные данные
- Подключить внешнюю компоненту;
- Обратиться к ее свойствам и методам.
В качестве примера асинхронных методов встроенного языка можно привести клиентские методы для работы с файлами, поскольку в веб-клиенте доступны только асинхронные методы работы с файлами. Поэтому вместо синхронного метода КопироватьФайл следует использовать асинхронные аналоги НачатьКопированиеФайла или КопироватьФайлАсинх, вместо синхронного метода ПолучитьФайлы – НачатьПолучениеФайловССервера или ПолучитьФайлыССервераАсинх и т.д.
Обратите внимание, что указанные асинхронные методы доступны только на клиенте. На сервере доступны аналогичные синхронные методы для работы с файлами, криптографией и внешними компонентами.
JavaScript имеет развитый функционал для работы с асинхронными методами. Особенно часто асинхронный код применяется при получении данных или для доступа к внешним устройствам – получение файлов с удаленных серверов, получение данных из базы, доступ к видео с веб-камеры.
Таким образом, некоторые приемы программирования на JavaScript теперь можно применять при разработке на платформе “1С:Предприятие”. Во встроенном языке существуют схожие способы реализации асинхронности, которые уже давно присутствуют в JavaScript:
- Использование методов обратного вызова (callback)
- Использование Обещаний (promise)
В следующих главах подробно рассмотрим эти приемы.
Использование методов обратного вызова
Принцип работы методов обратного вызова
После вызова асинхронного метода управление вернется к коду, вызвавшему этот метод, не дожидаясь, когда асинхронный метод завершит свою работу. Когда вызванный асинхронный метод отработает, он оповестит о своем завершении и предоставит доступ к результату. Оповещение о завершении происходит при помощи вызова процедуры, описание которой передается в асинхронно вызываемый метод. Эта процедура и называется методом обратного вызова (англ. callback – дословно “обратный звонок”), поскольку позволяет асинхронному методу “связаться” с вызвавшим его кодом:

Во многих языках программирования, в том числе и в JavaScript (на котором написан веб-клиент), можно передавать функцию как параметр другой процедуры. Рассмотрим подробнее на примере.
Пусть есть следующий код на языке JavaScript:
console.log('1'); console.log('2'); console.log('3');
Этот код последовательно выводит в консоль числа 1, 2 и 3. Это синхронный код.
Теперь модифицируем его – реализуем вывод числа 2 в консоль по таймеру, через 2 секунды:
console.log('1'); setTimeout(LogTwoSecondsLater, 2000); console.log('3'); function LogTwoSecondsLater() console.log('2') >
Функция setTimeout ожидает 2 секунды (вторым параметром функции указан период ожидания в миллисекундах: 2000 мс = 2 с), а затем вызывает функцию обратного вызова LogTwoSecondsLater, переданную в качестве первого параметра. Функция setTimeout не блокирует основной поток. Скрипт не “зависнет” на 2 секунды, а продолжит выполнение. В консоль будут выведены числа 1, 3. И только через 2 секунды, когда сработает таймер, будет вызвана функция LogTwoSecondsLater, в консоль будет выведено число 2.
Но во встроенном языке “1С:Предприятие” нельзя указывать функцию в качестве параметра другого метода. Поэтому приведенный ниже код иллюстрирует логику работы асинхронных функций с использованием метода обратного вызова, но является синтаксически некорректным:
Процедура ДлительнаяЗагрузка() ЗагрузитьАсинхронно(ВыполнитьДействияПослеЗагрузки); КонецПроцедуры Процедура ЗагрузитьАсинхронно(МетодОбратногоВызова) //Действия для загрузки данных. МетодОбратногоВызова(); КонецПроцедуры Процедура ВыполнитьДействияПослеЗагрузки() //Оповещение, что загрузка завершена. Действия после завершения загрузки. КонецПроцедуры
Во встроенном языке асинхронный код с использованием методов обратного вызова реализован при помощи объекта ОписаниеОповещения. Этот объект содержит описание метода обратного вызова:
- Какую процедуру нужно выполнить после завершения асинхронного метода?
- В каком модуле она располагается?
- Какие параметры нужно в нее передать?
Для того, чтобы правильно составить описание метода обратного вызова, корректно описать все необходимые параметры, удобно использовать Синтакс-помощник. В нем указаны все параметры метода обратного вызова, который будет выполнен при завершении асинхронного метода.
Например, для метода ПоказатьВопрос после ответа пользователя на вопрос будет вызвана процедура со следующими параметрами:
- РезультатВопроса
- ДополнительныеПараметры

Пример 1 – Организация диалога с пользователем
Рассмотрим пример. В нем задается вопрос пользователю, на который он может ответить Да или Нет. После того, как пользователь ответит на вопрос, в сообщении выводим, сколько времени ему потребовалось на ответ.
Для реализации такого функционала используется следующий код в модуле формы:
&НаКлиенте Процедура ЗадатьВопрос(Команда) ДопПараметры = Новый Структура("ВремяНачала", ТекущаяДата()); ОписаниеОповещения = Новый ОписаниеОповещения("ПоказатьВопросЗавершение", ЭтотОбъект, ДопПараметры); ПоказатьВопрос(ОписаниеОповещения, "Выполнить загрузку?", РежимДиалогаВопрос.ДаНет); Сообщить("Начало загрузки"); КонецПроцедуры &НаКлиенте Процедура ПоказатьВопросЗавершение(РезультатВопроса, ДополнительныеПараметры) Экспорт Сообщить("Выбран вариант: " + РезультатВопроса); Если ТипЗнч(ДополнительныеПараметры) = Тип("Структура") Тогда ВремяНачала = Неопределено; Если ДополнительныеПараметры.Свойство("ВремяНачала", ВремяНачала) Тогда ЗатраченноеВремя = ТекущаяДата() - ВремяНачала; ТекстСообщения = СтрШаблон("Затраченное время: %1 %2", ЗатраченноеВремя, "с"); Сообщить(ТекстСообщения); КонецЕсли; КонецЕсли; КонецПроцедуры
Выделим важные моменты в этом коде:
- Используется асинхронный метод ПоказатьВопрос. В нем первым параметром передается описание оповещения. Оно сообщает системе, что после того, как пользователь ответит на вопрос, нужно вызвать метод ПоказатьВопросЗавершение, расположенный в модуле этой же формы, и передать в него ДопПараметры.
- Методы обратного вызова могут располагаться в модуле формы, общем модуле или в модуле команды. Расположение метода указывается вторым параметром в конструкторе объекта ОписаниеОповещения.
- Метод ПоказатьВопросЗавершение обязательно должен быть экспортным, даже если он располагается в модуле той же формы. Если не указать ключевое слово Экспорт, то будет выдана ошибка:

- Поскольку метод ПоказатьВопрос – асинхронный, после вызова этого метода управление вернется к коду, вызвавшему его, не дожидаясь, когда асинхронный метод завершит свою работу. Поэтому сообщение о начале загрузки появится до того, как пользователь ответит на вопрос. В этом заключается важное отличие асинхронного метода от синхронного. Синхронный метод Вопрос блокирует поток выполнения команд, поэтому следующая команда может быть выполнена только после того, как пользователь ответит на вопрос.
- Если нужно выполнять какие-то действия после того, как пользователь ответит на вопрос, то эти действия нужно выполнять в методе ПоказатьВопросЗавершение, а не сразу после вызова метода ПоказатьВопрос. Это также связано с тем, что метод ПоказатьВопрос является асинхронным.
- После ответа пользователя на вопрос будет вызвана отдельная процедура. Может потребоваться передать в нее какие-то данные, необходимые для дальнейшей работы. Для этого можно использовать ДополнительныеПараметры – второй параметр метода ПоказатьВопросЗавершение. В нашем примере нужно знать время начала, когда пользователю был показан вопрос. Поэтому время начала передается в метод обратного вызова через ДополнительныеПараметры.
Обратите внимание, что имена асинхронных процедур, которые используют методы обратного вызова, начинаются со слов Начать или Показать. Приведем в таблице несколько примеров – имя синхронного метода и соответствующего ему асинхронного:
Синхронный метод Асинхронный метод Вопрос ПоказатьВопрос Предупреждение ПоказатьПредупреждение ОткрытьЗначение ПоказатьЗначение КопироватьФайл НачатьКопированиеФайла ВвестиЧисло ПоказатьВводЧисла НайтиФайлы НачатьПоискФайлов ПолучитьФайл НачатьПолучениеФайлаССервера Пример 2 – Сохранение файла на клиентском компьютере
Рассмотрим более сложный пример.
По данным информационной базы нужно сформировать файл с расширением xlsx, затем сохранить его на компьютере пользователя. Пользователь в диалоге сохранения файла должен указать имя файла и каталог для его сохранения.
Для реализации такого функционала можно использовать следующий код в модуле формы:
&НаКлиенте Процедура СохранитьФайлНаКлиенте(Команда) //1. Формирование файла на сервере, помещение во временное хранилище АдресФайлаВоВременномХранилище = СформироватьФайлНаСервере(); Если НЕ ЭтоАдресВременногоХранилища(АдресФайлаВоВременномХранилище) Тогда Возврат; КонецЕсли; //2. Открытие диалога сохранения файла Диалог = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Сохранение); Диалог.Заголовок = "Выберите файл для сохранения"; Диалог.МножественныйВыбор = Ложь; Диалог.Фильтр = "Документы Excel|*.xlsx|Все файлы|*.*"; ДопПараметры = Новый Структура("АдресФайлаВоВременномХранилище", АдресФайлаВоВременномХранилище); ОписаниеОповещения = Новый ОписаниеОповещения("ОповещениеПослеВыбораФайлаДляЗаписи", ЭтотОбъект, ДопПараметры); Диалог.Показать(ОписаниеОповещения); КонецПроцедуры &НаСервере Функция СформироватьФайлНаСервере() ИмяФайла = ПолучитьИмяВременногоФайла("xlsx"); //Формирование файла, заполнение его данными Макет = РеквизитФормыВЗначение("Объект").ПолучитьМакет("Макет"); ОбластьШапка = Макет.ПолучитьОбласть("Шапка"); ТабличныйДокумент = Новый ТабличныйДокумент; ТабличныйДокумент.Вывести(ОбластьШапка); //Заполнение табличного документа //. ТабличныйДокумент.Записать(ИмяФайла, ТипФайлаТабличногоДокумента.XLSX); ДвоичныеДанные = Новый ДвоичныеДанные(ИмяФайла); Попытка УдалитьФайлы(ИмяФайла); Исключение Сообщить(ОписаниеОшибки()); КонецПопытки; Возврат ПоместитьВоВременноеХранилище(ДвоичныеДанные, ЭтотОбъект.УникальныйИдентификатор); КонецФункции &НаКлиенте Процедура ОповещениеПослеВыбораФайлаДляЗаписи(ВыбранныеФайлы, ДополнительныеПараметры) Экспорт //3. Получение данных из временного хранилища, сохранение с выбранным именем Если ТипЗнч(ВыбранныеФайлы) = Тип("Массив") Тогда Если ТипЗнч(ДополнительныеПараметры) = Тип("Структура") Тогда АдресФайлаВоВременномХранилище = Неопределено; Если ДополнительныеПараметры.Свойство("АдресФайлаВоВременномХранилище", АдресФайлаВоВременномХранилище) Тогда ОписаниеОповещения = Новый ОписаниеОповещения("ОповещениеПослеПолученияФайла", ЭтотОбъект); НачатьПолучениеФайлаССервера(ОписаниеОповещения, АдресФайлаВоВременномХранилище, ВыбранныеФайлы[0]); КонецЕсли; КонецЕсли; КонецЕсли; КонецПроцедуры &НаКлиенте Процедура ОповещениеПослеПолученияФайла(ПолученныйФайл, ДополнительныеПараметры) Экспорт //4. Уведомление пользователя о сохраненном файле Состояние("Записан файл " + ПолученныйФайл.ПолноеИмя); КонецПроцедуры
Разберем подробнее основные моменты в приведенном программном коде:
- На сервере в каталоге временных файлов создаем файл с уникальным именем и расширением xlsx. Заполняем его данными и помещаем во временное хранилище. Адрес во временном хранилище передаем на клиент, чтобы была возможность доступа к этим данным из клиентских процедур.
- Открываем диалог сохранения файла. Устанавливаем фильтр для диалога, чтобы пользователь мог сохранить файл с правильным расширением. Для отображения диалога используется асинхронный метод Показать, которому в параметре передается описание оповещения. Оно указывает системе, что после выбора пользователем имени файла будет вызвана процедура ОповещениеПослеВыбораФайлаДляЗаписи из модуля этой же формы, в эту процедуру будет передан адрес во временном хранилище, где располагаются данные требуемого файла.
- Вызывается метод НачатьПолучениеФайлаССервера – инициируется получение файла с сервера и сохранение его на клиентском компьютере под указанным пользователем именем. В качестве параметра передается еще одно описание оповещения. Оно указывает, что после завершения получения файла с сервера будет вызвана процедура ОповещениеПослеПолученияФайла.
- После того, как загрузка файла с сервера на клиентский компьютер закончена, система уведомляет пользователя, что загрузка файла завершена.
Схематично изобразим последовательность вызовов методов из приведенного фрагмента кода:

Вложенные процедуры с асинхронными методами
В предыдущих примерах описание оповещения передавалось в качестве параметра в асинхронный метод. Когда асинхронный метод завершал свою работу (пользователь ответил на вопрос, файл с сервера был сохранен на клиентский компьютер и т.д.), платформа вызывала метод, указанный в описании оповещения. Явного вызова обработчиков оповещений из программного кода не происходило.
Во встроенном языке есть метод ВыполнитьОбработкуОповещения, который используется для того, чтобы явно выполнить метод обратного вызова, указанный в качестве параметра. Такое действие может потребоваться в сложных случаях, когда асинхронные методы “вложены” друг в друга, и после цепочки асинхронных вызовов нужно выполнить действие, определенное раньше, еще до начала этих асинхронных вызовов.
В качестве примера рассмотрим следующую задачу. Необходимо открыть форму для выгрузки данных во внешнюю систему. Выгрузку можно производить только в том случае, когда в константе Лицензия заполнены данные лицензии для выгрузки. Если лицензия не заполнена, нужно предложить пользователю ввести данные лицензии, сохранить их в константе, после чего открыть форму для выгрузки данных во внешнюю систему.
Для начала рассмотрим синхронный код, решающий поставленную задачу:
&НаКлиенте Процедура ОтправитьВоВнешнююСистему(Команда) ДанныеДляВнешнейСистемы = Новый Структура; ДанныеДляВнешнейСистемы.Вставить("Дата", ТекущаяДата()); ДанныеДляВнешнейСистемы.Вставить("Описание", "ОписаниеПлатежа"); //. Если ПроверитьЛицензию() Тогда //Открыть форму подключения к внешней системе ОткрытьФорму("Обработка.ПодключениеКВнешнейСистеме.Форма", ДанныеДляВнешнейСистемы); Иначе ТекстВопроса = "Для подключения к внешней системе необходимо указать данные лицензии. |Ввести сейчас?"; Ответ = Вопрос(ТекстВопроса, РежимДиалогаВопрос.ДаНет); Если Ответ = КодВозвратаДиалога.Да Тогда Лицензия = ""; ВвестиСтроку(Лицензия, "Лицензия"); Если НЕ ПустаяСтрока(Лицензия) Тогда СохранитьЛицензию(Лицензия); //Открыть форму подключения к внешней системе ОткрытьФорму("Обработка.ПодключениеКВнешнейСистеме.Форма", ДанныеДляВнешнейСистемы); КонецЕсли; КонецЕсли; КонецЕсли; КонецПроцедуры &НаСервереБезКонтекста Функция ПроверитьЛицензию() Лицензия = Константы.Лицензия.Получить(); Возврат ЗначениеЗаполнено(Лицензия); КонецФункции &НаСервереБезКонтекста Процедура СохранитьЛицензию(Лицензия) Константы.Лицензия.Установить(Лицензия); КонецПроцедуры
Синхронный программный код достаточно компактный, легко читается, удобен для анализа. В нем встречаются методы Вопрос и ВвестиСтроку. Нужно заменить их на асинхронные аналоги ПоказатьВопрос и ПоказатьВводСтроки, создать описания оповещений и разделить код на отдельные процедуры, поскольку после вызова асинхронного метода управление вернется к коду, вызвавшему его, не дожидаясь, когда асинхронный метод завершит свою работу.
Получится следующий асинхронный код:
&НаКлиенте Процедура ОтправитьВоВнешнююСистему(Команда) ДанныеДляВнешнейСистемы = Новый Структура; ДанныеДляВнешнейСистемы.Вставить("Дата", ТекущаяДата()); ДанныеДляВнешнейСистемы.Вставить("Описание", "ОписаниеПлатежа"); //. ОбработкаПродолжения = Новый ОписаниеОповещения("ОтправитьВоВнешнююСистемуПродолжение", ЭтотОбъект, ДанныеДляВнешнейСистемы); НачатьПроверкуЛицензииИПодключениеКВнешнейСистеме(ОбработкаПродолжения); КонецПроцедуры &НаКлиенте Процедура НачатьПроверкуЛицензииИПодключениеКВнешнейСистеме(ОбработкаПродолжения) Экспорт Если ПроверитьЛицензию() Тогда ВыполнитьОбработкуОповещения(ОбработкаПродолжения, Истина); Иначе ПоказатьВопросВводаЛицензии(ОбработкаПродолжения); КонецЕсли; КонецПроцедуры &НаСервереБезКонтекста Функция ПроверитьЛицензию() Лицензия = Константы.Лицензия.Получить(); Возврат ЗначениеЗаполнено(Лицензия); КонецФункции &НаКлиенте Процедура ПоказатьВопросВводаЛицензии(ОбработкаЗавершения) ПараметрыЗавершения = Новый Структура; ПараметрыЗавершения.Вставить("ОбработкаЗавершения", ОбработкаЗавершения); ОбработкаОтвета = Новый ОписаниеОповещения("ПоказатьВопросВводаЛицензииЗавершение", ЭтотОбъект, ПараметрыЗавершения); ТекстВопроса = "Для подключения к внешней системе необходимо указать данные лицензии. |Ввести сейчас?"; ПоказатьВопрос(ОбработкаОтвета, ТекстВопроса, РежимДиалогаВопрос.ДаНет); КонецПроцедуры &НаКлиенте Процедура ПоказатьВопросВводаЛицензииЗавершение(Ответ, Параметры) Экспорт Если Ответ = КодВозвратаДиалога.Да Тогда ОбработкаПодключения = Новый ОписаниеОповещения("ВводЛицензииЗавершение", ЭтотОбъект, Параметры); ПоказатьВводСтроки(ОбработкаПодключения, "", "Лицензия"); Иначе ВыполнитьОбработкуОповещения(Параметры.ОбработкаЗавершения, Ложь); КонецЕсли; КонецПроцедуры &НаКлиенте Процедура ВводЛицензииЗавершение(ВведеннаяСтрока, Параметры) Экспорт Если ПустаяСтрока(ВведеннаяСтрока) Тогда ВыполнитьОбработкуОповещения(Параметры.ОбработкаЗавершения, Ложь); Иначе СохранитьЛицензию(ВведеннаяСтрока); ВыполнитьОбработкуОповещения(Параметры.ОбработкаЗавершения, Истина); КонецЕсли; КонецПроцедуры &НаСервереБезКонтекста Процедура СохранитьЛицензию(Лицензия) Константы.Лицензия.Установить(Лицензия); КонецПроцедуры &НаКлиенте Процедура ОтправитьВоВнешнююСистемуПродолжение(ЕстьЛицензия, ДанныеДляВнешнейСистемы) Экспорт Если НЕ ЕстьЛицензия Тогда Возврат; КонецЕсли; //Открыть форму подключения к внешней системе ОткрытьФорму("Обработка.ПодключениеКВнешнейСистеме.Форма", ДанныеДляВнешнейСистемы); КонецПроцедуры
Обратите внимание на использование метода ВыполнитьОбработкуОповещения. Описание оповещения создается в самой первой процедуре – ОтправитьВоВнешнююСистему. В нем указывается, что должна быть вызвана процедура ОтправитьВоВнешнююСистемуПродолжение, в которую следует передать в качестве параметра данные для выгрузки во внешнюю систему. Но вызов этой процедуре происходит не в момент создания описания оповещения, а позднее, в других процедурах – после проверки заполненности лицензии или после того, как пользователь введет номер лицензии.
Получается, что из-за асинхронности код состоит из логически взаимосвязанных отдельных процедур. Переход к открытию формы подключения к внешней системы может производиться из разных точек алгоритма. Поэтому в этих точках нужно знать данные для выгрузки во внешнюю систему и результат проверки лицензии.
Вспомним, что в асинхронных процедурах есть параметр с именем ДополнительныеПараметры, при помощи которого в метод обратного вызова можно передать произвольные данные. Этот параметр и используется в приведенном алгоритме. С его помощью организуется передача данных между вызываемыми друг за другом асинхронными методами. В этом параметре передаются данные для выгрузки и указывается метод, который нужно вызвать для открытия формы подключения к внешней системе. Для явного вызова этого метода и используется процедура ВыполнитьОбработкуОповещения.
Из приведенного кода видно, что использование методов обратного вызова усложняет разработку. Рассмотрим, какие сложности встречаются при написании асинхронного кода и какие способы упрощения может предложить платформа “1С:Предприятие”.
Сложности при использовании методов обратного вызова
В языках программирования, в которых можно передавать функцию как параметр другой процедуры, например, в JavaScript, есть такой термин, как callback hell (англ. – ад обратных вызовов) – многократно вложенные друг в друга блоки. В качестве примера можно привести следующий код:
function callbackHell()< step1(function() < step2(function() < step3(function() < step4(function() < step5(function() < //... >); >); >); >); >); >
Подобные конструкции делают код сложным для чтения, анализа и поддержки.
Во встроенном языке нет возможности указать функцию в качестве параметра другого метода, поэтому не удастся создать точно такой же код. Но использование методов обратного вызова усложняет код, делает его фрагментированным. Получается своего рода аналог callback hell для встроенного языка “1С:Предприятие”.
Вспомним для примера код, передающий файл с сервера на клиентский компьютер. Обратите внимание, что линейный по своей логике код (без условных операторов и циклов) оказывается разделенным на отдельные фрагменты только из-за того, что используются асинхронные процедуры с методами обратного вызова. Это усложняет разработку кода.
Также сложно разобраться в сильно фрагментированном коде (особенно в типовых конфигурациях), поскольку отдельная процедура сама по себе не дает полного представления, что именно делает программный код. Нужно обязательно отследить всю цепочку процедур, выяснить взаимосвязи между процедурами, только тогда можно разобраться, как работает этот код.
В качестве примера можно проанализировать код типовой конфигурации 1С:Розница, который выполняет закрытие кассовой смены. Этот процесс достаточно сложный, включает в себя несколько этапов:
- Выбрать кассу, по которой нужно произвести закрытие
- Подтвердить закрытие смены
- Проверить наличие непробитых чеков
- Запросить сумму выемки денежных средств
- Выполнить сверку итогов по картам на эквайринговом терминале
- Заархивировать чеки за текущую смену, сформировать отчет о розничных продажах
- Создать документ выемки денежных средств, отправить команду выемки на ККТ
- Сформировать Z-отчет на ККТ
- Проверить наличие ошибок ККТ при формировании Z-отчета
- Зафиксировать время закрытия кассовой смены в базе
- Передать документы в ЕГАИС
- Открыть формы сформированных документов
- Вывести сообщение с итоговой информацией о продажах и возвратах
Как видно, этапов достаточно много. К тому же периодически нужно задавать уточняющие вопросы, открывать дополнительные формы, обрабатывать возникающие ошибки. Поэтому цепочка методов получается сложная, приходится разбираться в похожих процедурах, вроде ПродолжитьЗакрытиеСменыОкончание, ПродолжитьЗакрытиеСменыОкончаниеФискальныйОтчетЗавершение и т.д.
Подобный код со своими нюансами есть во многих типовых конфигурациях – УТ, БП и т.д. За счет наличия в коде асинхронных методов код типовых конфигураций усложнился – его тяжелее и создавать, и отлаживать.
Поэтому в следующей главе рассмотрим инструменты платформы для упрощения написания кода.
Инструменты рефакторинга
В конфигураторе есть набор инструментов, которые призваны упростить работу с асинхронными методами. Команды доступны в меню конфигуратора Текст – Рефакторинг, а также в контекстном меню текстового редактора в подменю Рефакторинг:

Рассмотрим их подробнее.
Создать обработку оповещения
При создании описания метода обратного вызова нужно корректно указать все параметры этого метода. Конечно, можно посмотреть их в Синтакс-помощнике и вручную составить описание метода, но быстрее и удобнее будет, если платформа сама сгенерирует такой метод. И такая возможность есть.
Если установить курсор на имени асинхронного метода, после которого следует открывающаяся скобка, например, ПоказатьВопрос, НачатьПеремещениеФайла и т.д., то в меню станет доступна команда Создать обработку оповещения, которая самостоятельно сформирует описание оповещения и метод обратного вызова.
Рассмотрим пример. Есть фрагмент кода, в котором для метода ПоказатьВопрос не указан первый параметр – обработка оповещения:
&НаКлиенте Процедура ЗагрузитьДанные() ПоказатьВопрос(, "Выполнить загрузку?", РежимДиалогаВопрос.ДаНет); КонецПроцедуры
Установим курсор на метод ПоказатьВопрос и вызовем команду Создать обработку оповещения. Получим следующий код:
&НаКлиенте Процедура ЗагрузитьДанные() ПоказатьВопрос(Новый ОписаниеОповещения("ЗагрузитьДанныеЗавершение", ЭтаФорма), "Выполнить загрузку?", РежимДиалогаВопрос.ДаНет); КонецПроцедуры &НаКлиенте Процедура ЗагрузитьДанныеЗавершение(РезультатВопроса, ДополнительныеПараметры) Экспорт КонецПроцедуры
Это очень удобный инструмент, который ускоряет создание кода, поскольку теперь не требуется искать в Синтакс-помощнике описание параметров метода обратного вызова и создавать сам метод вручную.
Преобразовать вызов
Этот инструмент рефакторинга помогает преобразовать код, содержащий синхронный метод, в код с использованием соответствующего асинхронного метода. Также при этом создается описание оповещение и метод обратного вызова.
Рассмотрим пример. Есть фрагмент кода:
&НаКлиенте Процедура ЗагрузитьДанные() Ответ = Вопрос("Выполнить загрузку?", РежимДиалогаВопрос.ДаНет); Если Ответ = КодВозвратаДиалога.Да Тогда //Логика загрузки КонецЕсли; КонецПроцедуры
Установим курсор на метод Вопрос и вызовем команду Преобразовать вызов. Получим следующий код с использованием асинхронного метода ПоказатьВопрос:
&НаКлиенте Процедура ЗагрузитьДанные() Ответ = Неопределено; ПоказатьВопрос(Новый ОписаниеОповещения("ЗагрузитьДанныеЗавершение", ЭтаФорма), "Выполнить загрузку?", РежимДиалогаВопрос.ДаНет); КонецПроцедуры &НаКлиенте Процедура ЗагрузитьДанныеЗавершение(РезультатВопроса, ДополнительныеПараметры) Экспорт Ответ = РезультатВопроса; Если Ответ = КодВозвратаДиалога.Да Тогда //Логика загрузки КонецЕсли; КонецПроцедуры
Этот инструмент позволяет переписать код с устаревшими синхронными методами, помогает автоматически заменить их на соответствующие асинхронные методы. Конечно, любой сформированный автоматически код обязательно нужно проверять, чтобы при рефакторинге не потерялась логика.
Найти вызовы модуля
Команда Преобразовать вызов работает только с одним участком кода. Часто требуется проанализировать целый модуль, найти в нем “нерекомендуемые синхронные методы”, чтобы заменить их на асинхронные аналоги. Для этого существует команда Найти вызовы модуля. Найденные строки кода, содержащие “нерекомендуемые синхронные вызовы”, выводятся в окно с результатами поиска:

Анализ нерекомендуемых синхронных вызовов конфигурации
Команда Найти вызовы модуля ищет синхронные методы только в одном модуле. Если нужно проанализировать такие вызовы во всей конфигурации, то можно воспользоваться командой в меню Конфигурация – Рефакторинг – Анализ нерекомендуемых синхронных вызовов конфигурации:

Этот инструмент умеет не только находить синхронные вызовы во всех модулях конфигурации, но и выполнять автоматическую замену на соответствующие асинхронные методы в простых случаях. Также есть возможность создавать заготовки для методов обратного вызова.
Таким образом, фирма “1С” не “спрятала” асинхронность от программистов на уровне платформы. Нужно разобраться с этой функциональностью. Для реализации асинхронности с использованием методов обратного вызова следует применять асинхронные методы (вместо Вопрос – ПоказатьВопрос, вместо Предупреждение – ПоказатьПредупреждение и т.д.). Если же в существующем коде встречаются синхронные вызовы, его нужно переработать – заменить синхронные методы на соответствующие им асинхронные. Для упрощения разработки можно использовать рассмотренные инструменты конфигуратора по рефакторингу кода.
В следующей главе рассмотрим новый вариант реализации асинхронности, который появился в платформе 8.3.18. Это асинхронность с использованием обещаний.
Использование обещаний
Использование методов обратного вызова усложняет программный код, делает его фрагментированным, сложным для восприятия и доработки. Синхронный код, в котором выполняемые команды расположены последовательно, друг за другом, создается и анализируется гораздо проще. Но для работы в веб-клиенте требуется именно асинхронный принцип выполнения операций. Как разрешить это противоречие – упростить написание кода и выполнить требования, предъявляемые браузерами?
В других языках программирования, таких как JavaScript или Python, существует тип данных Promise (англ. – обещание) и ключевые слова async/await. Использование этих конструкций делает асинхронный код простым, читаемым и удобным для доработки.
Похожий подход был реализован фирмой “1С” в платформе 8.3.18. Причем новый функционал не завязан на режим совместимости конфигурации, т.е. режим совместимости может быть установлен, например, в значение Версия 8.3.6, но при использовании платформы 8.3.18 и новее возможно использовать функционал асинхронности через обещания.
Важно отметить, что сам принцип использования асинхронности в платформе остался прежним. По-прежнему асинхронность доступна для работы с блокирующими окнами, файлами и каталогами, механизмами криптографии и внешними компонентами. Изменился только “внешний вид” программного кода, другими словами – способ указания платформе, какой именно код нужно выполнить после завершения асинхронного метода. Главная задача нововведения – приблизить асинхронный код к обычному последовательному коду, уменьшить сложность разработки.
Рассмотрим далее подробнее новые возможности платформы.
Новый тип – Обещание
В платформе 8.3.18 появился новый тип данных – Обещание. Объект такого типа предназначен для организации асинхронного кода.
Обещание представляет собой “хранилище” для пока что еще неизвестного результата выполнения некоторого действия (асинхронной функции).
Почему используется термин Обещание? Вспомним, что при вызове асинхронного метода поток выполнения не прерывается, управление возвращается к коду, вызвавшему этот метод. Но на выполнение самого асинхронного метода требуется время (пока пользователь ответит на вопрос или пока файл будет передан с сервера на клиент). Во время выполнения асинхронного метода нельзя сказать, какой результат он вернул, поскольку метод еще не завершился. Как только асинхронный метод завершится, он уведомит о своем выполнении и предоставит доступ к результату своей работы.
Получается, что при вызове асинхронной функции она должна сразу вернуть управление вызвавшему ее коду, не блокируя поток выполнения. Значит, она должна вернуть какое-то значение. Но какое? Ведь на выполнение функции потребуется время, а результат выполнения еще неизвестен в данный момент. Значит, асинхронная функция не может сразу вернуть результат выполнения, но может “пообещать” вернуть его в указанную переменную, как только функция завершит работу. В этом и заключается смысл использования Обещаний.
Поэтому асинхронная функция во встроенном языке всегда возвращает значение типа Обещание, которое может находиться в одном из трех состояний:
- Ожидание (Pending) – начальное состояние. Асинхронная функция еще не завершила свое выполнение, результат еще неизвестен.
- Завершено успешно (Success/Fulfilled). Асинхронная функция отработала успешно. В таком случае Обещание содержит результат выполнения асинхронной функции.
- Завершено с ошибкой (Failure/Rejected). При выполнении асинхронной функции было вызвано исключение, не обработанное программно. В таком случае Обещание содержит исключение.
Таким образом, Обещание из начального состояния может перейти в одно из двух финальных состояний: или “Завершено успешно”, или “Завершено с ошибкой” в зависимости от результата работы асинхронной функции:

Фактически Обещание является заменой объекта ОписаниеОповещения. При использовании асинхронности с методами обратного вызова в этом объекте указывается, какую процедуру нужно выполнить после завершения асинхронного метода. Результат выполнения асинхронного метода будет содержаться в параметре метода обратного вызова. При использовании асинхронности через обещания результат асинхронной функции будет содержаться именно в объекте типа Обещание.
Объект типа Обещание доступен только в клиентском контексте: в тонком, толстом, мобильном клиентах и в веб-клиенте. На сервере этот объект недоступен. Это связано с тем, что асинхронные методы во встроенном языке создавались для использования только на клиенте.
Объект Обещание не имеет конструкторов, методов и свойств. Объект такого типа можно получить во встроенном языке только в качестве результата вызова асинхронной функции. Далее Обещание можно использовать в качестве аргумента оператора Ждать, чтобы получить результат выполнения асинхронной функции. Примеры программного кода будут рассмотрены далее. Других сценариев использования объекта Обещание не предусмотрено.
Асинхронные функции
Ранее отмечалось, что изначально во встроенном языке присутствовали только синхронные методы, которые блокируют поток выполнения команд.
Затем появились асинхронные процедуры, которые используют методы обратного вызова. Имена таких процедур начинаются со слов Начать или Показать, а в качестве параметра в процедуру передается ОписаниеОповещения. После завершения асинхронной процедуры производится вызов метода, указанного в объекте ОписаниеОповещения.
В платформе 8.3.18 появились новые асинхронные функции, возвращающие Обещание. Имена таких функций заканчиваются на Асинх.
Приведем в таблице несколько примеров – имя синхронного метода, соответствующую ему асинхронную процедуру и новую асинхронную функцию:
Синхронный метод Асинхронная процедура, использующая метод обратного вызова Асинхронная функция, возвращающая Обещание Вопрос ПоказатьВопрос ВопросАсинх Предупреждение ПоказатьПредупреждение ПредупреждениеАсинх ОткрытьЗначение ПоказатьЗначение ОткрытьЗначениеАсинх КопироватьФайл НачатьКопированиеФайла КопироватьФайлАсинх ВвестиЧисло ПоказатьВводЧисла ВвестиЧислоАсинх НайтиФайлы НачатьПоискФайлов НайтиФайлыАсинх ПолучитьФайл НачатьПолучениеФайлаССервера ПолучитьФайлССервераАсинх При помощи Синтакс-помощника для синхронного метода можно найти соответствующие ему асинхронные методы:

Сравним, например, методы для отображения вопроса пользователю. Отметим их сходства и отличия.
Синтаксис этих методов выглядит следующим образом:
Вопрос(ТекстВопроса>, Кнопки>, Таймаут>, КнопкаПоУмолчанию>, Заголовок>, КнопкаТаймаута>) ПоказатьВопрос(ОписаниеОповещенияОЗавершении>, ТекстВопроса>, Кнопки>, Таймаут>, КнопкаПоУмолчанию>, Заголовок>, КнопкаТаймаута>) ВопросАсинх(ТекстВопроса>, Кнопки>, Таймаут>, КнопкаПоУмолчанию>, Заголовок>, КнопкаТаймаута>)
Вопрос и ВопросАсинх являются функциями.
ПоказатьВопрос – это процедура. Первым параметром передается объект ОписаниеОповещения, который указывает, какой именно метод должен быть вызван после того, как пользователь ответит на вопрос или откажется от ответа.
Новая функция ВопросАсинх, появившаяся в платформе 8.3.18, возвращает объект типа Обещание. Перечень параметров у этой функции такой же, как и у синхронного метода Вопрос.
Процедура ПоказатьВопрос и функция ВопросАсинх выполняются асинхронно. Ответ пользователя при использовании процедуры ПоказатьВопрос будет передан в первом параметре метода обратного вызова. При использовании функции ВопросАсинх тот же ответ пользователя будет содержаться в объекте типа Обещание.
Отметим, что не для всех синхронных методов реализованы аналогичные функции с модификатором Асинх. Например, для метода формы ОткрытьМодально соответствующая асинхронная функция не существует. Поэтому следует использовать процедуру ОткрытьФорму с указанием обработчика оповещения:
&НаКлиенте Процедура ВыбратьНоменклатуру(Команда) ОписаниеОповещения = Новый ОписаниеОповещения("ОткрытиеФормыЗавершение", ЭтотОбъект); ОткрытьФорму("Справочник.Номенклатура.ФормаВыбора", , , , , , ОписаниеОповещения, РежимОткрытияОкнаФормы.БлокироватьОкноВладельца); КонецПроцедуры &НаКлиенте Процедура ОткрытиеФормыЗавершение(РезультатЗакрытия, ДополнительныеПараметры) Экспорт //Обработка результата. КонецПроцедуры
Работа с асинхронными функциями
Асинхронная функция возвращает значение типа Обещание. Чтобы из этого значения получить результат работы асинхронной функции, нужно воспользоваться оператором Ждать, который также появился в платформе 8.3.18. Единственным параметром оператора Ждать является объект типа Обещание:
Результат = Ждать Обещание;
Оператор Ждать выполняет ожидание завершения работы вызванной асинхронной функции, которая вернула Обещание, переданное как параметр оператора Ждать. Если асинхронная функция отработала штатно (Обещание находится в состоянии “Завершено успешно”), то результатом оператора Ждать является результат асинхронной функции. Если же асинхронная функция вызвала исключение, которое не было обработано в программном коде (Обещание находится в состоянии “Завершено с ошибкой”), оператор Ждать вызовет исключение.
В коде может встретиться вот такая строка:
Ждать 1000;
Что она означает? Может показаться, что это способ организации паузы во встроенном языке, а 1000 – это длительность ожидания в миллисекундах. Но на самом деле это совсем не так. Если параметром оператора Ждать является любое значение, кроме Обещания, то платформа создаст Обещание, в котором будет “спрятано” указанное значение (в данном случае – число 1000). Затем созданное Обещание будет передано как параметр оператора Ждать. Результатом такого оператора является число 1000, но этот результат не присваивается другой переменной, поэтому в строке кода опущен оператор присваивания:
Результат = Ждать 1000;
Так что указанная строка – это синтаксически корректный программный код, но не способ организации паузы.
Модификатор Асинх
Имена асинхронных функций платформы, возвращающих Обещание, заканчиваются на Асинх. Если разработчику нужно реализовать собственную процедуру или функцию, которая будет работать асинхронно, ее нужно описать с использованием модификатора Асинх:
&НаКлиенте Асинх Процедура ИмяПроцедуры() КонецПроцедуры &НаКлиенте Асинх Функция ИмяФункции() КонецФункции
Выделим несколько принципиально важных особенностей процедур или функций с модификатором Асинх.
Только внутри процедур или функций с модификатором Асинх можно использовать оператор Ждать. Также важно, что модификатор Асинх можно использовать только для клиентских процедур и функций. Если внутри обработчика клиентского события планируется использовать оператор Ждать, то такой обработчик также нужно описать с модификатором Асинх.
В процедурах и функциях с модификатором Асинх параметры всегда передаются по значению. При этом неважно, будет ли в описании метода использоваться ключевое слово Знач перед параметром или нет. Поэтому два указанных ниже описания асинхронных процедур являются абсолютно одинаковыми, параметр будет передан по значению:
Асинх Процедура ЗадатьВопрос(Знач РежимДиалога) Асинх Процедура ЗадатьВопрос(РежимДиалога)
Функция с модификатором Асинх всегда возвращает Обещание. Но по правилам встроенного языка функция должна возвращать то значение, которое разработчик указывает в качестве параметра оператора Возврат. Функция без модификатора Асинх именно так и делает – возвращает значение параметра оператора Возврат. Функция с модификатором Асинх возвращает Обещание. Если такая функция завершится успешно, без генерации исключения, то в Обещании будет “спрятано” возвращаемое значение. Чтобы из Обещания получить конкретное значение, нужно воспользоваться оператором Ждать:
&НаКлиенте Асинх Процедура ВывестиДанныеИзВнешнейСистемы(Команда) Результат = Ждать ПолучитьЗначениеИзВнешнейСистемы(); КонецПроцедуры &НаКлиенте Асинх Функция ПолучитьЗначениеИзВнешнейСистемы() //Здесь должен располагаться программный код для получения данных. Возврат Значение; КонецФункции
Если функция с модификатором Асинх во время своего выполнения вызвала исключение, то в Обещании будет “спрятано” это исключение.
Поговорим про перехват исключений подробнее, здесь тоже есть несколько важных нюансов.
Обработка исключений в асинхронных процедурах и функциях
Исключение, возникшее в процедуре или функции с модификатором Асинх, не отобразится пользователю. Например, вот в таком программном коде:
&НаКлиенте Процедура ВывестиДанныеИзВнешнейСистемы(Команда) ПолучитьЗначениеИзВнешнейСистемы(); КонецПроцедуры &НаКлиенте Асинх Функция ПолучитьЗначениеИзВнешнейСистемы() Возврат 5/0;//деление на ноль КонецФункции
Аналогично в таком коде выполнение никогда не попадет в секцию Исключение, именно потому что функция ПолучитьЗначениеИзВнешнейСистемы является асинхронной. Исключение, вызванное в такой функции, не возникнет в вызывающем коде:
&НаКлиенте Процедура ВывестиДанныеИзВнешнейСистемы(Команда) Попытка ПолучитьЗначениеИзВнешнейСистемы(); Исключение Сообщить(ОписаниеОшибки());//в эту строку не попадем КонецПопытки; КонецПроцедуры &НаКлиенте Асинх Функция ПолучитьЗначениеИзВнешнейСистемы() Возврат 5/0;//деление на ноль КонецФункции
Чтобы в вызывающем коде обработать исключение, возникшее в функции с модификатором Асинх, нужно Обещание, возвращаемое такой функцией, использовать в операторе Ждать:
&НаКлиенте Асинх Процедура ВывестиДанныеИзВнешнейСистемы(Команда) Попытка Ждать ПолучитьЗначениеИзВнешнейСистемы(); Исключение Сообщить(ОписаниеОшибки()); КонецПопытки; КонецПроцедуры &НаКлиенте Асинх Функция ПолучитьЗначениеИзВнешнейСистемы() Возврат 5/0; КонецФункции
Любая процедура встроенного языка, в том числе процедура с модификатором Асинх, в отличие от функции, не возвращает значения. Поэтому процедура не может вернуть Обещание, следовательно, в коде, вызвавшем асинхронную процедуру, принципиально нельзя перехватить исключение, возникшее в асинхронной процедуре. Такое исключение будет сразу показано пользователю:

&НаКлиенте Процедура ВывестиДанныеИзВнешнейСистемы(Команда) Попытка ПолучитьЗначениеИзВнешнейСистемы(); Исключение Сообщить(ОписаниеОшибки()); КонецПопытки; КонецПроцедуры &НаКлиенте Асинх Процедура ПолучитьЗначениеИзВнешнейСистемы() А = 5/0; КонецПроцедуры
Поэтому процедуры с модификатором Асинх удобно использовать в качестве обработчиков клиентских событий формы, в которых нужно будет применять оператор Ждать. В остальных случаях удобнее будет воспользоваться асинхронными функциями, поскольку это позволит обрабатывать исключения.
Примеры использования асинхронных функций
Организация диалога с пользователем
Необходимо задать вопрос пользователю, на который он может ответить Да или Нет. После того, как пользователь ответит на вопрос, в сообщении выводим, сколько времени ему потребовалось на ответ.
Для решения поставленной задачи в модуле формы написан следующий код:
&НаКлиенте Асинх Процедура ЗадатьВопрос(Команда) ВремяНачала = ТекущаяДата(); Результат = Ждать ВопросАсинх("Выполнить загрузку?", РежимДиалогаВопрос.ДаНет); Сообщить("Начало загрузки"); ЗатраченноеВремя = ТекущаяДата() - ВремяНачала; ТекстСообщения = СтрШаблон("Затраченное время: %1 %2", ЗатраченноеВремя, "с"); Сообщить(ТекстСообщения); КонецПроцедуры
В коде используется асинхронная функция платформы ВопросАсинх, которая возвращает Обещание. Это Обещание указывается в качестве параметра оператора Ждать. Система ожидает реакции пользователя. Когда пользователь ответит на вопрос и Обещание “разрешится”, в переменную Результат будет помещен выбранный пользователем вариант ответа на вопрос (Да или Нет).
Эта же задача раньше была решена с использованием метода обратного вызова. В данном примере программный код получился более простым, причем он очень похож на код с использованием аналогичных синхронных методов.
Копирование файла
Необходимо скопировать файл с одного диска на другой. Для решения этой задачи в модуле формы используется следующий код:
&НаКлиенте Асинх Процедура СкопироватьФайл(Команда) Попытка Результат = Ждать КопироватьФайлАсинх("C:\1.xlsx", "D:\1.xlsx"); Исключение Сообщить(ОписаниеОшибки()); КонецПопытки; КонецПроцедуры
В этом фрагменте кода используется асинхронная функция платформы КопироватьФайлАсинх. Она также возвращает Обещание, поступающее в оператор Ждать в качестве параметра. Система дожидается, когда копирование файла будет завершено. Когда файл будет скопирован, Обещание будет “развернуто”, из него будет получена строка, содержащая полное имя скопированного файла. Эта строка будет помещена в переменную Результат.
Важный момент и отличие от предыдущего примера – асинхронная функция платформы КопироватьФайлАсинх может вызвать исключение в процессе своей работы, например, когда не был найден исходный файл, когда нет прав доступа для записи файла, когда недостаточно места на диске и т.д. В таком случае в Обещании, возвращаемом функцией КопироватьФайлАсинх, будет содержаться исключение. Чтобы перехватить это исключение и обработать его, используется конструкция Попытка.
Проверка возможности работы с внешней системой
Ранее рассматривалась задача проверки возможности выгрузки данных во внешнюю систему. Для ее решения был написан синхронный код, а также процедуры, использующие методы обратного вызова. Теперь решим эту же задачу с использованием функций, возвращающих Обещание.
Напомним, что выгрузку можно производить только в том случае, когда в константе Лицензия заполнены данные лицензии для выгрузки. Если лицензия не заполнена, нужно предложить пользователю ввести данные лицензии, сохранить их в константе, после чего открыть форму для выгрузки данных во внешнюю систему.
Программный код выглядит следующим образом:
&НаКлиенте Асинх Процедура ОтправитьВоВнешнююСистему(Команда) ДанныеДляВнешнейСистемы = Новый Структура; ДанныеДляВнешнейСистемы.Вставить("Дата", ТекущаяДата()); ДанныеДляВнешнейСистемы.Вставить("Описание", "ОписаниеПлатежа"); //. Если ПроверитьЛицензию() Тогда //Открыть форму подключения к внешней системе Иначе ТекстВопроса = "Для подключения к внешней системе необходимо указать данные лицензии. |Ввести сейчас?"; Ответ = Ждать ВопросАсинх(ТекстВопроса, РежимДиалогаВопрос.ДаНет); Если Ответ = КодВозвратаДиалога.Да Тогда Лицензия = ""; Лицензия = Ждать ВвестиСтрокуАсинх(Лицензия, "Лицензия"); Если НЕ ПустаяСтрока(Лицензия) Тогда СохранитьЛицензию(Лицензия); //Открыть форму подключения к внешней системе КонецЕсли; КонецЕсли; КонецЕсли; КонецПроцедуры &НаСервереБезКонтекста Функция ПроверитьЛицензию() Лицензия = Константы.Лицензия.Получить(); Возврат ЗначениеЗаполнено(Лицензия); КонецФункции &НаСервереБезКонтекста Процедура СохранитьЛицензию(Лицензия) Константы.Лицензия.Установить(Лицензия); КонецПроцедуры
В этом фрагменте кода используются асинхронные функции платформы ВопросАсинх и ВвестиСтрокуАсинх, которые возвращают Обещание. Логика работы с асинхронными функциями такая же, как и в двух предыдущих примерах, – Обещание передается в качестве параметра оператора Ждать, который ожидает выполнения асинхронных функций, после чего может быть получен доступ к результату их работы.
Отметим важное отличие приведенного кода от кода с использованием объектов типа ОписаниеОповещения. При использовании асинхронных функций, возвращающих Обещания, не нужно разрабатывать цепочку взаимосвязанных методов обратного вызова, организовывать передачу данных между ними. Код становится не таким фрагментированным.
Таким образом, использование асинхронных функций, возвращающих Обещания, упрощает работу разработчикам, код становится более понятным (похожим на код с использованием аналогичных синхронных методов), простым для анализа и модификации.
Вопрос пользователю во время закрытия формы
Перед закрытием формы или перед закрытием всего приложения нужно задать вопрос, действительно ли следует выполнить закрытие, чтобы пользователь не потерял несохраненные данные. Попробуем решить эту задачу с использованием асинхронных методов. В форме создадим обработчик события ПередЗакрытием:
&НаКлиенте Асинх Процедура ПередЗакрытием(Отказ, ЗавершениеРаботы, ТекстПредупреждения, СтандартнаяОбработка) Если ЗавершениеРаботы Тогда Отказ = Истина; ТекстПредупреждения = "Несохраненные данные будут потеряны. Закрыть форму?"; Иначе Ответ = Ждать ВопросАсинх("Закрыть форму без сохранения данных?", РежимДиалогаВопрос.ДаНет); Если Ответ = КодВозвратаДиалога.Нет Тогда Отказ = Истина; КонецЕсли; КонецЕсли; КонецПроцедуры
Начиная с платформы 8.3.8, нужно разделять два варианта закрытия:
- Форма закрывается при закрытии приложения в целом
- Пользователь закрывает конкретную форму
Поэтому в коде обрабатывается два этих варианта. При закрытии приложения в целом параметр ЗавершениеРаботы принимает значение Истина. Важно, что программно нельзя отменить закрытие приложения, можно только показать предупреждение пользователю. Для этого параметр Отказ устанавливается в значение Истина. В таком случае пользователю при закрытии будет показано предупреждение, которое передается в параметре ТекстПредупреждения.
При закрытии конкретной формы пользователю задается вопрос. Если пользователь отвечает, что не требуется закрывать форму, параметр Отказ устанавливается в значение Истина, форма не закрывается.
Но при проверке работоспособности этого кода в режиме “1С:Предприятие” обнаруживается неожиданное поведение. При закрытии приложения никакое предупреждение не отображается. При закрытии формы сначала форма закрывается, а только потом отображается вопрос. При любом ответе пользователя на вопрос ничего не происходит, потому что форма уже закрылась.
Почему система ведет себя таким образом?
При закрытии приложения в целом или конкретной формы платформа вызывает обработчик события формы ПередЗакрытием. В этом обработчике можно управлять поведением системы при помощи изменения значения параметров. В приведенном выше примере изменялись значения параметров Отказ и ТекстПредупреждения.
Вспомним, что в процедурах и функциях с модификатором Асинх параметры всегда передаются по значению. Поэтому изменения значений параметров, произведенные внутри асинхронной процедуры ПередЗакрытием, недоступны для кода, который вызвал эту процедуру. Поэтому “снаружи” этой процедуры параметр Отказ принимает значение Ложь. В таком случае при закрытии приложения предупреждение пользователю не выводится, приложение просто закрывается. Это и наблюдается в пользовательском режиме именно потому, что процедура ПередЗакрытием асинхронная:

Когда закрывается только одна форма, а не приложение в целом, в приведенном коде происходит вызов асинхронной функции ВопросАсинх. При этом выполнение процедуры ПередЗакрытием приостанавливается, управление возвращается к коду, вызвавшему этот метод. Поскольку процедуру ПередЗакрытием вызвала платформа, управление возвращается платформе. Параметр Отказ в коде не был изменен, кроме того любые изменения параметра в самой асинхронной процедуре “не видны” тому коду, который вызвал эту асинхронную процедуру, потому что параметры в нее передаются по значению. Значит, при возвращении управления платформе параметр Отказ принимает значение Ложь, поэтому форма закрывается. Вопрос пользователю отображается, но когда пользователь ответит на вопрос, форма уже закрыта, отменить ее закрытие невозможно.
Эта особенность асинхронных обработчиков событий может оказаться неожиданной. Поэтому если процедура – обработчик события должна через параметры передать какие-то данные вызывающему ее коду, нельзя описывать ее с модификатором Асинх из-за описанной выше особенности.
Вместо асинхронной функции в данном случае можно использовать метод обратного вызова:
&НаКлиенте Перем ЗакрытиеФормыРазрешено; &НаКлиенте Процедура ПередЗакрытием(Отказ, ЗавершениеРаботы, ТекстПредупреждения, СтандартнаяОбработка) Если ЗавершениеРаботы Тогда Отказ = Истина; ТекстПредупреждения = "Несохраненные данные будут потеряны. Закрыть форму?"; ИначеЕсли ЗакрытиеФормыРазрешено = Неопределено Тогда ОписаниеОповещения = Новый ОписаниеОповещения("ПередЗакрытиемЗавершение", ЭтотОбъект); ПоказатьВопрос(ОписаниеОповещения, "Закрыть форму без сохранения данных?", РежимДиалогаВопрос.ДаНет); Отказ = Истина; КонецЕсли; КонецПроцедуры &НаКлиенте Процедура ПередЗакрытиемЗавершение(РезультатВопроса, ДополнительныеПараметры) Экспорт Если РезультатВопроса = КодВозвратаДиалога.Да Тогда ЗакрытиеФормыРазрешено = Истина; Закрыть(); КонецЕсли; КонецПроцедуры
Ошибки при использовании асинхронных функций в коде
Разберем наиболее часто встречающиеся ошибки при использовании асинхронных функций в программном коде.
Ошибка 1 – Только клиентский контекст
Объявим асинхронную функцию с директивой компиляции НаСервере:
&НаСервере Асинх Функция ПроверитьНаСервере() //. Возврат РезультатПроверки; КонецФункции
При проверке синтаксиса модуля конфигуратор выдаст ошибку:
Недопустимое объявление асинхронной процедуры или функции
Ошибка возникла, потому что модификатор Асинх можно использовать только для клиентских процедур и функций. К тому же тип данных Обещание доступен только на клиенте. Поэтому для исправления ошибки нужно или убрать из описания функции модификатор Асинх, или использовать директиву компиляции НаКлиенте.
Ошибка 2 – Оператор Ждать только в асинхронных методах
Рассмотрим следующий пример. В клиентской процедуре задаем вопрос пользователю:
&НаКлиенте Процедура ЗадатьВопрос(Команда) Результат = Ждать ВопросАсинх("Выполнить загрузку?", РежимДиалогаВопрос.ДаНет); //. КонецПроцедуры
При проверке синтаксиса модуля конфигуратор выдает ошибку:
Оператор Ждать (Await) может употребляться только в асинхронных процедурах или функциях
Для исправления ошибки нужно воспользоваться модификатором Асинх для описания этой процедуры:
&НаКлиенте Асинх Процедура ЗадатьВопрос(Команда) Результат = Ждать ВопросАсинх("Выполнить загрузку?", РежимДиалогаВопрос.ДаНет); //. КонецПроцедуры
Ошибка 3 – Подходящие версии платформы
Еще один пример. Процедура выглядит синтаксически корректно:
&НаКлиенте Асинх Процедура ЗадатьВопрос(Команда) Результат = Ждать ВопросАсинх("Выполнить загрузку?", РежимДиалогаВопрос.ДаНет); //. КонецПроцедуры
Но проверка синтаксиса в конфигураторе выдает ошибки:
Ожидается определение процедуры/функции >Асинх Процедура ЗадатьВопрос(Команда) (Проверка: Тонкий клиент) Ожидается символ ';' Результат = Ждать> ВопросАсинх("Выполнить загрузку?", РежимДиалогаВопрос.ДаНет); (Проверка: Тонкий клиент)Такая ошибка может возникать, если открыть данный программный код на платформе 8.3.17 или предыдущих, в которых еще не реализован функционал Обещаний. Обратите внимание, что в приведенном фрагменте кода не подсвечиваются красным ключевые слова Асинх и Ждать. Чтобы ошибка не возникала, нужно использовать платформу 8.3.18 или новее.
Принципы работы асинхронных функций
Разберем подробнее, как работают асинхронные функции, возвращающие значение типа Обещание.
Ранее отмечалось, что оператор Ждать выполняет ожидание завершения работы вызванной асинхронной функции, которая вернула Обещание, переданное как параметр оператора Ждать. Получается, что оператор Ждать блокирует поток выполнения команд, пока асинхронная функция не отработает? Как тогда такие функции могут использоваться в веб-клиенте?
Нет, блокировки потока не происходит. Асинхронность в платформе “1С:Предприятие” осталась такая же, как и при работе с методами обратного вызова. Изменился только подход к написанию кода.
С логической точки зрения оператор Ждать ожидает, пока завершится асинхронная функция. Технически при этом происходит приостановка выполнения текущего метода, управление возвращается к коду, вызвавшему текущий метод. Происходит выход из текущего выполняющегося метода, но при этом значения локальных переменных, использующихся в текущем методе, сохраняются. Это позволит в дальнейшем, после завершения работы асинхронной функции, вернуться к продолжению работы текущего метода.
Асинхронная функция работает, это занимает какое-то время, наконец она завершается. Оператор Ждать в зависимости от результата работы функции может вернуть значение или вызвать исключение. После этого происходит возобновление приостановленного метода, выполняется повторный вход в этот метод в ту строку, где была выполнена приостановка. Восстанавливаются значения локальных переменных, что позволяет дальше выполнять код метода с момента приостановки с тем же самым контекстом.
На примере продемонстрируем, что при выполнении оператора Ждать действительно происходит возврат к вызвавшему методу:
&НаКлиенте Асинх Процедура ВывестиОтветНаВопрос(Команда) Обещание = ОтветНаВопрос(); Сообщить("Пользователь выбрал вариант"); Сообщить(Ждать Обещание); КонецПроцедуры &НаКлиенте Асинх Функция ОтветНаВопрос() Возврат Ждать ВопросАсинх("Выполнить загрузку?", РежимДиалогаВопрос.ДаНет); КонецФункции
При выполнении данного кода в пользовательском режиме вывод сообщения “Пользователь выбрал вариант” произойдет раньше, чем пользователь ответит на вопрос. Хотя в коде строка с выводом сообщения следует после вызова функции, задающей вопрос пользователю. Это демонстрирует, что основной поток выполнения не был заблокирован во время ожидания реакции пользователя. Выполнение функции ОтветНаВопрос было приостановлено, управление вернулось к процедуре ВывестиОтветНаВопрос.
Проанализируем, как выполняется код асинхронной процедуры:
&НаКлиенте Асинх Процедура СкопироватьФайл(Команда) СкопированныйФайл = Ждать КопироватьФайлАсинх("C:\1.xlsx", "D:\1.xlsx"); Сообщить(СкопированныйФайл); КонецПроцедуры
В данном фрагменте кода приостановка выполнения происходит в операторе Ждать. Приостановка будет продолжаться до того момента, пока не разрешится Обещание, возвращаемое асинхронной функцией КопироватьФайлАсинх. Другими словами – до тех пор, пока файл не будет скопирован либо не возникнет исключение при выполнении функции. После разрешения Обещания происходит возобновление выполнения процедуры. Имя файла-получателя будет присвоено переменной СкопированныйФайл. После этого платформа может переходить к выполнению следующей строки кода, сообщить значение этой переменной пользователю.
Такое поведение платформы можно сравнить с использованием метода обратного вызова и провести аналогию. Код уже знаком нам:
&НаКлиенте Процедура СкопироватьФайл(Команда) ОписаниеОповещения = Новый ОписаниеОповещения("КопированиеФайлаЗавершение", ЭтотОбъект); НачатьКопированиеФайла(ОписаниеОповещения, "C:\1.xlsx", "D:\1.xlsx"); КонецПроцедуры &НаКлиенте Процедура КопированиеФайлаЗавершение(СкопированныйФайл, ДополнительныеПараметры) Экспорт Сообщить(СкопированныйФайл); КонецПроцедуры
Здесь аналог приостановки – это завершение процедуры СкопироватьФайл после вызова метода НачатьКопированиеФайла. Аналог возобновления – это выполнение метода обратного вызова, указанного в описании оповещения, после завершения копирования файла. Отличие заключается в форме – вместо последовательного кода используется набор логически связанных процедур.
Поэтому ранее отмечалось, что глобально смысл асинхронности в платформе не изменился с появлением Обещаний. Изменился только способ написания кода.
Рассмотрим более сложный пример, демонстрирующий приостановку и возобновление программного кода. На форме есть реквизит с типом ТаблицаЗначений, у которой есть две колонки – ИмяФайла и Размер:

Необходимо найти в определенном каталоге все файлы с расширением xlsx и вывести в эту таблицу значений на форме имя и размер найденных файлов:

Программный код для решения поставленной задачи приведен ниже:
1 &НаКлиенте 2 Асинх Процедура ВывестиСведенияОФайлах(Команда) 3 ОбещаниеФайлы = ПолучитьСведенияОФайлах(); 4 МассивСведений = Ждать ОбещаниеФайлы; 5 ТаблицаФайлов.Очистить(); 6 Для каждого СведенияОФайле Из МассивСведений Цикл 7 НоваяСтрока = ТаблицаФайлов.Добавить(); 8 ЗаполнитьЗначенияСвойств(НоваяСтрока, СведенияОФайле); 9 КонецЦикла; 10 КонецПроцедуры 11 12 &НаКлиенте 13 Асинх Функция ПолучитьСведенияОФайлах() 14 Результат = Новый Массив; 15 ОбещаниеНайденныеФайлы = НайтиФайлыАсинх("C:\Users\User1\Downloads\", "*.xlsx", Ложь); 16 НайденныеФайлы = Ждать ОбещаниеНайденныеФайлы; 17 Для каждого НайденныйФайл Из НайденныеФайлы Цикл 18 ОбещаниеРазмерФайла = НайденныйФайл.РазмерАсинх(); 19 РазмерФайла = Ждать ОбещаниеРазмерФайла; 20 Результат.Добавить(Новый Структура("ИмяФайла, Размер", НайденныйФайл.Имя, РазмерФайла)); 21 КонецЦикла; 22 Возврат Результат; 23 КонецФункции
Разберем, в каком порядке будут выполняться строки этого асинхронного кода. Строки кода пронумерованы, чтобы удобнее было ссылаться на них.
- При нажатии на кнопку на форме срабатывает обработчик – процедура ВывестиСведенияОФайлах (строка 2). В строке 3 вызывается асинхронная функция ПолучитьСведенияОФайлах (строка 13).
- В строке 14 создается массив, в который будем помещать данные о найденных файлах. Функция ПолучитьСведенияОФайлах возвращает массив структур, каждая структура содержит имя и размер одного найденного файла.
- В строке 15 производится вызов асинхронной функции платформы НайтиФайлыАсинх. Эта функция возвращает Обещание, которое в строке 16 используется в качестве параметра оператора Ждать. Выполнение функции ПолучитьСведенияОФайлах приостанавливается, управление возвращается к вызвавшему эту функцию коду – в строку 3.
- Асинхронная функция ПолучитьСведенияОФайлах возвращает Обещание (строка 3). Это Обещание в строке 4 передается в качестве параметра оператора Ждать. Выполнение процедуры ВывестиСведенияОФайлах приостанавливается.
- Получается, что в данный момент существует 2 Обещания (ОбещаниеФайлы и ОбещаниеНайденныеФайлы), которые предназначены для хранения еще неизвестных результатов функций ПолучитьСведенияОФайлах и НайтиФайлыАсинх соответственно. Эти функции еще не завершили выполнение, поэтому невозможно получить их результат, нужно дождаться их выполнения. При этом важно, что система не может ждать выполнения обещаний нескольких асинхронных методов одновременно. Это может происходить только последовательно.
- После завершения работы функции НайтиФайлыАсинх происходит возобновление выполнения функции ПолучитьСведенияОФайлах в строке 16, восстанавливаются сохраненные на момент приостановки значения локальных переменных. В переменную НайденныеФайлы помещается массив объектов типа Файл. Остается только одно нерешенное Обещание – ОбещаниеФайлы.
- Выполнение кода перемещается на строку 17, в цикле перебираются элементы массива НайденныеФайлы.
- В строке 18 для каждого найденного файла вызывается асинхронная функция РазмерАсинх, которая возвращает еще одно Обещание – ОбещаниеРазмерФайла. В данный момент существует 2 Обещания (ОбещаниеФайлы и ОбещаниеРазмерФайла).
- В строке 19 используется оператор Ждать, поэтому еще раз происходит приостановка выполнения функции ПолучитьСведенияОФайлах. Получается, что никакой код в этот момент не выполняется, стек вызовов пустой, вернуть управление вызывающему коду нет возможности, поэтому управление возвращается платформе.
- После того, как размер файла определен, выполнение функции ПолучитьСведенияОФайлах возобновляется в строке 19. В переменной РазмерФайла содержится вычисленный размер файла, который был “спрятан” в ОбещаниеРазмерФайла.
- В строке 20 в массив Результат добавляется структура с именем и размером найденного файла. Строки 17-21 выполняются столько раз, сколько файлов было найдено, т.е. сколько элементов есть в массиве НайденныеФайлы.
- В строке 22 происходит возврат из функции ПолучитьСведенияОФайлах. Значит, ожидание в строке 4 завершается, в переменной МассивСведений содержится массив структур с информацией о найденных файлах.
- Остается только вывести полученные сведения о найденных файлах на экран, что происходит в строках 5-9.
- Выполнение процедуры ВывестиСведенияОФайлах завершается в строке 10.
Итак, вот в таком порядке выполняются строки кода, который выглядит последовательным, но фактически при его выполнении происходит приостановка и возобновление асинхронных методов.
Изобразим на схеме указанную последовательность шагов:

Особенности использования отладчика при работе с асинхронными функциями
Асинхронный код не выполняется строго линейно, в том порядке, как строки кода следуют друг за другом, поскольку происходят приостановки и возобновления выполнения, возврат управления вызывающему коду.
Для удобства разработчиков в отладчике была реализована “имитация” синхронного выполнения асинхронных функций. Это означает, что при выполнении команд Шагнуть через (F10) и Шагнуть в (F11) на строке, содержащей оператор Ждать, отладчик будет дожидаться завершения работы асинхронной функции, после чего перейдет к следующей строке кода. Хотя фактически при этом произойдет выход из текущего метода и передача управления вызвавшему эту функцию коду. За счет такого поведения “эмулируется” синхронная работа отладчика.
Рассмотрим пример. Есть следующий асинхронный код:
1 &НаКлиенте 2 Асинх Процедура ВывестиОтветНаВопрос(Команда) 3 Обещание = ОтветНаВопрос(); 4 Сообщить("Пользователь выбрал вариант"); 5 Сообщить(Ждать Обещание); 6 КонецПроцедуры 7 8 &НаКлиенте 9 Асинх Функция ОтветНаВопрос() 10 Возврат Ждать ВопросАсинх("Выполнить загрузку?", РежимДиалогаВопрос.ДаНет); 11 КонецФункции
Разработчик в отладчике остановился на строке 10, в которой используется оператор Ждать. При выполнении команды Шагнуть через (F10) в пользовательском режиме будет показан вопрос, а также выведено сообщение из строки 4. После ответа пользователя на вопрос указатель текущей строки кода перемещается на строку 11, хотя фактически строка 4 уже была выполнена.
Еще раз выполним команду Шагнуть через (F10). При этом указатель текущей строки окажется на строке 6, а не на строке 4, как это произошло бы при синхронных вызовах. Получается, что отладчик может оказаться не на той строке, на которой разработчик ожидает остановиться.
Такое поведение объясняется природой асинхронных вызовов, потому что при ожидании на строке 10 выполнение функции ОтветНаВопрос приостановливается, управление передается вызывающем коду в процедуру ВывестиОтветНаВопрос. Выводится сообщение в строке 4. В строке 5 используется оператор Ждать, поэтому происходит приостановка выполнения процедуры ВывестиОтветНаВопрос, управление возвращается платформе.
После того, как пользователь ответит на вопрос, выполнение функции ОтветНаВопрос возобновляется, происходит возврат из этой функции. Ожидание в строке 5 завершается, выводится сообщение с выбранным пользователем вариантом.
Поэтому отладчик показывает, что при выполнении шага со строки 11 указатель текущей строки оказывается на строке 6.
Чтобы разработчику было проще разобраться с порядком вызовов асинхронных функций, в инструменте Стек вызовов цветом выделяются строки. После возобновления асинхронной функции (текущая строка – 11) черным цветом будет выделен стек, выполняющийся в данный момент, а серым – стек, из которого была вызвана асинхронная функция:

Может сложиться ситуация, что исходная процедура, которая вызвала асинхронную функцию, к моменту возобновления работы функции уже завершила свое выполнение. Рассмотрим пример:
1 &НаКлиенте 2 Асинх Процедура ВывестиОтветНаВопрос(Команда) 3 ОтветНаВопрос(); 4 КонецПроцедуры 5 6 &НаКлиенте 7 Асинх Функция ОтветНаВопрос() 8 Ответ = Ждать ВопросАсинх("Выполнить загрузку?", РежимДиалогаВопрос.ДаНет); 9 Сообщить("Пользователь выбрал вариант"); 10 Сообщить(Ответ); 11 КонецФункции
Если в отладчике выполнить остановку на строке 11 и сделать шаг (нажатием F10 или F11), то произойдет выход из отладки, в строку 4 выполнение кода не попадет. Почему? Дело в том, что в строке 8 используется оператор Ждать, поэтому происходит приостановка выполнения функции ОтветНаВопрос, управление возвращается к вызывающему коду – процедуре ВывестиОтветНаВопрос. В строке 4 происходит завершение работы процедуры.
Когда пользователь ответит на вопрос, произойдет возобновление выполнения функции в строке 8, будут выведены сообщения из строк 9 и 10. При выходе из функции ОтветНаВопрос в строке 11 возвращаться уже некуда, поскольку вызвавший эту функцию код уже отработал. Но тем не менее в стеке вызовов этот код будет отображаться серым цветом:

Использование фоновых заданий для выполнения длительных операций
Ранее отмечалось, что асинхронность в платформе “1С:Предприятие” появилась из-за требований, предъявляемых браузерами. Например, существуют ограничения на время выполнения скриптов, чтобы предотвратить блокировку интерфейса браузера.
В типовых конфигурациях есть длительные процедуры, такие, как заполнение документов по данным информационной базы, при этом во время заполнения пользователь может работать с другими объектами – формировать отчеты, создавать документы. Рассмотрим, как организовано такое выполнение операций, не блокирующее работу пользователей. И самое главное – выясним, как оно связано с асинхронностью.
Для примера рассмотрим типовую конфигурацию “Бухгалтерия предприятия”. В документе СчетФактураВыданный есть форма ФормаДокументаКорректировочнаяСправка. В этой форме реализован обработчик команды Заполнить. Поскольку заполнение документа может занять длительное время, эта операция выполняется с помощью фонового задания.
Код, заполняющий документ, располагается в модуле менеджера документа, в процедуре ПодготовитьДанныеДляЗаполненияКорректировочнойСправки. В этой процедуре данные из базы, необходимые для заполнения документа, помещаются во временное хранилище.
Запускается фоновое задание, исполняющее этот код. Отображается общая форма с анимированной картинкой:

Если за определенное время фоновое задание не завершилось, в форме документа подключается обработчик ожидания, который периодически проверяет состояние фонового задания. Когда фоновое задание успешно завершено, из временного хранилища извлекаются данные, помещаются в табличную часть документа.
Для доступа из формы к результату заполнения документа, помещенному в фоновом задании во временное хранилище, используется реквизит формы АдресХранилища. Чтобы из клиентского приложения можно было проверить состояние запущенного фонового задания (завершено успешно, еще выполняется или завершено с ошибкой), его идентификатор также хранится в реквизите формы ИдентификаторЗадания:

Приведем фрагменты программного кода модуля формы, которые имеют отношение к фоновому заполнению документа. При помощи комментариев отметим назначение отдельных процедур или переменных.
//Клиентские переменные для управления формой длительной операции &НаКлиенте Перем ПараметрыОбработчикаОжидания; &НаКлиенте Перем ФормаДлительнойОперации; //Обработчик команды Заполнить &НаКлиенте Процедура Заполнить(Команда) Если Объект.ДокументыОснования.Количество() > 0 Тогда Если Объект.Проведен Тогда ТекстВопроса = НСтр("ru = 'Перед заполнением проведение документа будет отменено, а список документов-основанией будет очищен. Заполнить?'"); Иначе ТекстВопроса = НСтр("ru = 'Список документов-основанией будет очищен. Заполнить?'"); КонецЕсли; Оповещение = Новый ОписаниеОповещения("ВопросЗаполнитьДокументЗавершение", ЭтотОбъект); ПоказатьВопрос(Оповещение, ТекстВопроса, РежимДиалогаВопрос.ДаНет); Иначе ЗаполнитьДокумент(); КонецЕсли; КонецПроцедуры //Метод обратного вызова, выполняемый после того, как пользователь ответит на вопрос &НаКлиенте Процедура ВопросЗаполнитьДокументЗавершение(Результат, ДополнительныеПараметры) Экспорт Если Результат = КодВозвратаДиалога.Да Тогда ЗаполнитьДокумент(); КонецЕсли; КонецПроцедуры //Подключение обработчика ожидания для проверки выполнения фонового задания &НаКлиенте Процедура ЗаполнитьДокумент() Результат = ЗаполнитьДокументНаСервере(); Если Результат.ЗаданиеВыполнено Тогда ОповеститьОбИзменении(Объект.Ссылка); Иначе ИдентификаторЗадания = Результат.ИдентификаторЗадания; АдресХранилища = Результат.АдресХранилища; ДлительныеОперацииКлиент.ИнициализироватьПараметрыОбработчикаОжидания(ПараметрыОбработчикаОжидания); ПодключитьОбработчикОжидания("Подключаемый_ПроверитьВыполнениеЗадания", 1, Истина); ФормаДлительнойОперации = ДлительныеОперацииКлиент.ОткрытьФормуДлительнойОперации(ЭтаФорма, ИдентификаторЗадания); КонецЕсли; КонецПроцедуры //Серверный метод, запускающий фоновое задание &НаСервере Функция ЗаполнитьДокументНаСервере() Если Объект.Проведен Тогда Записать(Новый Структура("РежимЗаписи", РежимЗаписиДокумента.ОтменаПроведения)); КонецЕсли; Объект.ДокументыОснования.Очистить(); СтруктураПараметров = Новый Структура("Ссылка, Организация, Дата, ИсправляемыйСчетФактура", Объект.Ссылка, Объект.Организация, Объект.Дата, Объект.ИсправляемыйСчетФактура); Если ОбщегоНазначения.ИнформационнаяБазаФайловая() Тогда АдресХранилища = ПоместитьВоВременноеХранилище(Неопределено, УникальныйИдентификатор); Документы.СчетФактураВыданный.ПодготовитьДанныеДляЗаполненияКорректировочнойСправки(СтруктураПараметров, АдресХранилища); Результат = Новый Структура("ЗаданиеВыполнено", Истина); Иначе НаименованиеЗадания = НСтр("ru = 'Заполнение документа ""Корректировочная справка по розничным продажам""'"); Результат = ДлительныеОперации.ЗапуститьВыполнениеВФоне( УникальныйИдентификатор, "Документы.СчетФактураВыданный.ПодготовитьДанныеДляЗаполненияКорректировочнойСправки", СтруктураПараметров, НаименованиеЗадания); АдресХранилища = Результат.АдресХранилища; КонецЕсли; Если Результат.ЗаданиеВыполнено Тогда ЗагрузитьПодготовленныеДанные(); КонецЕсли; Возврат Результат; КонецФункции //Проверка выполнения задания по его идентификатору &НаСервереБезКонтекста Функция ЗаданиеВыполнено(ИдентификаторЗадания) Возврат ДлительныеОперации.ЗаданиеВыполнено(ИдентификаторЗадания); КонецФункции //Проверка, нужно ли закрыть форму длительной операции. Установка интервала ожидания &НаКлиенте Процедура Подключаемый_ПроверитьВыполнениеЗадания() Попытка Если ФормаДлительнойОперации.Открыта() И ФормаДлительнойОперации.ИдентификаторЗадания = ИдентификаторЗадания Тогда Если ЗаданиеВыполнено(ИдентификаторЗадания) Тогда ЗагрузитьПодготовленныеДанные(); ДлительныеОперацииКлиент.ЗакрытьФормуДлительнойОперации(ФормаДлительнойОперации); Иначе ДлительныеОперацииКлиент.ОбновитьПараметрыОбработчикаОжидания(ПараметрыОбработчикаОжидания); ПодключитьОбработчикОжидания( "Подключаемый_ПроверитьВыполнениеЗадания", ПараметрыОбработчикаОжидания.ТекущийИнтервал, Истина); КонецЕсли; КонецЕсли; Исключение ДлительныеОперацииКлиент.ЗакрытьФормуДлительнойОперации(ФормаДлительнойОперации); ВызватьИсключение; КонецПопытки; КонецПроцедуры //Получение данных из временного хранилища, загрузка в табличную часть документа &НаСервере Процедура ЗагрузитьПодготовленныеДанные() СтруктураДанных = ПолучитьИзВременногоХранилища(АдресХранилища); Если ТипЗнч(СтруктураДанных) > Тип("Структура") Тогда Возврат; КонецЕсли; Если СтруктураДанных.Свойство("ДокументыОснования") Тогда Объект.ДокументыОснования.Загрузить(СтруктураДанных.ДокументыОснования); КонецЕсли; //работа с формой. Модифицированность = Истина; КонецПроцедуры
Подобный код, выполняющий длительные операции в фоновом задании, можно встретить во всех конфигурациях на базе БСП – Управление торговлей 11, Комплексная автоматизация 2, Зарплата и управление персоналом 3 и другие.
Также в демонстрационной базе БСП можно ознакомиться, как работает этот механизм. Для демонстрации возможностей подсистемы в демо-базе реализована обработка _ДемоДлительнаяОперация.
Обратите внимание, что данный программный код значительно отличается от рассмотренного ранее асинхронного кода.
В коде не используются асинхронные процедуры, имя которых начинается с Показать или Начать, также нет функций с модификатором Асинх.
Запуск фонового задания производится на сервере, в то время, как асинхронные методы, разбираемые в предыдущих главах, являются клиентскими. Вообще работа с фоновыми заданиями производится только на сервере. Например, проверка того, выполнено ли задание, выполняется в серверной функции.
Для работы фонового задания создается отдельный сеанс, в котором и выполняется длительная операция. При вызове асинхронных методов отдельный сеанс не создается, все действия выполняются в одном сеансе. В целом асинхронность в платформе не является реализацией многопоточности.
При вызове асинхронной процедуры в описании оповещения указывается, какую процедуру необходимо выполнить при завершении работы асинхронного метода. При использовании оператора Ждать происходит приостановка выполнения метода, после завершения работы асинхронной функции выполняется возобновление приостановленного метода. Таким образом, из программного кода не нужно проверять, отработал ли уже асинхронный метод. Платформа самостоятельно выполнит нужное действие – передаст управление методу обратного вызова или возобновит приостановленный метод.
При выполнении длительных действий в фоновом задании нет возможности получить “уведомление” от сервера, что фоновое задание завершено, чтобы получить подготовленные данные и загрузить их в табличную часть документа. Поэтому приходится подключать обработчик ожидания, который с заданной периодичностью проверяет состояние фонового задания с определенным идентификатором. Как только такая проверка определяет, что фоновое задание завершилось, можно приступать к обработке данных, помещенных во временное хранилище в фоновом задании.
Таким образом, длительные операции с использованием фоновых заданий и асинхронные методы – это принципиально разные механизмы. Правда на практике достаточно часто термин “асинхронное выполнение” применяется и для способа с фоновыми заданиями, поскольку при этом не блокируется интерфейс приложения. Поэтому обязательно нужно уточнять, какая именно асинхронность имеется в виду.
Заключение
Итак, в данной статье продемонстрировано, что современные браузеры диктуют требования, из-за которых фирме “1С” пришлось добавить асинхронные методы в платформу “1С:Предприятие”.
Отличие асинхронного метода от синхронного заключается в том, что асинхронный метод не блокирует основной поток выполнения кода. После вызова асинхронного метода управление вернется к коду, вызвавшему этот метод, до того, как сам асинхронный метод завершит свою работу.
Существует 2 варианта асинхронных методов – с использованием методов обратного вызова и с использованием нового типа данных Обещание и оператора Ждать, которые появились в платформе 8.3.18.
Использование асинхронных процедур с методами обратного вызова приводит к тому, что код становится фрагментированным. Это усложняет разработку кода.
Поэтому фирма “1С” упростила работу разработчикам, добавив в платформу 8.3.18 асинхронные функции, возвращающие Обещания. Использование таких методов делает код более понятным, простым для анализа и модификации – похожим на код с использованием аналогичных синхронных методов.
В феврале 2021 года использование платформы 8.3.18 для работы с типовыми конфигурациями не является обязательным. Поэтому новые асинхронные функции, возвращающие Обещания, еще нельзя встретить в коде. Ожидается, что в скором времени этот функционал будет использоваться повсеместно – начиная с БСП, откуда он попадет во все типовые конфигурации, базирующихся на БСП.
Сейчас как раз есть время подготовиться, изучить новый функционал, разобраться с асинхронностью в платформе “1С:Предприятие”. Когда в БСП и в типовых начнут применяться асинхронные функции – это не будет неожиданностью. К тому же с кодом станет работать удобнее, проще, разработчики смогут сконцентрироваться на реализации алгоритмов, а не на преодолении сложностей встроенного языка.
База с приведенными в статье фрагментами кода
Хотите еще?
Кроме этой статьи мы опубликовали несколько видео-уроков по использованию асинхронных методов «Как выводить сообщения пользователю без километров кода» от Сергея Калинкина (автор курса «Интерфейсы и формы»).
33 минуты, рекомендуем к просмотру 🙂
Не пропустите новый курс (по специальной цене для первого потока)!
Коллеги, запускаем новый курс по EDT и Git, первый курс в серии курсов по DevOps.
Если Вы тимлид / руководитель отдела разработки / работаете в команде из 5+ человек над одним проектом / планируете перейти в ведущие франчайзи не простым разработчиком – рекомендуем внимательно его посмотреть:
