Параллельная обработка с Promise.all

Web Crypto API выполняет криптографические операции асинхронно, возвращая Promise. Это делает её естественно совместимой с параллельным выполнением задач через Promise.all, особенно когда требуется обработка массивов данных: хеширование файлов, шифрование наборов сообщений, проверка подписи для множества объектов или генерация нескольких ключей.


Большинство методов crypto.subtle возвращают Promise:

  • digest
  • encrypt
  • decrypt
  • sign
  • verify
  • generateKey
  • deriveKey
  • importKey
  • exportKey

Каждая операция выполняется в изолированном криптографическом контексте браузера и не блокирует основной поток выполнения JavaScript. Это означает, что несколько криптографических задач можно запускать одновременно, а затем синхронизировать их результат через Promise.all.


Базовый принцип параллелизма с Promise.all

Promise.all принимает массив промисов и возвращает новый промис, который:

  • выполняется, когда завершены все операции
  • отклоняется, если хотя бы одна операция завершилась с ошибкой
  • сохраняет порядок результатов в соответствии с порядком входного массива

Это критично для криптографических задач, где важно сопоставление входных данных и результатов.


Параллельное хеширование набора данных

Одним из самых частых сценариев является вычисление хешей для множества сообщений или файлов.

const encoder = new TextEncoder();

const messages = [
  "сообщение 1",
  "сообщение 2",
  "сообщение 3"
];

const hashPromises = messages.map(msg =>
  crypto.subtle.digest("SHA-256", encoder.encode(msg))
);

const hashes = await Promise.all(hashPromises);

Каждый вызов digest выполняется независимо, что позволяет браузеру распределить нагрузку между доступными потоками.


Обработка бинарных данных в параллельном режиме

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

async function hashFileChunks(chunks) {
  return Promise.all(
    chunks.map(chunk =>
      crypto.subtle.digest("SHA-256", chunk)
    )
  );
}

Важно, что ArrayBuffer и TypedArray передаются в Web Crypto без копирования, что снижает накладные расходы при массовой обработке.


Параллельное шифрование набора сообщений

Симметричное шифрование через AES-GCM также хорошо масштабируется через Promise.all.

const key = await crypto.subtle.generateKey(
  { name: "AES-GCM", length: 256 },
  true,
  ["encrypt", "decrypt"]
);

const iv = crypto.getRandomValues(new Uint8Array(12));
const encoder = new TextEncoder();

const plaintexts = [
  "секрет 1",
  "секрет 2",
  "секрет 3"
];

const encryptedPromises = plaintexts.map(text =>
  crypto.subtle.encrypt(
    { name: "AES-GCM", iv },
    key,
    encoder.encode(text)
  )
);

const encrypted = await Promise.all(encryptedPromises);

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


Ошибки в Promise.all и влияние на криптографические операции

Ключевая особенность Promise.all — немедленный отказ при первой ошибке.

await Promise.all([
  crypto.subtle.digest("SHA-256", data1),
  crypto.subtle.digest("SHA-256", data2)
]);

Если один из входных буферов некорректен (например, undefined или повреждённый ArrayBuffer), вся операция будет отклонена.

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


Promise.allSettled для устойчивой криптографической обработки

Для задач массовой обработки криптографических данных более устойчивым решением является Promise.allSettled.

const results = await Promise.allSettled(
  files.map(file =>
    crypto.subtle.digest("SHA-256", file)
  )
);

Каждый результат имеет структуру:

  • status: "fulfilled" — операция успешна
  • status: "rejected" — ошибка выполнения

Это позволяет продолжить обработку даже при частичных сбоях.


Параллельная генерация ключей

Генерация ключей — одна из самых тяжёлых операций в Web Crypto API, особенно для RSA или ECDSA.

const keyPromises = Array.from({ length: 5 }, () =>
  crypto.subtle.generateKey(
    {
      name: "ECDSA",
      namedCurve: "P-256"
    },
    true,
    ["sign", "verify"]
  )
);

const keys = await Promise.all(keyPromises);

Каждый вызов generateKey выполняется независимо, и браузер может использовать нативные криптографические библиотеки и многопоточность.


Ограничения параллелизма в Web Crypto API

Несмотря на поддержку параллельного выполнения, существуют практические ограничения:

1. Ограничение ресурсов CPU

Большое количество параллельных операций может привести к конкуренции за CPU, особенно при:

  • RSA-генерации
  • PBKDF2-вычислениях
  • большом количестве SHA-512 операций

2. Отсутствие явного контроля потоков

Web Crypto API не предоставляет интерфейса для управления количеством одновременно выполняемых операций.

3. Накладные расходы планирования

Слишком мелкие операции (например, хеширование коротких строк по отдельности) могут быть менее эффективны, чем пакетная обработка.


Оптимизация: группировка криптографических операций

Эффективная стратегия — уменьшать количество промисов, объединяя данные перед обработкой.

Плохо:

messages.map(m => crypto.subtle.digest("SHA-256", encode(m)));

Оптимальнее:

const combined = encoder.encode(messages.join("|"));
const hash = await crypto.subtle.digest("SHA-256", combined);

Параллельные цепочки криптографических операций

Иногда требуется комбинация операций: хеширование → подпись → проверка.

const data = encoder.encode("сообщение");

const digestPromise = crypto.subtle.digest("SHA-256", data);

const keyPromise = crypto.subtle.generateKey(
  { name: "HMAC", hash: "SHA-256" },
  true,
  ["sign", "verify"]
);

const [digest, key] = await Promise.all([digestPromise, keyPromise]);

const signature = await crypto.subtle.sign(
  "HMAC",
  key,
  digest
);

Здесь параллелизм позволяет сократить общее время подготовки криптографического контекста.


Параллельная проверка цифровых подписей

При проверке большого массива подписей:

const verifyTasks = signatures.map(sig =>
  crypto.subtle.verify(
    "RSA-PSS",
    publicKey,
    sig.signature,
    encoder.encode(sig.message)
  )
);

const results = await Promise.all(verifyTasks);

Такой подход используется в системах:

  • проверки целостности сообщений
  • валидации пакетов данных
  • массовой аутентификации

Поведение порядка результатов

Promise.all сохраняет порядок входного массива, независимо от времени завершения операций.

const tasks = [
  slowCryptoOp(),
  fastCryptoOp()
];

const results = await Promise.all(tasks);

results[0] всегда соответствует slowCryptoOp, даже если он завершился позже.

Это критично для криптографических пайплайнов, где соответствие данных строго фиксировано.


Параллельная обработка в потоках данных (stream-like batching)

При обработке больших массивов данных часто используется батчинг:

async function processInBatches(items, size) {
  const results = [];

  for (let i = 0; i < items.length; i += size) {
    const batch = items.slice(i, i + size);

    const batchResults = await Promise.all(
      batch.map(item => crypto.subtle.digest("SHA-256", item))
    );

    results.push(...batchResults);
  }

  return results;
}

Такой подход предотвращает перегрузку криптографического подсистемного слоя браузера.


Влияние параллельности на производительность криптографии

Использование Promise.all в Web Crypto API позволяет:

  • максимально задействовать нативные криптографические ускорения
  • снизить общее время обработки наборов данных
  • избежать блокировки UI-потока
  • эффективно масштабировать операции на многоядерных системах

Однако чрезмерный параллелизм может привести к деградации производительности из-за конкуренции за ресурсы, особенно при сложных алгоритмах с высокой вычислительной сложностью.