Обзор
Когда нужно обработать большие объёмы данных из 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()
- Очень большие объёмы данных: когда число записей в тысячах или десятках тысяч.
- Потоковая обработка: когда данные нужно обрабатывать по мере поступления.
- Длительные операции: когда обработка каждой записи требует значительного времени.
Сигнатура метода
make<T = unknown>(
options: ActionFetchListV3
): AsyncGenerator<T[]>
Параметры
Объект options содержит следующие свойства:
Возвращаемое значение
AsyncGenerator<T[]> — асинхронный генератор, возвращающий чанки данных в виде массивов типа T.
Каждая итерация генератора возвращает:
- Массив элементов (чанк) до
options.limitзаписей. - Генератор завершается, когда все данные получены.
Ключевые понятия
AsyncGenerator vs Promise
В отличие от CallListV3.make(), который возвращает Promise со всеми данными сразу, FetchListV3.make() возвращает асинхронный генератор:
Оптимизация производительности
Метод реализует рекомендованный Bitrix24 алгоритм для эффективной работы с большими объёмами данных:
- Фильтрация по возрастающему id: каждый следующий запрос использует фильтр
[cursorIdKey, '>', id]с id последнего полученного элемента (читается черезidKey). - Потоковая обработка: данные обрабатываются по мере поступления, экономя память.
- Автоматическое определение конца данных: запросы останавливаются при получении пустого массива или когда страница короче самой большой из виденных. Это привязывает остановку к размеру страницы, который реально возвращает сервер, а не к запрошенному
options.limit, поэтому метод, молча ограничивающий страницу нижеlimit(например,tasks.task.list, зафиксирован на 50), всё равно проходит все записи, а не останавливается после первой урезанной страницы.
Структура ответа REST API v3
customKeyForResult сохранён для обратной совместимости. В будущем после анализа реального использования будет принято решение о его необходимости.В REST API версии 3 разные методы возвращают данные в одинаковых структурах:
- Сгруппированный массив:
{ result: { items: [...] } }
Параметр customKeyForResult позволяет указать ключ, по которому расположены данные в ответе.
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)
}
}
Примеры
Поэтапная обработка большого числа элементов журнала событий
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отбрасывается).