במאמר הזה מוסבר איך משתמשים ב-Cache API כדי שנתוני האפליקציות שלכם יהיו זמינים במצב אופליין.
Cache API הוא מערכת לאחסון ולאחזור של בקשות רשת והתשובות התואמות שלהן. אלו יכולות להיות בקשות ותשובות רגילות שנוצרות במהלך הרצת האפליקציה, או שהן נוצרו אך ורק למטרות אחסון נתונים לשימוש מאוחר יותר.
Cache API נוצר כדי לאפשר ל-Service Workers לשמור בקשות רשת במטמון כדי שיוכלו לספק תגובות מהירות, ללא קשר למהירות הרשת או לזמינות. עם זאת, ניתן להשתמש בממשק ה-API גם כמנגנון אחסון כללי.
איפה השירות זמין?
Cache API זמין בכל הדפדפנים המתקדמים. הוא נחשף דרך המאפיין הגלובלי caches
, כך שאפשר לבדוק את נוכחות ה-API באמצעות זיהוי תכונות פשוט:
const cacheAvailable = 'caches' in self;
ניתן לגשת ל-Cache API מחלון, מ-iframe, מ-worker או מ-service worker.
מה אפשר לאחסן
במטמון מאוחסנים רק צמדים של Request
ו-Response
אובייקטים, שמייצגים בקשות ותגובות של HTTP, בהתאמה. עם זאת, הבקשות והתשובות יכולות להכיל כל סוג של נתונים שאפשר להעביר באמצעות HTTP.
כמה אפשר לאחסן?
בקיצור, הרבה, לפחות כמה מאות מגה-בייט ואולי גם מאות ג'יגה-בייט או יותר. יישומי הדפדפן משתנים, אבל נפח האחסון הזמין נקבע בדרך כלל בהתאם לנפח האחסון הזמין במכשיר.
יצירה ופתיחה של מטמון
כדי לפתוח מטמון, משתמשים ב-method caches.open(name)
ומעבירים את שם המטמון בתור הפרמטר היחיד. אם המטמון בעל השם לא קיים, הוא נוצר. ה-method הזה מחזירה Promise
שמתרחשת עם האובייקט Cache
.
const cache = await caches.open('my-cache');
// do something with cache...
הוספה למטמון
יש שלוש דרכים להוסיף פריט למטמון – add
, addAll
ו-put
.
בכל שלוש השיטות מוחזרות Promise
.
cache.add
קודם כל, יש cache.add()
. היא מקבלת פרמטר אחד, Request
או כתובת URL (string
). היא שולחת בקשה לרשת ושומרת את התגובה במטמון. אם השליפה נכשלה או אם קוד הסטטוס של התשובה לא נמצא בטווח של 200, שום דבר לא נשמר ו-Promise
יידחה. שימו לב שאי אפשר לשמור בקשות ממקורות שונים שלא במצב CORS כי הן מחזירות status
של 0
. אפשר לאחסן בקשות כאלה רק באמצעות put
.
// Retreive data.json from the server and store the response.
cache.add(new Request('/data.json'));
// Retreive data.json from the server and store the response.
cache.add('/data.json');
cache.addAll
השלב הבא הוא cache.addAll()
. היא פועלת באופן דומה ל-add()
, אבל היא לוקחת מערך של Request
אובייקטים או כתובות URL (string
s). הפעולה הזו פועלת באופן דומה לקריאה ל-cache.add
לכל בקשה בנפרד, אבל ה-Promise
ידחה אם בקשה אחת לא נשמרה במטמון.
const urls = ['/weather/today.json', '/weather/tomorrow.json'];
cache.addAll(urls);
בכל אחד מהמקרים האלו, רשומה חדשה מחליפה רשומה קיימת תואמת. לשם כך צריך להשתמש באותם כללי ההתאמה שמתוארים בקטע על retrieving.
cache.put
לבסוף, יש את cache.put()
, שמאפשר לאחסן תגובה מהרשת או ליצור ולאחסן Response
משלכם. הוא לוקח שני פרמטרים. הראשון יכול להיות אובייקט Request
או כתובת URL (string
). השני חייב להיות Response
, מהרשת או שנוצר על ידי הקוד שלכם.
// Retrieve data.json from the server and store the response.
cache.put('/data.json');
// Create a new entry for test.json and store the newly created response.
cache.put('/test.json', new Response('{"foo": "bar"}'));
// Retrieve data.json from the 3rd party site and store the response.
cache.put('https://example.com/data.json');
השיטה put()
מאפשרת יותר מאשר add()
או addAll()
, והיא תאפשר לשמור תשובות שלא קשורות ל-CORS או תשובות אחרות שבהן קוד הסטטוס של התשובה הוא לא בטווח של 200. הוא יחליף תשובות קודמות לאותה בקשה.
יצירת אובייקטים של בקשות
יוצרים את האובייקט Request
באמצעות כתובת ה-URL של הפריט שמאוחסן:
const request = new Request('/my-data-store/item-id');
עבודה עם אובייקטים של תגובה
ה-constructor של האובייקטים Response
מקבל סוגים רבים של נתונים, כולל אובייקטים מסוג Blob
, ArrayBuffer
, FormData
ומחרוזות.
const imageBlob = new Blob([data], {type: 'image/jpeg'});
const imageResponse = new Response(imageBlob);
const stringResponse = new Response('Hello world');
אפשר להגדיר את סוג MIME של Response
על ידי הגדרת הכותרת המתאימה.
const options = {
headers: {
'Content-Type': 'application/json'
}
}
const jsonResponse = new Response('{}', options);
אם מאחזרים Response
ורוצים לגשת לגוף שלו, יש כמה שיטות עזרה שאפשר להשתמש בהן. כל אחת מחזירה Promise
שמקודד עם ערך מסוג אחר.
שיטה | תיאור |
---|---|
arrayBuffer |
הפונקציה מחזירה ArrayBuffer שמכיל את הגוף, עם סריאליזציה לבייטים.
|
blob |
הפונקציה מחזירה את הערך Blob . אם השדה Response נוצר באמצעות Blob , המאפיין Blob החדש הוא מאותו הסוג. אחרת, נעשה שימוש ב-Content-Type של Response .
|
text |
הפונקציה מפרשת את הבייטים של הגוף כמחרוזת בקידוד UTF-8. |
json |
הפונקציה מפרשת את הבייטים של הגוף כמחרוזת בקידוד UTF-8, ואז מנסה לנתח אותם כ-JSON. מחזירה את האובייקט שנוצר, או מזינה
TypeError אם לא ניתן לנתח את המחרוזת כ-JSON.
|
formData |
הפונקציה מפרשת את הבייטים של הגוף כצורת HTML, שמקודדים כ-multipart/form-data או כ-application/x-www-form-urlencoded . הפונקציה מחזירה אובייקט FormData, או גורמת TypeError אם אי אפשר לנתח את הנתונים.
|
body |
מחזירה ReadableStream עבור נתוני הגוף. |
לדוגמה
const response = new Response('Hello world');
const buffer = await response.arrayBuffer();
console.log(new Uint8Array(buffer));
// Uint8Array(11) [72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]
אחזור מהמטמון
כדי למצוא פריט במטמון, אפשר להשתמש בשיטה match
.
const response = await cache.match(request);
console.log(request, response);
אם המחרוזת request
היא מחרוזת, הדפדפן ממיר אותה ל-Request
על ידי קריאה ל-new Request(request)
. הפונקציה מחזירה Promise
שמפנה ל-Response
אם נמצאה רשומה תואמת. אחרת, הפונקציה מחזירה undefined
.
כדי לקבוע אם שתי התאמות של Requests
תואמות, הדפדפן משתמש באפשרויות נוספות מלבד כתובת ה-URL. שתי בקשות נחשבות שונות אם יש להן מחרוזות שאילתה שונות, כותרות Vary
או methods שונות של HTTP (GET
, POST
, PUT
וכו').
כדי להתעלם מחלק מהדברים האלה או מכולם, אפשר להעביר אובייקט אפשרויות כפרמטר שני.
const options = {
ignoreSearch: true,
ignoreMethod: true,
ignoreVary: true
};
const response = await cache.match(request, options);
// do something with the response
אם יש התאמה ליותר מבקשה אחת במטמון, הבקשה שנוצרה ראשונה מוחזרת. אם רוצים לאחזר את כל התשובות התואמות, אפשר להשתמש ב-cache.matchAll()
.
const options = {
ignoreSearch: true,
ignoreMethod: true,
ignoreVary: true
};
const responses = await cache.matchAll(request, options);
console.log(`There are ${responses.length} matching responses.`);
בתור קיצור דרך, אפשר לחפש בכל המטמון בבת אחת באמצעות caches.match()
במקום לקרוא ל-cache.match()
לכל מטמון.
מתבצע חיפוש
Cache API לא מאפשר לחפש בקשות או תגובות מלבד רשומות תואמות לאובייקט Response
. עם זאת, תוכלו להטמיע חיפוש משלכם באמצעות סינון או על ידי יצירת אינדקס.
סינון
אחת הדרכים שבהן אפשר להטמיע חיפוש משלכם היא לעבור שוב על כל הרשומות ולסנן כך שיופיעו רק הערכים הרצויים. נניח שאתם רוצים למצוא את כל הפריטים עם כתובות URL שמסתיימות ב-.png
.
async function findImages() {
// Get a list of all of the caches for this origin
const cacheNames = await caches.keys();
const result = [];
for (const name of cacheNames) {
// Open the cache
const cache = await caches.open(name);
// Get a list of entries. Each item is a Request object
for (const request of await cache.keys()) {
// If the request URL matches, add the response to the result
if (request.url.endsWith('.png')) {
result.push(await cache.match(request));
}
}
}
return result;
}
כך אפשר להשתמש בכל מאפיין של האובייקטים Request
ו-Response
כדי לסנן את הרשומות. שימו לב שזה איטי אם מחפשים על קבוצות גדולות של נתונים.
יצירת אינדקס
הדרך השנייה להטמיע חיפוש משלכם היא לשמור אינדקס נפרד של רשומות שאפשר לחפש ולאחסן אותן ב-IndexedDB. מכיוון שזו הפעולה ש-IndexedDB תוכנן, יש לה ביצועים טובים הרבה יותר במספר גדול של רשומות.
אם מאחסנים את כתובת ה-URL של Request
לצד המאפיינים שבהם אפשר לחפש, אפשר לאחזר בקלות את רשומת המטמון הנכונה אחרי ביצוע החיפוש.
מחיקת פריט
כדי למחוק פריט ממטמון:
cache.delete(request);
הבקשה יכולה להיות מחרוזת של Request
או של כתובת URL. השיטה הזו גם משתמשת באותו אובייקט אפשרויות כמו cache.match
, וכך אפשר למחוק מספר צמדים של Request
/Response
עבור אותה כתובת URL.
cache.delete('/example/file.txt', {ignoreVary: true, ignoreSearch: true});
מחיקת מטמון
כדי למחוק מטמון, צריך להתקשר אל caches.delete(name)
. הפונקציה הזו מחזירה Promise
שמפנה ל-true
אם המטמון היה קיים ונמחק. אחרת, הוא false
.
תודה
תודה ל-Mat Scales שכתב את הגרסה המקורית של המאמר הזה, שהופיעה לראשונה ב-WebFundamentals.