שיטות מומלצות לשימוש ב-IndexedDB

שיטות מומלצות לסנכרון מצבי אפליקציות בין IndexedDB לספריות פופולריות לניהול מצבים.

פיליפ וולטון
פיליפ וולטון

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

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

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

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

איך לוודא שהאפליקציה שלכם צפויה

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

לא ניתן לאחסן הכול ב-IndexedDB בכל הפלטפורמות

אם מאחסנים קבצים גדולים שנוצרו על ידי משתמשים, כמו תמונות או סרטונים, כדאי לנסות לאחסן אותם כאובייקטים File או Blob. הפעולה הזו תפעל בפלטפורמות מסוימות, אבל תיכשל בפלטפורמות אחרות. באופן ספציפי, Safari ב-iOS לא יכול לאחסן Blobs ב-IndexedDB.

למרבה המזל, לא קשה להמיר Blob ל-ArrayBuffer, ולהפך. יש תמיכה רבה באחסון של ArrayBuffers ב-IndexedDB.

עם זאת, חשוב לזכור של-Blob יש סוג MIME ואילו ל-ArrayBuffer אין אותו. כדי לבצע את ההמרה כמו שצריך, צריך לאחסן את הסוג לצד המאגר.

כדי להמיר ArrayBuffer ל-Blob, פשוט משתמשים בבנאי Blob.

function arrayBufferToBlob(buffer, type) {
  return new Blob([buffer], { type: type });
}

הכיוון השני קצת יותר מעורב, והוא תהליך אסינכרוני. אפשר להשתמש באובייקט FileReader כדי לקרוא את ה-blob כ-ArrayBuffer. בסיום הקריאה, אירוע loadend מופעל בקורא. אפשר לסיים את התהליך הזה ב-Promise כך:

function blobToArrayBuffer(blob) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.addEventListener('loadend', () => {
      resolve(reader.result);
    });
    reader.addEventListener('error', reject);
    reader.readAsArrayBuffer(blob);
  });
}

הכתיבה לאחסון עלולה להיכשל

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

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

כדי לזהות שגיאות בפעולות IndexedDB, אפשר להוסיף גורם מטפל באירועים עבור האירוע error בכל פעם שיוצרים אובייקט IDBDatabase, IDBTransaction או IDBRequest.

const request = db.open('example-db', 1);
request.addEventListener('error', (event) => {
  console.log('Request error:', request.error);
};

ייתכן שנתונים שמורים שונו או נמחקו על ידי המשתמש

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

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

ייתכן שהנתונים המאוחסנים לא עדכניים

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

ב-IndexedDB יש תמיכה מובנית בגרסאות סכימה ובשדרוג באמצעות השיטה IDBOpenDBRequest.onupgradeneeded(). עם זאת, עדיין צריך לכתוב את קוד השדרוג כך שיוכל לטפל במשתמש שמגיע מגרסה קודמת (כולל גרסה עם באג).

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

שמירה על הביצועים של האפליקציה

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

באופן כללי, נפח הקריאה והכתיבה ב-IndexedDB לא אמור להיות גדול מהנדרש כדי לגשת לנתונים.

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

כך מתכננים איך לשמור את מצב האפליקציה ב-IndexedDB, כי רוב ספריות ניהול המצב הפופולריות (כמו Redux) פועלות על ידי ניהול עץ המצבים כאובייקט JavaScript יחיד.

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

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

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

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

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

מסקנות

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

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

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