Практические паттерны с update: счётчики, очереди, флаги

Метод update в библиотеке Idb-keyval используется для безопасного изменения значений в IndexedDB без необходимости вручную читать, изменять и записывать данные. Он автоматически обеспечивает атомарность операции, предотвращая состояния гонки при параллельных изменениях. Синтаксис выглядит следующим образом:

import { upd ate } fr om 'idb-keyval';

upd ate('ключ', oldValue => {
    // возвращаем новое значение на основе oldValue
    return newValue;
});

Ключевые моменты:

  • oldValue — текущее значение по ключу. Если ключ отсутствует, будет undefined.
  • Функция может возвращать любое сериализуемое значение.
  • Операция атомарная: чтение и запись происходят как единый транзакционный блок.

Счётчики

Счётчики — классический пример использования update. Они позволяют аккуратно увеличивать или уменьшать числовое значение без риска перезаписи при конкурирующих обновлениях.

Простейший счётчик

import { update } fr om 'idb-keyval';

function incrementCounter(key) {
    return update(key, value => (value || 0) + 1);
}

incrementCounter('visits').then(() => {
    console.log('Счётчик увеличен');
});
  • (value || 0) гарантирует корректную работу при отсутствии ключа.
  • Каждое обновление безопасно даже при одновременных вызовах.

Сложные счётчики с ограничением

function incrementWithLimit(key, lim it) {
    return update(key, value => {
        const current = value || 0;
        return current < lim it ? current + 1 : current;
    });
}

incrementWithLimit('likes', 100);
  • Позволяет реализовать верхнюю границу счётчика без лишней логики вне функции update.

Очереди

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

Простейшая очередь

function enqueue(key, item) {
    return update(key, queue => {
        const arr = queue || [];
        arr.push(item);
        return arr;
    });
}

enqueue('tasks', { id: 1, action: 'sendEmail' });
  • Использование массива для хранения элементов.
  • Можно безопасно добавлять элементы одновременно из разных частей приложения.

Извлечение и обработка элемента

import { get, se t } from 'idb-keyval';

async function dequeue(key) {
    const queue = (await get(key)) || [];
    if (queue.length === 0) return null;

    const item = queue.shift();
    await se t(key, queue);
    return item;
}
  • update можно использовать и для извлечения с изменением:
update('tasks', queue => {
    if (!queue || queue.length === 0) return [];
    queue.shift();
    return queue;
});
  • Позволяет реализовать атомарное удаление без необходимости отдельного чтения и записи.

Флаги и состояния

Флаги обычно используются для хранения булевых значений или статусов. С update их легко переключать или комбинировать с условиями.

Переключение булевого флага

function toggleFlag(key) {
    return update(key, value => !value);
}

toggleFlag('darkMode');
  • Если ключ отсутствует, undefined превращается в true, что часто удобно для инициализации.

Множественные флаги в объекте

function setFlags(key, changes) {
    return update(key, value => ({ ...(value || {}), ...changes }));
}

setFlags('userSettings', { notifications: false, betaAccess: true });
  • Позволяет атомарно обновлять несколько флагов без риска перезаписи других полей.

Комбинированные паттерны

Счётчик в очереди

Можно сочетать счётчики и очереди, например, для отслеживания числа обработанных задач:

update('tasksProcessed', value => (value || 0) + 1);
update('tasks', queue => {
    const arr = queue || [];
    arr.shift();
    return arr;
});
  • update гарантирует корректное инкрементирование даже при параллельных вызовах.
  • Очередь и счётчик могут обновляться в разных транзакциях, сохраняя целостность данных.

Флаги с ограничениями

update('featureFlags', flags => {
    const current = flags || {};
    if (!current.newUI) current.newUI = true; // включаем только один раз
    return current;
});
  • Удобно для фич-тогглов или контрольных точек активации функций.

Практические рекомендации

  • Всегда проверять oldValue на undefined, особенно для чисел и массивов.
  • Использовать update для атомарных изменений, когда есть вероятность конкурентного доступа.
  • Для сложных объектов удобно применять оператор разворота ..., чтобы не перезаписывать весь объект.
  • Сохранять простые типы (числа, строки, массивы) для предсказуемого поведения update.
  • Комбинировать update с get и set только при необходимости чтения до или после изменения.

Эти паттерны позволяют строить безопасные, атомарные и легко масштабируемые структуры данных в IndexedDB с использованием Idb-keyval, минимизируя вероятность ошибок и конфликтов при параллельной работе приложения.