Skip to content

Практическая работа №9: Service Workers и IndexedDB

Цель работы: Научиться использовать Service Worker для кэширования ресурсов и обеспечения офлайн-доступа, а также применять IndexedDB для локального хранения данных в браузере. По итогам работы студент должен уметь регистрировать Service Worker, перехватывать сетевые запросы, сохранять данные в IndexedDB, реализовывать простой офлайн-сценарий веб-приложения и имитировать синхронизацию данных.


Основные теоретические положения

Section titled “Основные теоретические положения”

Service Worker — это JavaScript-файл, который работает в фоновом режиме браузера, отдельно от основного потока выполнения веб-страницы. Он действует как программный прокси-сервер между браузером и сетью, позволяя перехватывать сетевые запросы, управлять кэшем и обеспечивать офлайн-функциональность. Service Worker не имеет прямого доступа к DOM, но может взаимодействовать со страницей через механизм обмена сообщениями (postMessage) и события.

Жизненный цикл Service Worker:

  1. Регистрация (Registration): Браузер скачивает и регистрирует файл Service Worker.
  2. Установка (Installation): Вызывается событие install. На этом этапе обычно происходит предварительное кэширование статических ресурсов (HTML, CSS, JS, изображения).
  3. Активация (Activation): Вызывается событие activate. Здесь обычно происходит очистка старых кэшей, оставшихся от предыдущих версий Service Worker.
  4. Работа (Fetch/Message): Service Worker готов перехватывать сетевые запросы (событие fetch) или получать сообщения от страницы.

Пример регистрации Service Worker:

if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
.then(registration => {
console.log('ServiceWorker успешно зарегистрирован в области:', registration.scope);
})
.catch(error => {
console.log('Ошибка регистрации ServiceWorker:', error);
});
});
}

IndexedDB — это встроенная в браузер низкоуровневая объектная база данных для хранения значительных объёмов структурированных данных на стороне клиента. В отличие от localStorage (который хранит только строки и имеет жесткие ограничения по объему), IndexedDB:

  • Поддерживает транзакции для обеспечения целостности данных.
  • Позволяет создавать индексы для быстрого поиска.
  • Работает асинхронно, не блокируя основной поток выполнения (UI).
  • Может хранить сложные объекты JavaScript (например, файлы, Blob).

Основные понятия IndexedDB:

  • База данных (Database): Контейнер для хранилищ объектов.
  • Хранилище объектов (Object Store): Аналог таблиц в реляционных БД. Хранит данные в виде пар “ключ-значение”.
  • Транзакция (Transaction): Оболочка вокруг операций чтения/записи. Гарантирует, что операции будут выполнены целиком или не выполнены вообще.
  • Курсор (Cursor): Механизм для перебора записей в хранилище.

Пример открытия базы данных:

const request = indexedDB.open('MyDatabase', 1);
request.onupgradeneeded = (event) => {
const db = event.target.result;
// Создаем хранилище объектов, если оно не существует
if (!db.objectStoreNames.contains('notes')) {
db.createObjectStore('notes', { keyPath: 'id', autoIncrement: true });
}
};
request.onsuccess = (event) => {
const db = event.target.result;
console.log('База данных успешно открыта');
};
request.onerror = (event) => {
console.error('Ошибка при открытии базы данных:', event.target.error);
};

3. Стратегии кэширования

Section titled “3. Стратегии кэширования”

При перехвате запросов в Service Worker можно использовать различные стратегии:

  • Cache First (Сначала кэш): Сначала проверяется кэш. Если ресурс найден, он возвращается. Если нет — делается запрос в сеть, результат кэшируется и возвращается. Идеально для статических ресурсов (картинки, CSS, шрифты).
  • Network First (Сначала сеть): Сначала делается запрос в сеть. Если он успешен, ответ кэшируется и возвращается. Если сети нет, берется резервная копия из кэша. Подходит для часто обновляемых данных.
  • Stale While Revalidate (Устаревшее при обновлении): Немедленно возвращается закэшированная версия, а в фоновом режиме делается запрос в сеть для обновления кэша.

Алгоритм выполнения работы

Section titled “Алгоритм выполнения работы”

1. Подготовка проекта и окружения

Section titled “1. Подготовка проекта и окружения”
  1. Создайте папку проекта, например pwa_lab/.
  2. Подготовьте базовые файлы:
    • index.html — разметка страницы.
    • app.js — основной скрипт приложения (регистрация SW, работа с UI и IndexedDB).
    • sw.js — скрипт Service Worker (должен лежать в корне проекта).
    • styles.css — стили для интерфейса.
  3. Важно: Service Worker работает только по протоколу HTTPS или на localhost. Для локальной разработки используйте локальный сервер (например, python3 -m http.server 8000 или расширение Live Server в VS Code). Открытие файла через file:// не позволит зарегистрировать Service Worker.

