Как реализовать рекурсивный обход каталога на клиенте без использования модальности в 1С?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

Переход платформы 1С:Предприятие на асинхронную модель работы и постепенный отказ от модальности принесли разработчикам немало трудностей. Одной из классических проблем стал рекурсивный обход каталогов на стороне клиента. Попытка использовать привычные синхронные методы, такие как ЭтоКаталог() или НайтиФайлы() в клиентском коде при включенном режиме запрета модальных вызовов, приводит к ошибке выполнения (Run-time error).

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

Выясняем причину: почему ломается привычная рекурсия?

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


Для Каждого Файл Из НайденныеФайлы Цикл
    Файл.НачатьПроверкуЭтоКаталог(ОписаниеОповещения);
КонецЦикла;

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

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

Решение №1: Современный подход через «Асинх / Ждать» (для платформы 8.3.18 и выше)

Если ваша конфигурация работает на платформе 1С:Предприятие версии 8.3.18 или выше, вам доступен наиболее элегантный и лаконичный механизм — асинхронные функции. Он позволяет писать асинхронный код в привычном синхронном (линейном) стиле без использования громоздких обратных вызовов (колбэков).

Разберем по шагам, как реализовать рекурсивный обход с использованием оператора Ждать и директивы Асинх:


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

&НаКлиенте
Процедура ПоказатьЛогОбработки(ИмяФайла)
    Сообщить("Обработан файл: " + ИмяФайла);
КонецПроцедуры

Почему это работает: Оператор Ждать приостанавливает выполнение текущей процедуры до завершения дисковой операции, при этом интерфейс программы и другие фоновые процессы 1С не «зависают». Это позволяет сохранить стек вызовов рекурсии в первозданном виде.

Решение №2: Эмуляция рекурсии через «ОписаниеОповещения» (для старых платформ)

Если вам приходится работать на версиях платформы ниже 8.3.18, использовать конструкции Асинх / Ждать не получится. В этом случае обычный цикл Для Каждого использовать категорически нельзя. Нам придется реализовать так называемый «конечный автомат» — эмулировать цикл и рекурсию через цепочку последовательных оповещений, передавая состояние обхода в структуру ДополнительныеПараметры.

Проанализируем пример реализации такого обхода:


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

&НаКлиенте
Процедура ПослеПоискаФайлов(НайденныеФайлы, ПараметрыОбхода) Экспорт
    
    ПараметрыОбхода.НайденныеФайлы = НайденныеФайлы;
    ПараметрыОбхода.ТекущийИндекс = 0;
    
    // Запускаем пошаговую обработку полученного списка
    ОбработатьСледующийФайл(ПараметрыОбхода);
    
КонецПроцедуры

&НаКлиенте
Процедура ОбработатьСледующийФайл(ПараметрыОбхода)
    
    // Проверяем, закончились ли файлы на текущем уровне обхода
    Если ПараметрыОбхода.ТекущийИндекс >= ПараметрыОбхода.НайденныеФайлы.Количество() Тогда
        
        // Файлы текущего каталога обработаны. Проверяем, есть ли куда вернуться (в стек)
        Если ПараметрыОбхода.СтекКаталогов.Количество() > 0 Тогда
            
            // Извлекаем состояние предыдущего (родительского) каталога из стека
            ИндексПоследнего = ПараметрыОбхода.СтекКаталогов.Количество() - 1;
            ПредыдущееСостояние = ПараметрыОбхода.СтекКаталогов[ИндексПоследнего];
            ПараметрыОбхода.СтекКаталогов.Удалить(ИндексПоследнего);
            
            // Восстанавливаем состояние обхода родительского уровня
            ПараметрыОбхода.НайденныеФайлы = ПредыдущееСостояние.НайденныеФайлы;
            ПараметрыОбхода.ТекущийИндекс = ПредыдущееСостояние.ТекущийИндекс + 1; // Сдвигаемся на следующий элемент
            
            // Продолжаем обход родительского уровня
            ОбработатьСледующийФайл(ПараметрыОбхода);
            
        Иначе
            // Стек пуст и файлы обработаны — обход полностью завершен
            ПоказатьПредупреждение(, "Обход дерева каталогов успешно завершен!");
        КонецЕсли;
        Возврат;
        
    КонецЕсли;
    
    // Получаем текущий файл для анализа
    ТекущийФайл = ПараметрыОбхода.НайденныеФайлы[ПараметрыОбхода.ТекущийИндекс];
    
    // Проверяем тип объекта (файл или каталог) асинхронно
    Оповещение = Новый ОписаниеОповещения("ПослеПроверкиКаталога", ЭтотОбъект, ПараметрыОбхода);
    ТекущийФайл.НачатьПроверкуЭтоКаталог(Оповещение);
    
