FetchListV3.make

Возвращает AsyncGenerator, позволяющий обрабатывать данные из list-методов REST API Bitrix24 версии 3 по мере поступления, не загружая весь массив в память сразу. Особенно полезно при работе с очень большими объёмами данных.

Обзор

Когда нужно обработать большие объёмы данных из list-методов REST API версии 3 по частям (чанками), используйте FetchListV3.make().

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

// Базовое использование
const generator = $b24.actions.v3.fetchList.make({
  method: 'main.eventlog.list',
  params: {
    filter: [
      ['userId', '=', 1]
    ],
    select: ['id', 'userId']
  },
  idKey: 'id',
  customKeyForResult: 'items',
  requestId: 'unique-request-id',
  limit: 600
})

for await (const chunk of generator) {
  // Обработка чанка (например, сохранение в БД, анализ и т.д.)
  console.log(`Processing ${chunk.length} items`)
}

Когда использовать FetchListV3.make()

  1. Очень большие объёмы данных: когда число записей в тысячах или десятках тысяч.
  2. Потоковая обработка: когда данные нужно обрабатывать по мере поступления.
  3. Длительные операции: когда обработка каждой записи требует значительного времени.

Сигнатура метода

make<T = unknown>(
  options: ActionFetchListV3
): AsyncGenerator<T[]>

Параметры

Объект options содержит следующие свойства:

ПараметрТипОбязательныйОписание
methodstringДаИмя метода REST API, возвращающего список данных (например, crm.contact.list, tasks.task.list).
paramsOmit<TypeCallParams, 'pagination' | 'order'>НетПараметры запроса без pagination и order. Параметр pagination зарезервирован, так как метод получает все данные одним вызовом. Параметр order зарезервирован, так как курсорной пагинации нужна строгая сортировка по cursorIdKey (по умолчанию равен idKey) по возрастанию — см. Ограничения. Для сужения выборки используйте filter и select.
idKeystringНетИмя поля id так, как оно приходит в каждом элементе ответа; его значение управляет курсором. По умолчанию: 'id'. Задайте под то поле id, которое возвращает метод.
cursorIdKeystringНетИмя поля, используемое в запросе для order и постраничного фильтра [field, '>', n]. По умолчанию равно idKey. Задавайте, только когда имя сортируемого / фильтруемого поля отличается от поля в ответе (например, поле запроса в верхнем регистре, а id в ответе в нижнем): передайте idKey: 'id', cursorIdKey: 'ID'.
customKeyForResultstringДаКлюч, по которому в ответе REST API будет выбран массив данных. Например: items для списка элементов CRM.
requestIdstringНетУникальный идентификатор запроса для отслеживания. Используется для дедупликации запросов и отладки.
limitnumberНетСколько записей получать за раз. По умолчанию 50. Максимум 1000.

Возвращаемое значение

AsyncGenerator<T[]> — асинхронный генератор, возвращающий чанки данных в виде массивов типа T.

Каждая итерация генератора возвращает:

  • Массив элементов (чанк) до options.limit записей.
  • Генератор завершается, когда все данные получены.

Ключевые понятия

AsyncGenerator vs Promise

В отличие от CallListV3.make(), который возвращает Promise со всеми данными сразу, FetchListV3.make() возвращает асинхронный генератор:

АспектCallListV3.make()FetchListV3.make()
Возвращаемое значениеPromise<Result<T[]>>AsyncGenerator<T[]>
ПамятьЗагружает все данные в памятьОбрабатывает данные по частям
Использованиеawait response.getData()for await (const chunk of generator)
Подходит дляМалые и средние объёмы данныхОчень большие объёмы данных

Оптимизация производительности

Метод реализует рекомендованный Bitrix24 алгоритм для эффективной работы с большими объёмами данных:

  1. Фильтрация по возрастающему id: каждый следующий запрос использует фильтр [cursorIdKey, '>', id] с id последнего полученного элемента (читается через idKey).
  2. Потоковая обработка: данные обрабатываются по мере поступления, экономя память.
  3. Автоматическое определение конца данных: запросы останавливаются при получении пустого массива или когда страница короче самой большой из виденных. Это привязывает остановку к размеру страницы, который реально возвращает сервер, а не к запрошенному options.limit, поэтому метод, молча ограничивающий страницу ниже limit (например, tasks.task.list, зафиксирован на 50), всё равно проходит все записи, а не останавливается после первой урезанной страницы.

Структура ответа REST API v3

Параметр customKeyForResult сохранён для обратной совместимости. В будущем после анализа реального использования будет принято решение о его необходимости.

В REST API версии 3 разные методы возвращают данные в одинаковых структурах:

  • Сгруппированный массив: { result: { items: [...] } }

Параметр customKeyForResult позволяет указать ключ, по которому расположены данные в ответе.

Некоторые v3 list-методы (например, note.*) также возвращают в конверте ответа поле nextCursor рядом с items{ result: { nextCursor, items: [...] } }. Читать или передавать его не нужно: FetchListV3.make() листает собственным курсором по idKey (внедрённый фильтр [idField, '>', n]) и проходит все страницы независимо от nextCursor. Поле информационное; SDK его игнорирует.

Ограничения

  • Размер страницы: ограничение Bitrix24 REST API версии 3 — максимум 1000 записей на запрос.
  • Сортировка фиксирована: метод всегда сортирует по cursorIdKey (по умолчанию равен idKey) по возрастанию, потому что курсорная пагинация опирается на фильтры [cursorIdKey, '>', nextId] для прохождения по набору. Пользовательское значение order нарушило бы этот инвариант, поэтому сигнатура типа исключает order, а любое значение, переданное в runtime, отбрасывается с записью warning в лог. Для сужения выборки используйте filter.
  • Только для list-методов: предназначен только для методов, возвращающих массивы данных.

