При переводе конфигураций 1С на работу в веб-клиенте программисты часто сталкиваются с жестким ограничением: использование синхронных вызовов (например, Файл.Существует(), Вопрос(), Предупреждение()) становится невозможным. Браузеры блокируют такие операции, требуя асинхронного подхода. Особую сложность представляет ситуация, когда синхронный вызов находится внутри цикла, например, при поиске уникального имени файла путем перебора индексов.
В этой статье мы подробно разберем, как преобразовать привычный цикл Пока в асинхронную модель, используя два подхода: классическую рекурсию через оповещения и современный синтаксис Async/Await, появившийся в платформе 8.3.18.
Проанализируем стандартную ситуацию. Допустим, у нас есть задача: проверить наличие файла на диске, и если он существует, добавить к имени индекс (1, 2, 3...) до тех пор, пока не найдем свободное имя. В синхронном коде это выглядит так:
Пока НЕ Успешно И ЦиклНомер < 100 Цикл
ФайлПопытки = ПолныйПодКаталог + ИмяФайла;
ФайлНаДиске = Новый Файл(ФайлПопытки);
Если НЕ ФайлНаДиске.Существует() Тогда
ИтоговыйПуть = ПодКаталог + ИмяФайла;
Успешно = Истина;
КонецЕсли;
ЦиклНомер = ЦиклНомер + 1;
КонецЦикла;
В веб-клиенте метод Существует() вызовет ошибку. Если же мы заменим его на асинхронный НачатьПроверкуСуществования(), то цикл не будет ждать ответа от файловой системы. Он мгновенно «прокрутится» 100 раз, запустив 100 параллельных проверок, и завершит выполнение функции, так и не получив результат. Выясним, как это исправить.
Если ваша конфигурация работает на платформе ниже 8.3.18 или используется режим совместимости, единственным надежным способом будет превращение цикла в цепочку процедур. Мы создаем основную процедуру («вход») и процедуру обратного вызова («завершение»), которая будет вызывать сама себя до тех пор, пока условие не будет выполнено.
Рассмотрим по шагам реализацию этого механизма:
ДополнительныеПараметры.Пример реализации такой логики:
&НаКлиенте
Процедура ПолучитьУникальноеИмяАсинхронно(ИмяФайла, ОповещениеОЗавершении) Экспорт
Файл = Новый Файл(ИмяФайла);
// Сохраняем все состояние цикла в структуру
ПараметрыЦикла = Новый Структура;
ПараметрыЦикла.Вставить("ОповещениеОЗавершении", ОповещениеОЗавершении);
ПараметрыЦикла.Вставить("Счетчик", 0);
ПараметрыЦикла.Вставить("Путь", Файл.Путь);
ПараметрыЦикла.Вставить("ИмяБезРасширения", Файл.ИмяБезРасширения);
ПараметрыЦикла.Вставить("Расширение", Файл.Расширение);
// Запускаем первую итерацию "цикла"
ВыполнитьИтерациюПроверки(Истина, ПараметрыЦикла);
КонецПроцедуры
&НаКлиенте
Процедура ВыполнитьИтерациюПроверки(ФайлСуществует, ПараметрыЦикла) Экспорт
Если ФайлСуществует Тогда
ПараметрыЦикла.Счетчик = ПараметрыЦикла.Счетчик + 1;
// Проверка условия выхода (ограничение итераций)
Если ПараметрыЦикла.Счетчик > 100 Тогда
ВыполнитьОбработкуОповещения(ПараметрыЦикла.ОповещениеОЗавершении, Неопределено);
Возврат;
КонецЕсли;
// Формируем новое имя для проверки
НовоеИмя = ПараметрыЦикла.Путь + ПараметрыЦикла.ИмяБезРасширения
+ "_" + ПараметрыЦикла.Счетчик + ПараметрыЦикла.Расширение;
// Создаем оповещение, указывающее на ЭТУ ЖЕ процедуру
Оповещение = Новый ОписаниеОповещения("ВыполнитьИтерациюПроверки", ЭтотОбъект, ПараметрыЦикла);
Файл = Новый Файл(НовоеИмя);
ПараметрыЦикла.Вставить("ТекущееИмя", НовоеИмя);
Файл.НачатьПроверкуСуществования(Оповещение);
Иначе
// Успех: файл не существует, возвращаем найденное имя
ВыполнитьОбработкуОповещения(ПараметрыЦикла.ОповещениеОЗавершении, ПараметрыЦикла.ТекущееИмя);
КонецЕсли;
КонецПроцедуры
Важно понимать: в данном примере процедура ВыполнитьИтерациюПроверки имитирует поведение цикла Пока. Мы не переходим к следующему шагу, пока операционная система не вернет ответ о существовании файла.
Если вы имеете возможность использовать последние релизы платформы, задача упрощается в разы. Разберем, как проанализировать ситуацию с помощью новых ключевых слов. В этом случае код практически не отличается от синхронного, но платформа берет на себя управление ожиданием.
Для реализации нам понадобятся:
Асинх перед объявлением функции.Ждать перед вызовом асинхронного метода.СуществуетАсинх()).Посмотрим на пример обновленного кода:
&НаКлиенте
Асинх Функция ПолучитьУникальноеИмяАсинх(ПолныйПуть)
ЦиклНомер = 0;
Успешно = Ложь;
ИтоговыйПуть = "";
ФайлОбъект = Новый Файл(ПолныйПуть);
ПутьККаталогу = ФайлОбъект.Путь;
БазовоеИмя = ФайлОбъект.ИмяБезРасширения;
Расширение = ФайлОбъект.Расширение;
Пока НЕ Успешно И ЦиклНомер < 100 Цикл
ТекущееИмя = ПутьККаталогу + БазовоеИмя + "_" + ЦиклНомер + Расширение;
ФайлНаДиске = Новый Файл(ТекущееИмя);
// Платформа "замирает" здесь до получения результата, не блокируя интерфейс
Если НЕ Ждать ФайлНаДиске.СуществуетАсинх() Тогда
ИтоговыйПуть = ТекущееИмя;
Успешно = Истина;
КонецЕсли;
ЦиклНомер = ЦиклНомер + 1;
КонецЦикла;
Возврат ИтоговыйПуть;
КонецФункции
Преимущество этого метода: Код остается линейным и легко читаемым. Вам не нужно создавать цепочки процедур и передавать структуру параметров.
При использовании асинхронных методов работы с файлами часто возникает специфическая ошибка: "File find is not opened". Проанализируем причины ее появления:
НачатьПодключениеРасширенияРаботыСФайлами().Рассмотрим ситуацию: если нам нужно проверить 100 вариантов имен, делать 100 асинхронных вызовов (даже через Ждать) — это долго, так как каждый вызов создает задержку на переключение контекста. Выясним, как это оптимизировать.
Вместо проверки каждого файла по отдельности, мы можем один раз получить список всех файлов в каталоге, подходящих под маску, а затем в обычном синхронном цикле проверить наличие имени в полученном массиве. Это будет работать в тысячи раз быстрее.
&НаКлиенте
Асинх Функция ПолучитьУникальноеИмяОптимизировано(Путь)
ФайлОбъект = Новый Файл(Путь);
// Получаем список всех похожих файлов одним запросом
Маска = ФайлОбъект.ИмяБезРасширения + "_*" + ФайлОбъект.Расширение;
НайденныеФайлы = Ждать НайтиФайлыАсинх(ФайлОбъект.Путь, Маска);
// Переносим имена в соответствие для быстрого поиска в памяти
СуществующиеИмена = Новый Соответствие;
Для Каждого НайденныйФайл Из НайденныеФайлы Цикл
СуществующиеИмена.Вставить(НайденныйФайл.ПолноеИмя, Истина);
КонецЦикла;
// Теперь цикл проверки имен будет синхронным и мгновенным
Для Счетчик = 1 По 100 Цикл
ПроверяемоеИмя = ФайлОбъект.Путь + ФайлОбъект.ИмяБезРасширения + "_" + Счетчик + ФайлОбъект.Расширение;
Если СуществующиеИмена[ПроверяемоеИмя] = Неопределено Тогда
Возврат ПроверяемоеИмя;
КонецЕсли;
КонецЦикла;
КонецФункции
Подведем итог: использование асинхронности — это не просто замена методов, а изменение логики построения программы. Для старых версий платформы используйте рекурсию, для новых — Async/Await, а для повышения производительности старайтесь минимизировать количество асинхронных вызовов, заменяя их групповыми операциями.