Параллельное выполнение операций с Promise.all

Библиотека idb-keyval предоставляет простой интерфейс для работы с IndexedDB через промисы, что позволяет эффективно хранить и извлекать данные в браузере. Когда требуется выполнить несколько операций одновременно — например, чтение или запись нескольких ключей — возникает необходимость параллельного выполнения операций, и здесь ключевую роль играет Promise.all.

Promise.all принимает массив промисов и возвращает новый промис, который выполняется, когда все промисы завершены успешно, или отклоняется, если хотя бы один промис завершился с ошибкой.

Пример базового применения:

import { set, get } from 'idb-keyval';

const operations = [
  set('user1', { name: 'Alice', age: 25 }),
  set('user2', { name: 'Bob', age: 30 }),
  set('user3', { name: 'Charlie', age: 28 })
];

Promise.all(operations)
  .then(() => console.log('Все данные сохранены'))
  .catch(err => console.error('Ошибка при сохранении:', err));

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


Параллельное чтение данных

Для чтения нескольких значений из хранилища idb-keyval параллельно удобно использовать тот же подход с Promise.all.

import { get } from 'idb-keyval';

const keys = ['user1', 'user2', 'user3'];

const readOperations = keys.map(key => get(key));

Promise.all(readOperations)
  .then(results => {
    results.forEach((data, index) => {
      console.log(`${keys[index]}:`, data);
    });
  })
  .catch(err => console.error('Ошибка при чтении данных:', err));

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


Комбинированные операции: чтение и запись

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

import { get, set } from 'idb-keyval';

const keys = ['user1', 'user2', 'user3'];

const updateOperations = keys.map(async key => {
  const user = await get(key);
  if (user) {
    user.lastLogin = new Date().toISOString();
    return set(key, user);
  }
});

Promise.all(updateOperations)
  .then(() => console.log('Данные обновлены'))
  .catch(err => console.error('Ошибка при обновлении:', err));

Ключевой момент здесь — использование асинхронной функции внутри map, которая возвращает промис. Благодаря Promise.all все обновления выполняются одновременно, что сокращает общее время операции.


Обработка ошибок при параллельных операциях

При параллельных операциях критически важно учитывать возможность ошибок. Если хотя бы один промис завершится с ошибкой, Promise.all отклонится, и выполнение остальных операций не будет остановлено, но результат вернётся как ошибка.

Для более гибкой обработки ошибок можно использовать Promise.allSettled:

const operations = keys.map(async key => {
  try {
    const value = await get(key);
    return { key, value };
  } catch (err) {
    return { key, error: err };
  }
});

Promise.allSettled(operations)
  .then(results => {
    results.forEach(result => {
      if (result.status === 'fulfilled') {
        console.log(`${result.value.key}:`, result.value.value);
      } else {
        console.error(`Ошибка ключа ${result.reason.key}:`, result.reason.error);
      }
    });
  });

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


Оптимизация параллельных операций

  • Ограничение числа одновременных промисов. Если массив ключей очень большой, стоит использовать контроль количества одновременных операций, чтобы не перегружать IndexedDB.
  • Пакетная обработка. Разбивка на батчи по 50–100 операций снижает нагрузку и предотвращает возможные сбои при слишком большом количестве параллельных промисов.
  • Кеширование. Часто читаемые данные лучше сохранять в памяти, чтобы минимизировать количество обращений к IndexedDB.

Применение в реальных сценариях

  1. Сохранение настроек пользователя: все настройки можно сохранить одновременно при изменении интерфейса.
  2. Загрузка предварительных данных: параллельное чтение нескольких ключей для инициализации приложения.
  3. Массовое обновление записей: добавление новых полей или атрибутов для нескольких объектов в хранилище.

Использование Promise.all в сочетании с idb-keyval делает работу с IndexedDB прозрачной, асинхронной и эффективной, особенно при необходимости параллельной обработки множества операций.