Бинарные данные: Blob

Библиотека localForage предоставляет единый API для работы с различными типами хранилищ браузера, абстрагируя различия между IndexedDB, WebSQL и localStorage. Одним из ключевых преимуществ является возможность хранения бинарных данных, включая Blob, File и ArrayBuffer, без необходимости ручной сериализации.

Бинарные типы данных особенно важны при работе с медиа-контентом, файлами и любыми структурами, которые не могут быть корректно представлены в виде строки или JSON.


Blob как основной тип бинарных данных

Blob (Binary Large Object) представляет собой неизменяемый объект, содержащий сырые данные. Он используется для хранения изображений, аудиофайлов, видеофрагментов и любых других бинарных потоков.

В контексте localForage важно учитывать:

  • Blob поддерживается только в IndexedDB-драйвере
  • При использовании fallback-драйверов (localStorage, WebSQL) возможны ограничения
  • localForage автоматически определяет возможность сохранения бинарных объектов

Основное преимущество — отсутствие необходимости преобразования в base64.


Сохранение Blob в localForage

Сохранение бинарных данных осуществляется так же, как и работа с обычными значениями.

import localForage from "localforage";

const imageBlob = new Blob([binaryData], { type: "image/png" });

localForage.setItem("avatar", imageBlob)
  .then(() => {
    console.log("Blob сохранён");
  })
  .catch((err) => {
    console.error("Ошибка сохранения:", err);
  });

Важные особенности:

  • ключ может быть строкой любой структуры
  • значение сохраняется без сериализации вручную
  • IndexedDB хранит Blob нативно

Чтение Blob из хранилища

Получение данных полностью симметрично операции записи.

localForage.getItem("avatar")
  .then((blob) => {
    if (blob) {
      console.log("Получен Blob:", blob);
    } else {
      console.log("Данные отсутствуют");
    }
  })
  .catch((err) => {
    console.error("Ошибка чтения:", err);
  });

Использование Blob в DOM

Часто Blob используется для отображения изображений или медиа:

localForage.getItem("avatar").then((blob) => {
  const url = URL.createObjectURL(blob);
  const img = document.createElement("img");
  img.src = url;
  document.body.appendChild(img);
});

Преобразование Blob и ArrayBuffer

В некоторых сценариях требуется доступ к «сырым» байтам.

Blob → ArrayBuffer

function blobToArrayBuffer(blob) {
  return blob.arrayBuffer();
}

localForage.getItem("file").then(async (blob) => {
  const buffer = await blobToArrayBuffer(blob);
  console.log(new Uint8Array(buffer));
});

ArrayBuffer → Blob

const buffer = new Uint8Array([1, 2, 3, 4]).buffer;

const blob = new Blob([buffer], { type: "application/octet-stream" });

localForage.setItem("binary", blob);

Работа с File API

File является наследником Blob, поэтому поддерживается аналогично.

const fileInput = document.querySelector("input[type='file']");

fileInput.addEventListener("change", () => {
  const file = fileInput.files[0];

  localForage.setItem("uploadedFile", file);
});

Чтение:

localForage.getItem("uploadedFile").then((file) => {
  console.log(file.name);
  console.log(file.type);
  console.log(file.size);
});

Практические сценарии использования Blob

Хранение изображений

Типичный сценарий — кэширование аватаров или пользовательских изображений:

fetch("/api/avatar")
  .then(res => res.blob())
  .then(blob => localForage.setItem("avatar", blob));

Позднее:

localForage.getItem("avatar").then(blob => {
  document.querySelector("img").src = URL.createObjectURL(blob);
});

Кэширование аудио

fetch("/audio/track.mp3")
  .then(res => res.blob())
  .then(blob => localForage.setItem("track", blob));

Воспроизведение:

localForage.getItem("track").then(blob => {
  const audio = new Audio(URL.createObjectURL(blob));
  audio.play();
});

Оффлайн-доступ к медиа

Blob часто используется для реализации offline-first подхода:

  • изображения интерфейса
  • видеофрагменты
  • загруженные документы PDF

Ограничения хранения бинарных данных

Несмотря на удобство, существуют важные ограничения.

1. Драйверы хранения

Драйвер Поддержка Blob
IndexedDB Полная
WebSQL Частичная
localStorage Нет

localForage автоматически выбирает IndexedDB при наличии поддержки.


2. Размер данных

IndexedDB не имеет жесткого лимита, но фактически ограничивается:

  • свободным дисковым пространством
  • политиками браузера
  • производительностью устройства

3. Производительность

Большие Blob могут влиять на:

  • скорость записи
  • скорость сериализации внутри IndexedDB
  • время загрузки из хранилища

Особенности сериализации

localForage использует встроенные механизмы IndexedDB, поэтому:

  • Blob не превращается в строку
  • не применяется JSON.stringify
  • данные сохраняются «как есть»

Это критически важно для производительности при работе с большими файлами.


Ошибки и обработка исключений

Типичные проблемы при работе с Blob:

Ошибка квоты

QuotaExceededError

Причины:

  • слишком большой Blob
  • переполнение хранилища

Ошибки совместимости

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

new Blob([undefined]) // потенциально проблемный случай

Безопасная запись

async function saveBlobSafe(key, blob) {
  try {
    await localForage.setItem(key, blob);
  } catch (e) {
    console.error("Ошибка записи Blob:", e);
  }
}

Оптимизация работы с Blob

1. Использование URL вместо повторного чтения

const blob = await localForage.getItem("image");
const url = URL.createObjectURL(blob);

Важно освобождать память:

URL.revokeObjectURL(url);

2. Сжатие перед сохранением

Перед сохранением изображений часто применяется:

  • ресайзинг через canvas
  • конвертация в WebP
  • уменьшение качества JPEG

3. Разделение больших файлов

Для крупных данных иногда эффективнее:

  • хранить чанки Blob
  • использовать массив ключей
  • собирать файл при чтении

Работа с потоками и частичной загрузкой

localForage не поддерживает потоковую запись Blob напрямую, поэтому применяется стратегия:

  1. загрузка по частям
  2. объединение в Blob
  3. сохранение итогового объекта
const chunk1 = new Uint8Array([1, 2]);
const chunk2 = new Uint8Array([3, 4]);

const blob = new Blob([chunk1, chunk2]);
await localForage.setItem("file", blob);

Поведение при смене драйвера

localForage может переключаться между драйверами:

  • при недоступности IndexedDB
  • при ограничениях окружения (например, приватный режим браузера)

В таких случаях:

  • Blob может не сохраниться
  • данные могут быть автоматически приведены к строке (в зависимости от драйвера)

Взаимодействие Blob с другими типами данных

localForage поддерживает смешанные структуры:

const data = {
  name: "image",
  file: blob,
  created: Date.now()
};

localForage.setItem("record", data);

Однако при вложенных структурах важно учитывать:

  • сериализация объектов выполняется через structured clone
  • Blob сохраняется как отдельная сущность внутри структуры

Практические паттерны хранения

Кэш изображений по ключу URL

async function cacheImage(url) {
  const cached = await localForage.getItem(url);
  if (cached) return cached;

  const blob = await fetch(url).then(r => r.blob());
  await localForage.setItem(url, blob);

  return blob;
}

Ленивая загрузка медиа

async function getMedia(id) {
  let media = await localForage.getItem(id);

  if (!media) {
    media = await fetch(`/media/${id}`).then(r => r.blob());
    await localForage.setItem(id, media);
  }

  return media;
}