Обработка ошибок

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

import { SdkError } from '@bitrix24/b24jssdk'

try {
  const generator = $b24.actions.v3.fetchList.make({
    method: 'some.method',
    params: { /* some_params */ },
    idKey: 'id',
    customKeyForResult: 'items',
    requestId: 'unique-request-id',
    limit: 600
  })

  for await (const chunk of generator) {
    // Обработка чанка (например, сохранение в БД, анализ и т.д.)
    console.log(`Processing ${chunk.length} items`)
  }
} catch (error) {
  // Обработка ошибки
  if (
    error instanceof SdkError
    && error.code === 'JSSDK_CORE_B24_FETCH_LIST_METHOD_API_V3'
  ) {
    console.error(`${error.message}`, { code: error.code })
  } else {
    console.error('Some error', error)
  }
}

Примеры

Поэтапная обработка большого числа элементов журнала событий

ProcessMainEventLogItems.ts
import { B24Hook, LoggerFactory, Text, SdkError, AjaxError } from '@bitrix24/b24jssdk'

type MainEventLogItem = {
  id: number
  userId: number
}

const devMode = !!(typeof import.meta !== 'undefined' && (import.meta.dev || import.meta.env?.DEV))
const $logger = LoggerFactory.createForBrowser('Example:ProcessMainEventLogItems', devMode)
const $b24 = B24Hook.fromWebhookUrl('https://your_domain.bitrix24.com/rest/1/webhook_code/')

async function processMainEventLogItem(): Promise<void> {
  let batchNumber = 0
  let totalItems = 0

  const sixMonthAgo = new Date()
  sixMonthAgo.setMonth((new Date()).getMonth() - 6)
  sixMonthAgo.setHours(0, 0, 0)
  
  const requestId = 'some-main-event-log-item-list'
  
  try {
    const generator = $b24.actions.v3.fetchList.make<MainEventLogItem>({
      method: 'main.eventlog.list',
      params: {
        filter: [
          ['timestampX', '>=', Text.toB24Format(sixMonthAgo)] // созданы как минимум 6 месяцев назад
        ],
        select: ['id', 'userId']
      },
      idKey: 'id',
      customKeyForResult: 'items',
      requestId,
      limit: 60
    })
    
    for await (const chunk of generator) {
      batchNumber++
      totalItems += chunk.length

      $logger.info(`Processing batch #${batchNumber}`, {
        batchSize: chunk.length,
        totalSoFar: totalItems
      })
      
      // Пример: сохранение в базу данных
      await saveToDatabase(chunk)
      
      // Пример: отправка в очередь сообщений
      await sendToMessageQueue(chunk)
    }
    
    $logger.notice(`Processed ${totalItems} elements in ${batchNumber} batches`)
  } catch (error) {
    if (error instanceof SdkError) {
      $logger.error(`Processing error: ${error.message}`, {
        code: error.code,
        batchNumber,
        totalItems
      })
    } else {
      $logger.error('Unknown error', { error, batchNumber, totalItems })
    }
    throw error
  }
}

// Вспомогательные функции
// Реализация сохранения в базу данных
async function saveToDatabase(items: MainEventLogItem[]): Promise<void> {
  await new Promise(resolve => setTimeout(resolve, 100)) // Simulation
}

// Реализация отправки в очередь сообщений
async function sendToMessageQueue(items: MainEventLogItem[]): Promise<void> {
  await new Promise(resolve => setTimeout(resolve, 50)) // Simulation
}

// Использование
try {
  await processMainEventLogItem()
} catch (error) {
  $logger.critical('A problem occurred', { error })
}

Когда поля id в запросе и ответе различаются

Некоторые методы сортируют / фильтруют по одному имени поля, но возвращают другое (например, ID в верхнем регистре в запросе против id в нижнем в payload). Задайте idKey на поле ответа, а cursorIdKey — на поле запроса:

const generator = $b24.actions.v3.fetchList.make({
  method: 'some.list',
  params: { select: ['ID', 'TITLE'] },
  idKey: 'id', // читаем id из каждого возвращённого элемента
  cursorIdKey: 'ID', // order + постраничный фильтр ["ID", ">", n] в запросе
  customKeyForResult: 'items'
})

for await (const chunk of generator) {
  console.log(`Got ${chunk.length} records`)
}

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

Альтернативы и рекомендации

  • Размер чанка: каждый чанк вмещает до options.limit записей (по умолчанию 50, макс 1000). Большие чанки — меньше HTTP-запросов, но больше нагрузки на память на каждой итерации.
  • Оптимальная параллельность: при обработке данных из генератора рекомендуется ограниченная параллельность (3–5 одновременных операций).
  • Обработка ошибок: всегда обрабатывайте ошибки внутри цикла for await...of, чтобы предотвратить прерывание всего процесса.
  • Мониторинг прогресса: внедряйте логирование прогресса для длительных операций.
  • Для последовательных запросов: используйте Call для одиночных вызовов.
  • Для пакетных операций: используйте Batch для выполнения до 50 команд в одном запросе.
  • На стороне клиента (браузер): используйте встроенный объект B24Frame.
  • См. также: Фильтрация — построение filter (и почему order отбрасывается).