BatchV2.make

Метод для выполнения пакетных запросов к REST API Bitrix24 версии 2. Позволяет выполнить до 50 команд в одном API-вызове.

Обзор

Используйте BatchV2.make() для выполнения до 50 команд REST API в одном запросе. Это особенно полезно, когда нужно получить или обновить большие объёмы данных, минимизируя сетевые запросы и соблюдая ограничения REST API.

// Базовое использование
import { EnumCrmEntityTypeId } from '@bitrix24/b24jssdk'

const response = await $b24.actions.v2.batch.make({
  calls: [
    ['crm.item.get', { entityTypeId: EnumCrmEntityTypeId.contact, id: 1 }],
    ['crm.item.get', { entityTypeId: EnumCrmEntityTypeId.contact, id: 2 }]
  ],
  options: {
    isHaltOnError: true,
    returnAjaxResult: true,
    requestId: 'unique-request-id'
  }
})

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

make<T = unknown>(
  options: ActionBatchV2
): Promise<CallBatchResult<T>>

Параметры

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

ПараметрТипОбязательныйОписание
callsBatchCommandsArrayUniversal | BatchCommandsObjectUniversal | BatchNamedCommandsUniversalДаКоманды для выполнения в батче. Поддерживает несколько форматов.
optionsIB24BatchOptionsНетДополнительные опции для выполнения пакетного запроса.

Форматы команд (options.calls)

1. Массив кортежей (BatchCommandsArrayUniversal)

// @check-ignore: partial snippet — calls array literal, not a valid statement

calls: [
  ['method1', params1],
  ['method2', params2],
  // ...
]

2. Массив объектов (BatchCommandsObjectUniversal)

// @check-ignore: partial snippet — object array literal, not a valid statement

calls: [
  { method: 'method1', params: params1 },
  { method: 'method2', params: params2 },
  // ...
]

3. Объект с именованными командами (BatchNamedCommandsUniversal)

calls: {
  command1: { method: 'method1', params: params1 },
  command2: ['method2', params2],
  // ...
}

Опции пакетного запроса (options.options)

ОпцияТипПо умолчаниюОписание
isHaltOnErrorbooleantrueОстанавливать ли выполнение при первой ошибке.
returnAjaxResultbooleanfalseВозвращать ли объект AjaxResult вместо данных.
requestIdstringУникальный идентификатор запроса для отслеживания. Используется для дедупликации запросов и отладки.

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

Promise<CallBatchResult<T>> — promise, разрешающийся в объект CallBatchResult<T>.

Структура результата зависит от формата входных данных:

  • Для массива команд: массив результатов в том же порядке.
  • Для именованных команд: объект с ключами, соответствующими именам команд.

Данные элемента результата

Каждый result отдельной команды возвращается ровно так, как его отдал REST API, включая null, когда вызываемый метод действительно не возвращает данных (например, im.chat.get, вызванный с ENTITY_TYPE, не соответствующим никакому чату).

// @check-ignore: partial snippet — uses union result type and undeclared variables

const response = await $b24.actions.v2.batch.make<{ ID: number } | null>({
  calls: {
    chatGet: {
      method: 'im.chat.get',
      params: { ENTITY_TYPE: 'UNKNOWN', ENTITY_ID: 'UNKNOWN' }
    }
  },
  options: { returnAjaxResult: true, requestId: 'chat-get' }
})

