הממשק Cache API: מדריך קצר

איך משתמשים ב-Cache API כדי לאפשר גישה לנתוני האפליקציה במצב אופליין

Cache API הוא מערכת לאחסון ולאחזור של בקשות רשת והתשובות התואמות שלהן. אלה יכולות להיות בקשות ותשובות רגילות שנוצרות במהלך הפעלת האפליקציה, או שהן יכולות להיווצר אך ורק למטרת אחסון נתונים לשימוש מאוחר יותר.

‏Cache API נוצר כדי לאפשר ל-Service Workers לשמור בקשות רשת במטמון, כדי שיוכלו לספק תשובות מהירות, ללא קשר למהירות או לזמינות של הרשת. עם זאת, אפשר להשתמש ב-API גם כמנגנון אחסון כללי.

איפה השירות זמין?

Cache API זמין בכל הדפדפנים המודרניים. הוא מוצג דרך המאפיין הגלובלי caches, כך שאפשר לבדוק את נוכחות ה-API באמצעות זיהוי תכונה פשוט:

const cacheAvailable = 'caches' in self;

תמיכה בדפדפנים

  • Chrome: ‏ 40.
  • Edge: ‏ 16.
  • Firefox: 41.
  • Safari: 11.1.

מקור

אפשר לגשת ל-Cache API מחלון, מ-iframe, מ-worker או מקובץ שירות (service worker).

מה אפשר לאחסן

במטמון מאוחסנים רק זוגות של אובייקטים מסוג Request ו-Response, שמייצגים בקשות HTTP ותגובות, בהתאמה. עם זאת, הבקשות והתשובות יכולות להכיל כל סוג של נתונים שאפשר להעביר באמצעות HTTP.

כמה אפשר לאחסן?

בקיצור, הרבה, לפחות כמה מאות מגה-בייט, ואולי גם מאות ג'יגה-בייט או יותר. ההטמעות בדפדפנים משתנות, אבל נפח האחסון הזמין מבוסס בדרך כלל על נפח האחסון הזמין במכשיר.

יצירה ופתיחה של מטמון

כדי לפתוח מטמון, משתמשים בשיטה caches.open(name) ומעבירים את שם המטמון כפרמטר יחיד. אם המטמון המכונה לא קיים, הוא נוצר. השיטה הזו מחזירה 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). הפעולה הזו דומה לקריאה ל-cache.add לכל בקשה בנפרד, אלא שה-Promise דוחה אם אף בקשה לא נשמרה במטמון.

const urls = ['/weather/today.json', '/weather/tomorrow.json'];
cache.addAll(urls);

בכל אחד מהמקרים האלה, רשומה חדשה מחליפה כל רשומה קיימת תואמת. המערכת משתמשת באותם כללי התאמה שמתוארים בקטע אחזור.

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

יוצרים את האובייקט Request באמצעות כתובת URL של הדבר שרוצים לאחסן:

const request = new Request('/my-data-store/item-id');

עבודה עם אובייקטים מסוג Response

ה-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 או שיטות 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 יכול להיות 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.