Опануйте просунуті техніки Fetch API: перехоплення запитів для їх модифікації та реалізація кешування відповідей для оптимальної продуктивності.
Fetch API для просунутих: перехоплення запитів і кешування відповідей
Fetch API став стандартом для виконання мережевих запитів у сучасному JavaScript. Хоча базове використання є простим, для розкриття повного потенціалу необхідно розуміти просунуті техніки, такі як перехоплення запитів та кешування відповідей. У цій статті ми детально розглянемо ці концепції, надаючи практичні приклади та найкращі практики для створення високопродуктивних, глобально доступних веб-застосунків.
Розуміння Fetch API
Fetch API надає потужний та гнучкий інтерфейс для отримання ресурсів по мережі. Він використовує Promise, що полегшує керування та розуміння асинхронних операцій. Перш ніж зануритися у просунуті теми, коротко розглянемо основи:
Базове використання Fetch
Простий запит Fetch виглядає так:
fetch('https://api.example.com/data')
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
})
.then(data => {
console.log('Data:', data);
})
.catch(error => {
console.error('Fetch error:', error);
});
Цей код отримує дані за вказаною URL-адресою, перевіряє наявність HTTP-помилок, парсить відповідь як JSON та виводить дані в консоль. Обробка помилок є надзвичайно важливою для забезпечення надійності застосунку.
Перехоплення запитів
Перехоплення запитів передбачає зміну або спостереження за мережевими запитами перед їх відправкою на сервер. Це може бути корисно для різних цілей, зокрема:
- Додавання заголовків автентифікації
- Трансформація даних запиту
- Логування запитів для налагодження
- Імітація відповідей API під час розробки
Перехоплення запитів зазвичай реалізується за допомогою Service Worker, який діє як проксі-сервер між веб-застосунком та мережею.
Service Workers: основа для перехоплення
Service Worker — це JavaScript-файл, що виконується у фоновому режимі, окремо від основного потоку браузера. Він може перехоплювати мережеві запити, кешувати відповіді та забезпечувати офлайн-функціональність. Щоб використовувати Service Worker, його потрібно спочатку зареєструвати:
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js')
.then(registration => {
console.log('Service Worker registered with scope:', registration.scope);
})
.catch(error => {
console.error('Service Worker registration failed:', error);
});
}
Цей код перевіряє, чи підтримує браузер Service Workers, і реєструє файл service-worker.js
. Область видимості (scope) визначає, які URL-адреси буде контролювати Service Worker.
Реалізація перехоплення запитів
У файлі service-worker.js
ви можете перехоплювати запити за допомогою події fetch
:
self.addEventListener('fetch', event => {
// Intercept all fetch requests
event.respondWith(
new Promise(resolve => {
// Clone the request to avoid modifying the original
const req = event.request.clone();
// Modify the request (e.g., add an authentication header)
const headers = new Headers(req.headers);
headers.append('Authorization', 'Bearer your_api_key');
const modifiedReq = new Request(req.url, {
method: req.method,
headers: headers,
body: req.body,
mode: 'cors',
credentials: req.credentials,
cache: req.cache,
redirect: req.redirect,
referrer: req.referrer,
integrity: req.integrity
});
// Make the modified request
fetch(modifiedReq)
.then(response => resolve(response))
.catch(error => {
console.error('Fetch error in Service Worker:', error);
// Optionally, return a default response or error page
resolve(new Response('Offline', { status: 503, statusText: 'Service Unavailable' }));
});
})
);
});
Цей код перехоплює кожен fetch
-запит, клонує його, додає заголовок Authorization
, а потім виконує змінений запит. Метод event.respondWith()
повідомляє браузеру, як обробити запит. Важливо клонувати запит; інакше ви будете змінювати оригінальний запит, що може призвести до неочікуваної поведінки. Також він гарантує, що всі оригінальні опції запиту передаються для забезпечення сумісності. Зверніть увагу на обробку помилок: важливо передбачити запасний варіант на випадок збою запиту (наприклад, коли немає з'єднання з мережею).
Приклад: додавання заголовків автентифікації
Поширеним випадком використання перехоплення запитів є додавання заголовків автентифікації до запитів API. Це гарантує, що лише авторизовані користувачі зможуть отримати доступ до захищених ресурсів.
self.addEventListener('fetch', event => {
if (event.request.url.startsWith('https://api.example.com')) {
event.respondWith(
new Promise(resolve => {
const req = event.request.clone();
const headers = new Headers(req.headers);
// Replace with actual authentication logic (e.g., retrieving token from local storage)
const token = localStorage.getItem('api_token');
if (token) {
headers.append('Authorization', `Bearer ${token}`);
} else {
console.warn("No API token found, request may fail.");
}
const modifiedReq = new Request(req.url, {
method: req.method,
headers: headers,
body: req.body,
mode: 'cors',
credentials: req.credentials,
cache: req.cache,
redirect: req.redirect,
referrer: req.referrer,
integrity: req.integrity
});
fetch(modifiedReq)
.then(response => resolve(response))
.catch(error => {
console.error('Fetch error in Service Worker:', error);
resolve(new Response('Offline', { status: 503, statusText: 'Service Unavailable' }));
});
})
);
} else {
// Let the browser handle the request as usual
event.respondWith(fetch(event.request));
}
});
Цей код додає заголовок Authorization
до запитів, які починаються з https://api.example.com
. Він отримує токен API з локального сховища. Надзвичайно важливо реалізувати належне керування токенами та заходи безпеки, такі як HTTPS та безпечне зберігання.
Приклад: трансформація даних запиту
Перехоплення запитів також можна використовувати для трансформації даних запиту перед їх відправкою на сервер. Наприклад, ви можете конвертувати дані у певний формат або додавати додаткові параметри.
self.addEventListener('fetch', event => {
if (event.request.url.includes('/submit-form')) {
event.respondWith(
new Promise(resolve => {
const req = event.request.clone();
req.text().then(body => {
try {
const parsedBody = JSON.parse(body);
// Transform the data (e.g., add a timestamp)
parsedBody.timestamp = new Date().toISOString();
// Convert the transformed data back to JSON
const transformedBody = JSON.stringify(parsedBody);
const modifiedReq = new Request(req.url, {
method: req.method,
headers: req.headers,
body: transformedBody,
mode: 'cors',
credentials: req.credentials,
cache: req.cache,
redirect: req.redirect,
referrer: req.referrer,
integrity: req.integrity
});
fetch(modifiedReq)
.then(response => resolve(response))
.catch(error => {
console.error('Fetch error in Service Worker:', error);
resolve(new Response('Offline', { status: 503, statusText: 'Service Unavailable' }));
});
} catch (error) {
console.error("Error parsing request body:", error);
resolve(fetch(event.request)); // Fallback to original request
}
});
})
);
} else {
event.respondWith(fetch(event.request));
}
});
Цей код перехоплює запити до /submit-form
, парсить тіло запиту як JSON, додає часову мітку, а потім відправляє трансформовані дані на сервер. Обробка помилок є важливою, щоб застосунок не зламався, якщо тіло запиту не є валідним JSON.
Кешування відповідей
Кешування відповідей передбачає зберігання відповідей від API-запитів у кеші браузера. Це може значно покращити продуктивність, зменшивши кількість мережевих запитів. Коли кешована відповідь доступна, браузер може надати її безпосередньо з кешу, не роблячи нового запиту до сервера.
Переваги кешування відповідей
- Покращена продуктивність: Швидший час завантаження та більш чутливий користувацький досвід.
- Зменшене споживання трафіку: Менше даних передається по мережі, що заощаджує трафік як для користувача, так і для сервера.
- Офлайн-функціональність: Кешовані відповіді можуть надаватися, навіть коли користувач офлайн, забезпечуючи безперебійну роботу.
- Економія коштів: Менше споживання трафіку означає менші витрати для користувачів та постачальників послуг, особливо в регіонах з дорогими або обмеженими тарифними планами.
Реалізація кешування відповідей за допомогою Service Workers
Service Workers надають потужний механізм для реалізації кешування відповідей. Ви можете використовувати Cache
API для зберігання та отримання відповідей.
const cacheName = 'my-app-cache-v1';
const cacheableUrls = [
'/',
'/index.html',
'/styles.css',
'/script.js',
'https://api.example.com/data'
];
// Install event: Cache static assets
self.addEventListener('install', event => {
event.waitUntil(
caches.open(cacheName)
.then(cache => {
console.log('Caching app shell');
return cache.addAll(cacheableUrls);
})
);
});
// Activate event: Clean up old caches
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.filter(name => name !== cacheName)
.map(name => caches.delete(name))
);
})
);
});
// Fetch event: Serve cached responses or fetch from the network
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
// Cache hit - return response
if (response) {
return response;
}
// Not in cache - fetch from network
return fetch(event.request).then(
response => {
// Check if we received a valid response
if (!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
// Clone the response (because it's a stream and can only be consumed once)
const responseToCache = response.clone();
caches.open(cacheName)
.then(cache => {
cache.put(event.request, responseToCache);
});
return response;
}
).catch(error => {
// Handle network error
console.error("Fetch failed:", error);
// Optionally, provide a fallback response (e.g., offline page)
return caches.match('/offline.html');
});
})
);
});
Цей код кешує статичні ресурси під час події встановлення (install) та надає кешовані відповіді під час події запиту (fetch). Якщо відповідь не знайдена в кеші, він запитує її з мережі, кешує, а потім повертає. Подія активації (activate) використовується для очищення старих кешів при оновленні Service Worker. Цей підхід також гарантує, що кешуються лише валідні відповіді (статус 200 і тип 'basic').
Стратегії кешування
Існує кілька різних стратегій кешування, які ви можете використовувати залежно від потреб вашого застосунку:
- Спочатку кеш (Cache-First): Спершу спробувати надати відповідь з кешу. Якщо її там немає, зробити запит до мережі та закешувати. Це добре підходить для статичних ресурсів, які не змінюються часто.
- Спочатку мережа (Network-First): Спершу спробувати отримати відповідь з мережі. Якщо це не вдається, надати її з кешу. Це добре для динамічних даних, які мають бути актуальними.
- Кеш, потім мережа (Cache, then Network): Негайно надати відповідь з кешу, а потім оновити кеш останньою версією з мережі. Це забезпечує швидке початкове завантаження та гарантує, що користувач завжди матиме найсвіжіші дані (зрештою).
- Застаріле під час перевірки (Stale-While-Revalidate): Негайно повернути кешовану відповідь, одночасно перевіряючи мережу на наявність оновленої версії. Оновити кеш у фоновому режимі, якщо доступна новіша версія. Схоже на "Кеш, потім мережа", але забезпечує більш плавний користувацький досвід.
Вибір стратегії кешування залежить від конкретних вимог вашого застосунку. Враховуйте такі фактори, як частота оновлень, важливість актуальності даних та доступна пропускна здатність.
Приклад: кешування відповідей API
Ось приклад кешування відповідей API за допомогою стратегії "Спочатку кеш":
self.addEventListener('fetch', event => {
if (event.request.url.startsWith('https://api.example.com')) {
event.respondWith(
caches.match(event.request)
.then(response => {
// Cache hit - return response
if (response) {
return response;
}
// Not in cache - fetch from network
return fetch(event.request).then(
response => {
// Check if we received a valid response
if (!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
// Clone the response (because it's a stream and can only be consumed once)
const responseToCache = response.clone();
caches.open(cacheName)
.then(cache => {
cache.put(event.request, responseToCache);
});
return response;
}
);
})
);
} else {
// Let the browser handle the request as usual
event.respondWith(fetch(event.request));
}
});
Цей код кешує відповіді API з https://api.example.com
. Коли робиться запит, Service Worker спочатку перевіряє, чи є відповідь вже в кеші. Якщо так, повертається кешована відповідь. Якщо ні, запит надсилається до мережі, а відповідь кешується перед поверненням.
Додаткові аспекти
Інвалідація кешу
Однією з найбільших проблем при кешуванні є інвалідація кешу. Коли дані на сервері змінюються, потрібно переконатися, що кеш оновлено. Існує кілька стратегій інвалідації кешу:
- "Злам" кешу (Cache Busting): Додайте номер версії або часову мітку до URL-адреси ресурсу. Коли ресурс змінюється, URL-адреса також змінюється, і браузер завантажить нову версію.
- Термін дії за часом: Встановіть максимальний вік для кешованих відповідей. Після закінчення терміну дії браузер завантажить нову версію з сервера. Використовуйте заголовок
Cache-Control
для вказівки максимального віку. - Ручна інвалідація: Використовуйте метод
caches.delete()
для ручного видалення кешованих відповідей. Це може бути викликано подією на стороні сервера або дією користувача. - WebSockets для оновлень у реальному часі: Використовуйте WebSockets для надсилання оновлень з сервера клієнту, інвалідуючи кеш за потреби.
Мережі доставки контенту (CDN)
Мережі доставки контенту (CDN) — це розподілені мережі серверів, які кешують контент ближче до користувачів. Використання CDN може значно покращити продуктивність для користувачів по всьому світу, зменшуючи затримку та споживання трафіку. Популярні провайдери CDN включають Cloudflare, Amazon CloudFront та Akamai. При інтеграції з CDN переконайтеся, що заголовки Cache-Control
налаштовані правильно для оптимальної поведінки кешування.
Аспекти безпеки
При реалізації перехоплення запитів та кешування відповідей важливо враховувати аспекти безпеки:
- HTTPS: Завжди використовуйте HTTPS для захисту даних під час передачі.
- CORS: Правильно налаштуйте Cross-Origin Resource Sharing (CORS), щоб запобігти несанкціонованому доступу до ресурсів.
- Санітизація даних: Санітизуйте вхідні дані користувача, щоб запобігти атакам міжсайтового скриптингу (XSS).
- Безпечне зберігання: Зберігайте конфіденційні дані, такі як ключі API та токени, безпечно (наприклад, використовуючи HTTPS-only cookies або безпечний API для зберігання).
- Цілісність підресурсів (SRI): Використовуйте SRI, щоб гарантувати, що ресурси, отримані зі сторонніх CDN, не були підроблені.
Налагодження Service Workers
Налагодження Service Workers може бути складним, але інструменти розробника в браузері надають кілька функцій для допомоги:
- Вкладка Application: Вкладка Application у Chrome DevTools надає інформацію про Service Workers, включаючи їхній статус, область видимості та сховище кешу.
- Логування в консоль: Використовуйте оператори
console.log()
для виведення інформації про активність Service Worker. - Точки зупинки: Встановлюйте точки зупинки в коді Service Worker для покрокового виконання та перевірки змінних.
- Оновлення при перезавантаженні: Увімкніть "Update on reload" у вкладці Application, щоб Service Worker оновлювався щоразу при перезавантаженні сторінки.
- Скасувати реєстрацію Service Worker: Використовуйте кнопку "Unregister" у вкладці Application, щоб скасувати реєстрацію Service Worker. Це може бути корисно для усунення проблем або для початку з чистого аркуша.
Висновок
Перехоплення запитів та кешування відповідей — це потужні техніки, які можуть значно покращити продуктивність та користувацький досвід веб-застосунків. Використовуючи Service Workers, ви можете перехоплювати мережеві запити, змінювати їх за потреби та кешувати відповіді для офлайн-функціональності та швидшого завантаження. При правильній реалізації ці техніки допоможуть вам створювати високопродуктивні, глобально доступні веб-застосунки, що забезпечують безперебійний користувацький досвід навіть у складних мережевих умовах. Враховуйте різноманітність мережевих умов та вартість даних, з якими стикаються користувачі по всьому світу, при впровадженні цих технік, щоб забезпечити оптимальну доступність та інклюзивність. Завжди надавайте пріоритет безпеці для захисту конфіденційних даних та запобігання вразливостям.
Опанувавши ці просунуті техніки Fetch API, ви зможете підняти свої навички веб-розробки на новий рівень і створювати справді виняткові веб-застосунки.