const chatGet = response.getData().chatGet
if (chatGet.getData()?.result === null) {
  // метод вернул null — ни один чат не совпал
} else {
  console.log(chatGet.getData()?.result.ID)
}
До v1.1.1 результат null приводился к {}, что ломало nullable-type-guards на стороне вызова (см. issue #23). При типизации generic для методов, которые могут вернуть null, объявляйте его как T | null.

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

Всегда проверяйте результат через isSuccess и обрабатывайте ошибки:

// @check-ignore: top-level return in error-handling illustration

const response = await $b24.actions.v2.batch.make({
  calls: [
    { method: 'crm.item.get', params: { entityTypeId: EnumCrmEntityTypeId.contact, id: 1 } },
    { method: 'crm.item.get', params: { entityTypeId: EnumCrmEntityTypeId.contact, id: 2 } }
  ],
  options: {
    isHaltOnError: true,
    returnAjaxResult: true,
    requestId: 'unique-request-id'
  }
})

if (!response.isSuccess) {
  // Обработка ошибки
  console.error(new Error(`Error: ${response.getErrorMessages().join('; ')}`))
  return
}

// Работа с успешным результатом
const data = response.getData()

Проверка isSuccess покрывает два разных вида сбоя:

  • Ошибки отдельных команд — конверт батча успешен, но отдельные команды упали. Проверяйте каждый AjaxResult в getData() или используйте getErrorsByKey(), чтобы узнать, какая команда упала.
  • Ошибки уровня конверта (soft) — весь батч падает до того, как вернётся хоть один результат команды, например при серверном коде валидации (BITRIX_REST_V3_EXCEPTION_VALIDATION_*). Тогда внешний ResultisSuccess === false, ошибка(и) в getErrorMessages() / getErrors(), а getData().result пуст.

Одна проверка if (!response.isSuccess) обрабатывает оба случая.

Примеры

Получение нескольких компаний из CRM

import type { AjaxResult, Result, BatchCommandsArrayUniversal } from '@bitrix24/b24jssdk'
import { B24Hook, EnumCrmEntityTypeId, LoggerFactory, SdkError, AjaxError } from '@bitrix24/b24jssdk'

type Company = {
  id: number
  title: string
  [key: string]: any
}

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

async function getMultipleItems(itemIds: number[], requestId: string): Promise<Company[]> {
  if (itemIds.length < 1 || itemIds.length > 50) {
    throw new SdkError({
      code: 'MY_APP_GET_PROBLEM',
      description: `The number of elements must be between 1 and 50`,
      status: 404
    })
  }

  const batchCalls: BatchCommandsArrayUniversal = itemIds.map(id => [
    'crm.item.get',
    {
      entityTypeId: EnumCrmEntityTypeId.company,
      id
    }
  ])

  const response = await $b24.actions.v2.batch.make<{ item: Company }>({
    calls: batchCalls,
    options: {
      isHaltOnError: true,
      returnAjaxResult: true,
      requestId
    }
  })

  if (!response.isSuccess) {
    throw new SdkError({
      code: 'MY_APP_GET_PROBLEM',
      description: `Problem ${response.getErrorMessages().join('; ')}`,
      status: 404
    })
  }

  const resultData = (response as Result<AjaxResult<{ item: Company }>[]>).getData()!
  const results: Company[] = []
  resultData.forEach((resultRow, _index) => {
    if (resultRow.isSuccess) {
      results.push((resultRow.getData()!.result as { item: Company }).item)
    }
  })

  return results
}

// Usage
const requestId = 'batch/crm.item.get'
try {
  const itemIds = [2, 4]
  const items = await getMultipleItems(itemIds, requestId)

  $logger.info(`Retrieved ${items.length} items`, {
    expected: itemIds.length,
    retrieved: items.length,
    items: items.map(c => ({ id: c.id, title: c.title }))
  })
} catch (error) {
  if (error instanceof AjaxError) {
    $logger.critical(error.message, { requestId, code: error.code })
  } else {
    $logger.alert('Problem', { requestId, error })
  }
}

Инициализация хранилища данных

Этот код автоматизирует создание и инициализацию хранилищ данных в Bitrix24 через REST API.

Он проверяет существование указанных хранилищ и, если их нет, создаёт их вместе с указанными свойствами, используя пакетные запросы для эффективности.

batch-rest-api-ver2-data-storage.ts
import type { BatchNamedCommandsUniversal } from '@bitrix24/b24jssdk'
import { B24Hook, LoggerFactory, SdkError, AjaxError } from '@bitrix24/b24jssdk'

type DataStorageParams = {
  ENTITY: string
  NAME: string
  ACCESS: Record<string, 'R' | 'W' | 'X'>
}

type PropertyParams = {
  PROPERTY: string
  NAME: string
  TYPE: 'S' | 'N' | 'F'
}

type DataStorage = {
  isInit: boolean
  dataStorage: DataStorageParams
  props: PropertyParams[]
}

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

async function initDataStorageList(dataStorageMap: Map<string, DataStorage>, requestId: string): Promise<void> {
  // get current list
  const response = await $b24.actions.v2.call.make<{ ENTITY: string, NAME: string }[]>({
    method: 'entity.get',
    params: {},
    requestId: `${requestId}/init:getCurrentList`
  })

  if (!response.isSuccess) {
    throw new SdkError({
      code: 'MY_APP_GET_PROBLEM',
      description: `Problem ${response.getErrorMessages().join('; ')}`,
      status: 404
    })
  }

  const currentDataStorageList = response.getData()!.result as { ENTITY: string, NAME: string }[]

  for (const dataStorage of dataStorageMap.values()) {
    const isInit = currentDataStorageList.some(row => row.ENTITY === dataStorage.dataStorage.ENTITY)
    if (isInit) {
      dataStorage.isInit = true
    } else {
      await initDataStorage(dataStorage, requestId)
      dataStorage.isInit = true
    }
  }
}

async function initDataStorage(dataStorage: DataStorage, requestId: string): Promise<void> {
  const callBatch: BatchNamedCommandsUniversal = {
    AddEntity: {
      method: 'entity.add',
      params: dataStorage.dataStorage
    }
  }

  for (const property of dataStorage.props) {
    callBatch[`prop${property.PROPERTY}`] = {
      method: 'entity.item.property.add',
      params: {
        ...property,
        ENTITY: dataStorage.dataStorage.ENTITY
      }
    }
  }

  const response = await $b24.actions.v2.batch.make({
    calls: callBatch,
    options: {
      isHaltOnError: true,
      returnAjaxResult: false,
      requestId: `${requestId}/init:${dataStorage.dataStorage.ENTITY}`
    }
  })

  if (!response.isSuccess) {
    throw new SdkError({
      code: 'MY_APP_GET_PROBLEM',
      description: `Problem ${response.getErrorMessages().join('; ')}`,
      status: 404
    })
  }
}

// Usage
const requestId = 'batch/DataStorage'
try {
  const dataStorageMap: Map<string, DataStorage> = new Map([
    ['DataStorage1', {
      isInit: false,
      dataStorage: {
        ENTITY: 'DS1',
        NAME: 'Data Storage 1',
        ACCESS: {
          U1: 'X',
          AU: 'R'
        }
      },
      props: [
        {
          PROPERTY: 'PropertyN',
          NAME: 'Property N',
          TYPE: 'N'
        }
      ]
    }],
    ['DataStorage2', {
      isInit: false,
      dataStorage: {
        ENTITY: 'DS2',
        NAME: 'Data Storage 2',
        ACCESS: {
          U1: 'X',
          AU: 'R'
        }
      },
      props: [
        {
          PROPERTY: 'PropertyS',
          NAME: 'Property S',
          TYPE: 'S'
        }
      ]
    }],
    ['DataStorage3', {
      isInit: false,
      dataStorage: {
        ENTITY: 'DS3',
        NAME: 'Data Storage 3',
        ACCESS: {
          U1: 'X',
          AU: 'R'
        }
      },
      props: [
        {
          PROPERTY: 'PropertyF',
          NAME: 'Property F',
          TYPE: 'F'
        }
      ]
    }]
  ])

  await initDataStorageList(dataStorageMap, requestId)

  $logger.info(`dataStorageList`, {
    items: [...dataStorageMap.values()].map(c => ({
      entity: c.dataStorage.ENTITY,
      isInit: c.isInit,
      title: c.dataStorage.NAME,
      props: c.props
    }))
  })
} catch (error) {
  if (error instanceof AjaxError) {
    $logger.critical(error.message, { requestId, code: error.code })
  } else {
    $logger.alert('Problem', { requestId, error })
  }
}

Удаление хранилища данных

Этот код удаляет несколько хранилищ данных в системе Bitrix24 через REST API.

Он последовательно отправляет delete-запросы для каждого хранилища данных, указанного в dataStorageMap, используя метод entity.delete.

batch-rest-api-ver2-data-storage-delete.ts
import { B24Hook, LoggerFactory, SdkError, AjaxError } from '@bitrix24/b24jssdk'

type DataStorageParams = {
  ENTITY: string
  NAME: string
}

type DataStorage = {
  dataStorage: DataStorageParams
}

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

async function removeDataStorageList(dataStorageMap: Map<string, DataStorage>, requestId: string): Promise<void> {
  for (const dataStorage of dataStorageMap.values()) {
    const response = await $b24.actions.v2.call.make({
      method: 'entity.delete',
      params: {
        ENTITY: dataStorage.dataStorage.ENTITY
      },
      requestId: `${requestId}/init:getCurrentList`
    })

    if (!response.isSuccess) {
      throw new SdkError({
        code: 'MY_APP_GET_PROBLEM',
        description: `Problem ${response.getErrorMessages().join('; ')}`,
        status: 404
      })
    }
  }
}

// Usage
const requestId = 'batch/DataStorageRemove'
try {
  const dataStorageMap: Map<string, DataStorage> = new Map([
    ['DataStorage1', {
      dataStorage: {
        ENTITY: 'DS1',
        NAME: 'Data Storage 1'
      }
    }],
    ['DataStorage2', {
      dataStorage: {
        ENTITY: 'DS2',
        NAME: 'Data Storage 2'
      }
    }],
    ['DataStorage3', {
      dataStorage: {
        ENTITY: 'DS3',
        NAME: 'Data Storage 3'
      }
    }]
  ])

  await removeDataStorageList(dataStorageMap, requestId)
} catch (error) {
  if (error instanceof AjaxError) {
    $logger.critical(error.message, { requestId, code: error.code })
  } else {
    $logger.alert('Problem', { requestId, error })
  }
}

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

  • Для последовательных запросов: используйте Call для одиночных вызовов.
  • Для работы со списками: используйте CallList для получения больших объёмов данных.
  • Для поэтапной обработки: используйте FetchList для обработки данных по мере поступления.
  • Для запуска большего числа команд (более 50): используйте BatchByChunk, который автоматически разбивает команды на чанки по 50.
  • На стороне клиента (браузер): используйте встроенный объект B24Frame.