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

ג'ף פוזניק
ג'ף פוזניק

tl;dr

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

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 Storage Standard.

העתיד: 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 – של המספר המקסימלי של הבייטים שאפשר לאחסן ב-origin הנוכחי. (כמו כל דבר אחר שקשור לאחסון, גם המכסה חלה על כל המקורות).

אם אפליקציית אינטרנט מנסה לאחסן נתונים (לדוגמה, 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 משקף את מספר הבייטים שמקור נתון משתמש בפועל בנתונים של אותו מקור, והם עשויים להיות מושפעים משיטות דחיסה פנימיות, מבלוקי הקצאה בגודל קבוע שעשויים לכלול שטח לא בשימוש, ומהנוכחות של רשומות"tombstone" שעשויות להיווצר באופן זמני לאחר המחיקה. כדי למנוע דליפה של מידע בגודל מדויק, משאבים אטומים שנשמרו באופן מקומי עשויים לתרום עוד בייטים של מרווח פנימי לערך הכולל של usage.
  • quota משקף את נפח האחסון ששמור כרגע למקור. הערך תלוי בכמה גורמים קבועים, כמו הגודל הכולל של האחסון, אבל גם בכמה גורמים שיכולים להיות תנודתיים, כולל גודל נפח האחסון שלא נמצא כרגע בשימוש. לכן, כשאפליקציות אחרות במכשיר כותבות או מוחקות נתונים, כמות השטח שהדפדפן מוכן להקדיש למקור של אפליקציית האינטרנט שלכם עשויה להשתנות.

ההווה: זיהוי תכונות וחלופות

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

כשעובדים עם פונקציונליות שעדיין לא נתמכת בכל הדפדפנים, חובה לזהות תכונות. אפשר לשלב את זיהוי התכונות עם wrapper המבוסס על הבטחה, בנוסף לשיטות הישנות של 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});
}