2. Задания для выполнения

Section titled “2. Задания для выполнения”

Выполняйте задания последовательно. Для каждого пункта пишите код и проверяйте его работоспособность в браузере (используйте DevTools: вкладки Console, Application -> Service Workers, Application -> IndexedDB, Application -> Cache Storage).

Часть 1: Регистрация Service Worker и кэширование ресурсов

Section titled “Часть 1: Регистрация Service Worker и кэширование ресурсов”
  1. Регистрация: В файле app.js напишите код для регистрации Service Worker (sw.js). Выведите в консоль или на страницу статус регистрации.
  2. Установка и кэширование: В файле sw.js обработайте событие install. Создайте кэш с именем pwa-cache-v1 и добавьте в него основные файлы: /, /index.html, /app.js, /styles.css.
    • Подсказка: Используйте event.waitUntil() и caches.open().
  3. Активация и очистка: Обработайте событие activate. Напишите логику, которая удаляет все кэши, имя которых не совпадает с текущим (pwa-cache-v1).
  4. Перехват запросов (Cache First): Обработайте событие fetch. Реализуйте стратегию Cache First: при запросе ресурса сначала ищите его в кэше (caches.match()). Если он там есть — верните его. Если нет — выполните сетевой запрос (fetch()).
  5. Офлайн-заглушка: Измените логику fetch так, чтобы при отсутствии сети (ошибка fetch) и запросе HTML-страницы возвращался простой текстовый ответ или заранее закэшированная страница-заглушка с сообщением “Вы находитесь в офлайн-режиме”.
  6. Статус сети: В app.js добавьте слушатели событий online и offline на объект window. При изменении статуса обновляйте индикатор на странице (например, зеленый кружок “Online” и красный “Offline”).

В файле app.js (или отдельном db.js) реализуйте функции для работы с базой данных.

  1. Инициализация БД: Напишите функцию initDB(), которая открывает базу данных optimization_lab версии 1.
  2. Создание хранилища: В обработчике onupgradeneeded создайте объектное хранилище notes.
    • Настройки хранилища: keyPath: 'id', autoIncrement: true.
    • Создайте индекс по полю createdAt: objectStore.createIndex('createdAt', 'createdAt', { unique: false }).
  3. Добавление данных: Напишите функцию addNote(note), которая принимает объект заметки и сохраняет его в хранилище notes с использованием транзакции readwrite.
  4. Чтение данных: Напишите функцию getNotes(), которая возвращает все записи из хранилища (используйте метод getAll()).
  5. Удаление данных: Напишите функцию deleteNote(id), которая удаляет запись по её id.
  6. Тестовые данные: При успешном открытии пустой базы данных (проверьте количество записей), автоматически добавьте следующие тестовые данные для проверки:
    • { title: "Купить продукты", content: "Молоко, хлеб, яйца, сыр", createdAt: new Date().toISOString() }
    • { title: "Подготовиться к экзамену", content: "Повторить лекции по PWA и IndexedDB", createdAt: new Date().toISOString() }
    • { title: "Позвонить заказчику", content: "Обсудить макеты нового сайта", createdAt: new Date().toISOString() }

Часть 3: Интеграция интерфейса и офлайн-сценарий

Section titled “Часть 3: Интеграция интерфейса и офлайн-сценарий”
  1. Отображение данных: Напишите функцию renderNotes(notes), которая принимает массив заметок и отрисовывает их на странице (например, в виде карточек или таблицы). Каждая заметка должна иметь кнопку “Удалить”.
  2. Форма добавления: Создайте в index.html форму с полями “Заголовок” и “Текст заметки”. При отправке формы вызывайте addNote(), затем обновляйте список заметок на странице.
  3. Проверка офлайн-работы:
    • Откройте приложение в браузере. Убедитесь, что тестовые данные загрузились.
    • Перейдите в DevTools -> Network и включите режим “Offline”.
    • Перезагрузите страницу. Приложение должно загрузиться из кэша Service Worker, а данные должны успешно прочитаться из IndexedDB.
    • Попробуйте добавить новую заметку в офлайн-режиме. Она должна сохраниться в IndexedDB и отобразиться на странице.

Часть 4: Имитация фоновой синхронизации (Background Sync)

