Web Crypto API выполняет криптографические операции асинхронно,
возвращая Promise. Это делает её естественно совместимой с
параллельным выполнением задач через Promise.all, особенно
когда требуется обработка массивов данных: хеширование файлов,
шифрование наборов сообщений, проверка подписи для множества объектов
или генерация нескольких ключей.
Большинство методов crypto.subtle возвращают
Promise:
digestencryptdecryptsignverifygenerateKeyderiveKeyimportKeyexportKeyКаждая операция выполняется в изолированном криптографическом
контексте браузера и не блокирует основной поток выполнения JavaScript.
Это означает, что несколько криптографических задач можно запускать
одновременно, а затем синхронизировать их результат через
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 — немедленный отказ при
первой ошибке.
await Promise.all([
crypto.subtle.digest("SHA-256", data1),
crypto.subtle.digest("SHA-256", data2)
]);
Если один из входных буферов некорректен (например,
undefined или повреждённый ArrayBuffer), вся
операция будет отклонена.
В криптографических сценариях это может быть нежелательно, особенно при обработке больших наборов данных, где часть элементов допустимо обработать независимо.
Для задач массовой обработки криптографических данных более
устойчивым решением является 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 выполняется независимо, и
браузер может использовать нативные криптографические библиотеки и
многопоточность.
Несмотря на поддержку параллельного выполнения, существуют практические ограничения:
1. Ограничение ресурсов CPU
Большое количество параллельных операций может привести к конкуренции за CPU, особенно при:
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, даже если он завершился позже.
Это критично для криптографических пайплайнов, где соответствие данных строго фиксировано.
При обработке больших массивов данных часто используется батчинг:
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
позволяет:
Однако чрезмерный параллелизм может привести к деградации производительности из-за конкуренции за ресурсы, особенно при сложных алгоритмах с высокой вычислительной сложностью.