Офлайн-очередь отложенных запросов

idb-keyval — это лёгкая обёртка над IndexedDB, предоставляющая удобный интерфейс для работы с ключ-значение в браузере. Она идеально подходит для хранения данных оффлайн, включая отложенные запросы, кэширование и временные состояния приложения. Основная цель — упростить асинхронное взаимодействие с IndexedDB, скрывая сложность API браузера.

Библиотека предоставляет базовые функции:

  • set(key, value) — сохранение значения по ключу.
  • get(key) — получение значения по ключу.
  • del(key) — удаление значения по ключу.
  • clear() — очистка всего хранилища.
  • keys() — получение массива всех ключей.

Каждая из функций возвращает промис, что позволяет легко интегрировать их с async/await.


Организация офлайн-очереди

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

Пример структуры хранения:

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

const QUEUE_KEY = 'offlineQueue';

async function enqueueRequest(request) {
    const queue = (await get(QUEUE_KEY)) || [];
    queue.push(request);
    await set(QUEUE_KEY, queue);
}

async function dequeueRequest() {
    const queue = (await get(QUEUE_KEY)) || [];
    if (queue.length === 0) return null;
    const request = queue.shift();
    await set(QUEUE_KEY, queue);
    return request;
}

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

  • Все запросы хранятся в виде объекта с необходимыми параметрами (url, method, body, headers).
  • Используется единый ключ в IndexedDB для очереди, что упрощает управление состоянием.
  • Любые изменения очереди сохраняются атомарно через set().

Стратегии обработки запросов

Для обеспечения надежной работы офлайн-очереди используют несколько стратегий:

  1. Автоматическая отправка при восстановлении сети
window.addEventListener('online', async () => {
    let request;
    while ((request = await dequeueRequest())) {
        try {
            await fetch(request.url, request.options);
        } catch (err) {
            await enqueueRequest(request); // вернуть в очередь при ошибке
            break; // остановить цикл при сбое
        }
    }
});
  1. Повторная попытка с экспоненциальной задержкой

Для повышения устойчивости к кратковременным сбоям сети можно реализовать повторные попытки:

async function retryRequest(request, attempt = 0) {
    const MAX_ATTEMPTS = 5;
    const DELAY = 1000 * Math.pow(2, attempt);

    try {
        await fetch(request.url, request.options);
    } catch (err) {
        if (attempt < MAX_ATTEMPTS) {
            setTimeout(() => retryRequest(request, attempt + 1), DELAY);
        } else {
            await enqueueRequest(request); // вернуть в очередь
        }
    }
}

Оптимизация хранения данных

  • Минимизация размера объектов: хранят только необходимую информацию для восстановления запроса.
  • Использование JSON-сериализации: объекты request преобразуются в JSON для безопасного хранения.
  • Проверка целостности очереди: перед обработкой извлекается актуальное состояние, что предотвращает потерю данных при сбое.
async function safeEnqueue(request) {
    if (!request || !request.url) return;
    const queue = (await get(QUEUE_KEY)) || [];
    queue.push(JSON.stringify(request));
    await set(QUEUE_KEY, queue);
}

async function safeDequeue() {
    const queue = (await get(QUEUE_KEY)) || [];
    if (!queue.length) return null;
    const request = JSON.parse(queue.shift());
    await set(QUEUE_KEY, queue);
    return request;
}

Использование отдельных хранилищ

idb-keyval позволяет создавать отдельные хранилища для разных типов данных, что полезно для разделения очередей по приоритету или типу запроса.

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

const highPriorityStore = new Store('offlineDB', 'highPriorityQueue');
const lowPriorityStore = new Store('offlineDB', 'lowPriorityQueue');

await set('task1', request1, highPriorityStore);
await set('task2', request2, lowPriorityStore);

const highQueue = await get('task1', highPriorityStore);

Отслеживание состояния очереди

Для мониторинга состояния офлайн-очереди полезно хранить метаданные: время добавления, количество попыток, статус.

async function enqueueWithMeta(request) {
    const metaRequest = {
        ...request,
        addedAt: Date.now(),
        attempts: 0,
        status: 'pending'
    };
    await enqueueRequest(metaRequest);
}

Преимущества:

  • Позволяет сортировать и обрабатывать старые запросы в первую очередь.
  • Упрощает диагностику ошибок и повторную обработку неудачных запросов.

Вывод

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