...

Использование *[Symbol.iterator] для получения большого количества элементов

Тема в разделе "Примеры сценариев", создана пользователем bashorin, 23 янв 2022.

  1. bashorin

    bashorin New Member

    Цель:
    Получить из приложения более 100 тысяч записей.

    Пробовал использовать:

    Код:
    
    const CHUNK_SIZE 1000;

    const list = 
    getReallyBigList();
    let promissesPromise<void>[] = [];
    for (const 
    item of list) {
      if (
    promisses.length >= CHUNK_SIZE) {
        
    await Promise.all(promisses);
        
    promisses = [];
      }
      
    promises.push(item);
    }
    await Promise.all(promisses);



    Но в моем случае вариант с использованием *[Symbol.iterator] оказался чуть более быстрым.

    Данный сценарий позволил мне точно обрабатывать от 60 до 120 тысяч элементов.

    Пример:
    Код:
    
    async function getAllData(): Promise<void> {

        const 
    appSearch = Global.ns._clients.app._opportunities.search()
            .
    where((fg) => g.and(
                
    f.__deletedAt.eq(null),
            ))
        
    let numberOfFilteredElements await appSearch.count();
        
    let rangeElements = {
            
    from0,
            
    tonumberOfFilteredElements,
            *[
    Symbol.iterator]() {
                for (
    let value this.fromvalue <= this.to;) {
                    yield 
    appSearch.from(value).size(1000).all();
                    
    value value 1000;
                }
            }
        }
        const 
    itemsPromises = [...rangeElements];
        const 
    result await Promise.all(itemsPromises);
        const 
    result__red result.reduce((acc: [], val: []) => acc.concat(val), []);


    Результатом выполнения кода будет выгрузка всех данных из приложения с использованием фильтра.

    Шаги выполнения сценария:

    1. Создадим фильтр appSearch для элементов приложения Лиды из системного раздела CRM. Условия фильтрации (поиска) указаны в параметрах метода .where(). В данном случае использовано одно условие для фильтрации: элемент не должен быть удален (f.__deletedAt.eq(null)).

    2. Затем с помощью метода .count() получим общее количество элементов приложения, соответствующих условиям фильтра, и запишем его в переменную numberOfFilteredElements.

    3. Создадим пользовательский итерируемый объект rangeElements, в котором укажем начало: первый элемент массива (from: 0) и конец: общее количество искомых элементов (to: numberOfFilteredElements).

    4. Зададим функцию-генератор *[Symbol.iterator](), которая пройдется циклом по всем отобранным элементам, частями по 5000 штук
      выполняя yield (чем меньше в одной итерации, тем дольше обрабатывает, поэтому тут либо вводить какие-то условия, либо заранее знать сколько примерно будет элементов; для приложения с 200,000 элементов я беру в одной итерации 10,000).
    5. На выходе мы получим массив «обещаний» (промисов) itemsPromises.

    6. При помощи оператора await и метода Promise.all() получим массив результатов выполнения этих «обещаний» и запишем его в переменную result. Это и будут искомые элементы приложения.

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

    Код:
    
    yield appSearch.from(value).size(1000).all().then(=> e.map(=> i.data._contacts));

    value value 1000;


    Подробнее о генераторах можно прочитать в статьях:
    https://learn.javascript.ru/generators-iterators

    О методе Symbol.iterator:
    https://learn.javascript.ru/iterable

    О типе данных Symbol:
    https://learn.javascript.ru/symbol

    Как можно улучшить:
    Скорость обработки маленькая при минимальных фильтрах в search. На скрине ниже пример запроса к 200,000+ элементов с один фильтром. В итоге получил 116,895 элементов уже в Promise.all(), то есть мне доступны все значения полей элементов приложения.

    [​IMG]


    В моем случае, мне нужно собирать с такого количества элементов информацию и что-то с ней делать при каждом запросе со стороннего ресурса и отдавать результат. Пока что нашел выход в использовании Namespace.storage: храню полученные значения и обновляю раз в день. А после делаю проверку на время запроса. Если запрос был меньше, чем нужно, то верни из storage, иначе делай расчеты.

    Но если есть какие-то параметры, которые изменяют результат запроса по слайдеру (например, ограничение по полю сумма в элемене), то придется записывать не только весь результат, но и промежуточный для каждого вычисления.