הערכת נפח האחסון הזמין

אמ;לק

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

if ('storage' in navigator && 'estimate' in navigator.storage) {
  navigator.storage.estimate().then(({usage, quota}) => {
    console.log(`Using ${usage} out of ${quota} bytes.`);
  });
}

אפליקציות אינטרנט מודרניות ואחסון נתונים

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

הסוג הראשון של נתונים, הנדרש כדי לטעון את אפליקציית האינטרנט, מורכב מ-HTML, מ-JavaScript, מ-CSS ומתמונות מסוימות. Service workers, יחד עם Cache Storage API, מספקים את התשתית הנדרשת לשמירת משאבי הליבה האלה, ולאחר מכן להשתמש בהם מאוחר יותר כדי לטעון במהירות את אפליקציית האינטרנט, באופן אידיאלי על ידי עקיפה מוחלטת של הרשת. (כלים שמשולבים בתהליך ה-build של אפליקציית האינטרנט, כמו הספריות החדשות של Workbox או הספריות הישנות יותר של sw-precache, יכולים להפוך את תהליך האחסון, העדכון והשימוש בנתונים האלה לאוטומטי לחלוטין).

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

בעבר: window.webkitStorageInfo ו-navigator.webkitTemporaryStorage

בעבר, דפדפנים תמכו בסוג הזה של בדיקה עצמית באמצעות ממשקים עם קידומת, כמו window.webkitStorageInfo (הישן מאוד והלא תקף) ו-navigator.webkitTemporaryStorage (לא כל כך ישן, אבל עדיין לא סטנדרטי). הממשקים האלה סיפקו מידע שימושי, אבל אין להם עתיד כסטנדרטים של אינטרנט.

כאן נכנס לתמונה תקן האחסון של WHATWG.

העתיד: navigator.storage

כחלק מהעבודה המתמשכת על Storage Living Standard, כמה ממשקי API שימושיים נוספו לממשק StorageManager, שנחשף לדפדפנים בתור navigator.storage. בדומה לממשקי API אחרים של אינטרנט מהדור חדש, navigator.storage זמין רק במקורות מאובטחים (שמוצגים דרך HTTPS או localhost).

בשנה שעברה השקנו את השיטה navigator.storage.persist(), שמאפשרת לאפליקציית האינטרנט לבקש שהאחסון שלה יהיה פטור מניקוי אוטומטי.

עכשיו מצטרפת אליה השיטה navigator.storage.estimate(), שמשמשת כתחליף מודרני ל-navigator.webkitTemporaryStorage.queryUsageAndQuota(). הפונקציה estimate() מחזירה מידע דומה, אבל היא חושפת ממשק מבוסס-הבטחה, בהתאם לממשקי API אסינכררוניים מודרניים אחרים. ההבטחה שמוחזרת על ידי estimate() מתקבלת כאובייקט שמכיל שני מאפיינים: usage, שמייצג את מספר הבייטים שבשימוש כרגע, ו-quota, שמייצג את מספר הבייטים המקסימלי שאפשר לאחסן במקור הנוכחי. (כמו כל דבר אחר שקשור לאחסון, המכסה חלה על המקור כולו).

אם אפליקציית אינטרנט תנסה לאחסן נתונים גדולים מספיק כדי לגרום למקור מסוים לחרוג מהמכסה הזמינה שלו (למשל, באמצעות IndexedDB או Cache Storage API), הבקשה תיכשל עם חריגה מסוג QuotaExceededError.

אומדני אחסון בפעולה

אופן השימוש ב-estimate() תלוי בסוג הנתונים שהאפליקציה צריכה לאחסן. לדוגמה, תוכלו לעדכן רכיב בקרה בממשק כדי להודיע למשתמשים כמה מקום נותר אחרי כל פעולת אחסון. לאחר מכן, מומלץ לספק ממשק שמאפשר למשתמשים לנקות באופן ידני נתונים שכבר לא נחוצים. אפשר לכתוב קוד בערך כך:

// For a primer on async/await, see
// https://developers.google.com/web/fundamentals/getting-started/primers/async-functions
async function storeDataAndUpdateUI(dataUrl) {
  // Pro-tip: The Cache Storage API is available outside of service workers!
  // See https://googlechrome.github.io/samples/service-worker/window-caches/
  const cache = await caches.open('data-cache');
  await cache.add(dataUrl);

  if ('storage' in navigator && 'estimate' in navigator.storage) {
    const {usage, quota} = await navigator.storage.estimate();
    const percentUsed = Math.round(usage / quota * 100);
    const usageInMib = Math.round(usage / (1024 * 1024));
    const quotaInMib = Math.round(quota / (1024 * 1024));

    const details = `${usageInMib} out of ${quotaInMib} MiB used (${percentUsed}%)`;

    // This assumes there's a <span id="storageEstimate"> or similar on the page.
    document.querySelector('#storageEstimate').innerText = details;
  }
}

עד כמה האומדן מדויק?

ברור שהנתונים שמקבלים מהפונקציה הם רק אומדן של נפח האחסון שמשתמשים בו במקור. הוא מופיע בשם הפונקציה. הערכים של usage ושל quota לא אמורים להיות יציבים, לכן מומלץ להביא בחשבון את הדברים הבאים:

  • הערך של usage משקף את מספר הבייטים שמקור נתונים נתון משתמש בהם בפועל לנתונים מאותו מקור (same-origin). הערך הזה עשוי להשתנות בהתאם לשיטות דחיסה פנימיות, לגודל קבוע של בלוקים שמוקצים לנתונים שעשויים לכלול שטח לא מנוצל, ולנוכחות של רשומות 'מצבות' שעשויות להיווצר באופן זמני לאחר מחיקה. כדי למנוע זליגה של מידע מדויק לגבי הגודל, משאבים אטומים שמגיעים ממקורות שונים וששמורים באופן מקומי עשויים להוסיף עוד בייטים של מילוי (padding) לערך הכולל של usage.
  • הערך של quota משקף את נפח האחסון ששמור כרגע למקור. הערך תלוי בגורמים קבועים מסוימים, כמו נפח האחסון הכולל, אבל גם במספר גורמים שעשויים להיות תנודתיים, כולל נפח האחסון שלא בשימוש כרגע. לכן, כאשר אפליקציות אחרות במכשיר כותבות או מוחקות נתונים, סביר להניח שגודל המרחב שהדפדפן מוכן להקצות למקור של אפליקציית האינטרנט ישתנה.

המצב הנוכחי: זיהוי תכונות וחלופות

estimate() מופעל כברירת מחדל החל מגרסה 61 של Chrome. אנחנו ב-Firefox מבצעים ניסויים עם navigator.storage, אבל נכון לאוגוסט 2017 הוא לא מופעל כברירת מחדל. כדי לבדוק את ההגדרה הזו, צריך להפעיל את ההעדפה dom.storageManager.enabled.

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

function storageEstimateWrapper() {
  if ('storage' in navigator && 'estimate' in navigator.storage) {
    // We've got the real thing! Return its response.
    return navigator.storage.estimate();
  }

  if ('webkitTemporaryStorage' in navigator &&
      'queryUsageAndQuota' in navigator.webkitTemporaryStorage) {
    // Return a promise-based wrapper that will follow the expected interface.
    return new Promise(function(resolve, reject) {
      navigator.webkitTemporaryStorage.queryUsageAndQuota(
        function(usage, quota) {resolve({usage: usage, quota: quota})},
        reject
      );
    });
  }

  // If we can't estimate the values, return a Promise that resolves with NaN.
  return Promise.resolve({usage: NaN, quota: NaN});
}