КонецПроцедуры

&НаКлиенте
Процедура ПослеПроверкиКаталога(ЭтоКаталог, ПараметрыОбхода) Экспорт
    
    ТекущийФайл = ПараметрыОбхода.НайденныеФайлы[ПараметрыОбхода.ТекущийИндекс];
    
    Если ЭтоКаталог Тогда
        
        // Сохраняем текущее состояние (список файлов и текущую позицию) в стек перед уходом «вглубь»
        ТекущееСостояние = Новый Структура;
        ТекущееСостояние.Вставить("НайденныеФайлы", ПараметрыОбхода.НайденныеФайлы);
        ТекущееСостояние.Вставить("ТекущийИндекс", ПараметрыОбхода.ТекущийИндекс);
        ПараметрыОбхода.СтекКаталогов.Добавить(ТекущееСостояние);
        
        // Запускаем асинхронный поиск файлов во вложенном каталоге
        Оповещение = Новый ОписаниеОповещения("ПослеПоискаФайлов", ЭтотОбъект, ПараметрыОбхода);
        НачатьНахождениеФайлов(Оповещение, ТекущийФайл.ПолноеИмя, "*.*", Ложь);
        
    Иначе
        
        // Выполняем полезную работу с файлом
        Сообщить("Обработан файл: " + ТекущийФайл.Имя);
        
        // Переходим к следующему элементу текущего уровня
        ПараметрыОбхода.ТекущийИндекс = ПараметрыОбхода.ТекущийИндекс + 1;
        ОбработатьСледующийФайл(ПараметрыОбхода);
        
    КонецЕсли;
    
КонецПроцедуры

Такой подход выглядит сложным и громоздким, однако это единственный «железобетонный» способ реализовать честную рекурсию на клиенте в старых версиях платформы 1С без вызова модальных окон.

Решение №3: Перенос обхода на сторону сервера (&НаСервере)

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

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


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

Критический анализ: почему нельзя проверять каталог «по расширению»?

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

  1. Кроссплатформенность под угрозой: В операционных системах семейства Linux и macOS файлы без расширения — это стандартная практика (например, исполняемые файлы, скрипты конфигурации, служебные файлы баз данных). Использование алгоритма «пустое расширение = каталог» на клиентах под управлением Linux приведет к фатальным ошибкам или потере важных данных.
  2. Точки в именах папок: Имена каталогов нередко содержат точки. Например, папка проекта может называться project.v1 или .git. Система разбора строк посчитает части «v1» или «git» расширениями и ошибочно распознает папку как обычный файл, пропустив все её содержимое при обходе.
  3. Стандарты разработки 1С: Официальные стандарты разработки фирмы «1С» строго требуют использовать специализированные системные методы (ЭтоКаталог()) для определения типов объектов файловой системы. Использование строковых манипуляций вместо явной проверки свойств объектов является грубой ошибкой.

Резюме

Мы проанализировали проблему асинхронного рекурсивного обхода каталогов на клиенте и рассмотрели три основных сценария ее решения. Выбор конкретного метода зависит от технических условий вашего проекта:

← На главную