Section titled “Часть 4: Имитация фоновой синхронизации (Background Sync)”

Поскольку стандартный Background Sync API поддерживается не всеми браузерами, мы реализуем его упрощенную ручную версию.

  1. Очередь синхронизации: В onupgradeneeded создайте еще одно хранилище объектов syncQueue (с autoIncrement: true).
  2. Логика сохранения: Измените функцию добавления заметки. Если navigator.onLine равно false (нет сети), сохраняйте заметку не только в notes, но и в syncQueue.
  3. Синхронизация при появлении сети: В обработчике события online (которое срабатывает при восстановлении связи) напишите логику:
    • Прочитать все записи из syncQueue.
    • Вывести в консоль сообщение: Синхронизация данных с сервером: [данные]. (В реальном приложении здесь был бы fetch POST-запрос на сервер).
    • После “успешной отправки” очистить хранилище syncQueue.
  4. Индикатор: Добавьте на страницу счетчик “Ожидают синхронизации: N”, который показывает количество записей в syncQueue.

  1. Что такое Service Worker и чем он отличается от обычного JavaScript-кода на странице?
  2. Какие события жизненного цикла Service Worker вы использовали и зачем они нужны?
  3. Что такое Cache Storage и как он помогает ускорить приложение?
  4. Чем стратегия Cache First отличается от Network First? В каких случаях какую лучше применять?
  5. Что такое IndexedDB и чем она отличается от localStorage?
  6. Как создаётся объектное хранилище в IndexedDB? Что такое keyPath?
  7. Зачем нужны транзакции в IndexedDB? Какие режимы транзакций существуют?
  8. Почему Service Worker не может напрямую управлять DOM страницы?
  9. Какие ограничения есть у офлайн-режима веб-приложения?
  10. Как работает механизм синхронизации данных, реализованный в Части 4?

  • 30% — Корректная регистрация Service Worker, кэширование статических ресурсов и успешная загрузка приложения в офлайн-режиме.
  • 30% — Правильная работа с IndexedDB: создание базы, инициализация тестовыми данными, добавление, чтение и удаление записей.
  • 20% — Интеграция с пользовательским интерфейсом, корректное отображение статуса сети (Online/Offline).
  • 10% — Реализация механизма очереди синхронизации (syncQueue).
  • 10% — Оформление кода (разделение на функции, использование async/await или Promise, обработка ошибок try/catch), читаемость интерфейса и полные ответы на контрольные вопросы.

Рекомендуемая структура файлов

Section titled “Рекомендуемая структура файлов”
  • Заголовок лабораторной работы.
  • Индикатор статуса сети (Online/Offline) и счетчик синхронизации.
  • Форма добавления заметки (input для заголовка, textarea для текста, кнопка).
  • Контейнер (div или table) для вывода списка заметок.
  • Регистрация Service Worker.
  • Логика работы с IndexedDB (открытие, добавление, чтение, удаление).
  • Вставка тестовых данных при первом запуске.
  • Обработчики событий для формы и кнопок удаления.
  • Слушатели событий online и offline для обновления интерфейса и запуска синхронизации.
  • Массив ASSETS с путями к кэшируемым файлам.
  • Обработчик install: открытие кэша и cache.addAll().
  • Обработчик activate: удаление старых версий кэша.
  • Обработчик fetch: стратегия Cache First с резервным ответом для офлайна.

Рекомендуемая литература и ресурсы

Section titled “Рекомендуемая литература и ресурсы”
  1. MDN Web Docs: Service Worker API [1]
  2. MDN Web Docs: Использование IndexedDB [2]
  3. web.dev: Service workers and the Cache Storage API [3]
  4. JavaScript.info: IndexedDB [4]
  5. Google Developers: The Offline Cookbook (Стратегии кэширования) [5]

Дополнительное задание (для досрочно выполнивших основную часть)

Section titled “Дополнительное задание (для досрочно выполнивших основную часть)”
  1. Добавьте поле поиска, которое фильтрует отображаемые заметки по заголовку (используйте методы массивов JavaScript после получения данных из БД).
  2. Реализуйте возможность редактирования существующей заметки (метод put в IndexedDB).
  3. Добавьте кнопку “Очистить кэш и БД”, которая удаляет базу данных (indexedDB.deleteDatabase()) и очищает Cache Storage, после чего перезагружает страницу.
  4. Подготовьте краткий отчёт (в виде Markdown-файла) о том, как Service Worker влияет на скорость повторной загрузки приложения (сравните время загрузки с включенным и отключенным SW через вкладку Network в DevTools).