איך לחשוב על שירותי ה-Workers.
שירותי העבודה הם חזקים ומומלץ מאוד ללמוד אותם. הם מאפשרים לכם לספק למשתמשים רמה חדשה לגמרי של חוויה. האתר יכול להיטען מיידית. אפשר להשתמש בה באופליין. אפשר להתקין אותה כאפליקציה ספציפית לפלטפורמה, והיא תהיה מדויקת באותה מידה – אבל עם פוטנציאל החשיפה והחופש של האינטרנט.
אבל רוב מפתחי האינטרנט לא רגילים לעבוד עם שירותי עבודה. הן מגיעות עם זמן למידה ארוך ועם כמה בעיות שצריך להיזהר מהן.
לאחרונה שיתפת פעולה עם Google Developers בפרויקט – Service Workies – משחק חינמי להבנת קובצי שירות (service worker). במהלך היצירה שלו ועבודה עם כל הפרטים המורכבים של שירותי העבודה, נתקלתי בכמה בעיות. מה שעזר לי הכי הרבה היה למצוא כמה מטאפורות חזותיות. במאמר הזה נבחן את המודלים המנטליים האלה וננסה להבין את המאפיינים הפרדוקסליים שגורמים לשירותי העבודה להיות גם מורכבים וגם מדהימים.
אותו דבר, אבל שונה
בזמן הכתיבה של הקוד של ה-service worker, הרבה דברים ייראו מוכרים. אתם יכולים להשתמש בתכונות החדשות של שפת JavaScript שאתם אוהבים. אפשר להאזין לאירועים במחזור החיים בדיוק כמו לאירועים בממשק המשתמש. מנהלים את זרימת הבקרה באמצעות הבטחות כמו שאתם רגילים.
אבל יש התנהגויות אחרות של קובצי שירות (service worker) שמגרדות את הראש ומבלבלות אתכם. במיוחד אם מרעננים את הדף ולא רואים שהשינויים בקוד הוחלו.
שכבה חדשה
בדרך כלל, כשיוצרים אתר, צריך להתייחס רק לשתי שכבות: הלקוח והשרת. ה-service worker הוא שכבה חדש לגמרי שנמצא באמצע.
אפשר לחשוב על קובץ השירות כסוג של תוסף לדפדפן – תוסף שהאתר יכול להתקין בדפדפן של המשתמש. אחרי ההתקנה, ה-service worker מרחיב את הדפדפן של האתר בשכבת ביניים חזקה. שכבת ה-service worker הזו יכולה ליירט ולטפל בכל הבקשות שהאתר שולח.
לשכבת ה-service worker יש מחזור חיים משלה, בלתי תלוי בכרטיסייה בדפדפן. רענון פשוט של הדף לא מספיק כדי לעדכן את ה-service worker, בדיוק כמו שלא מצפים שרענון דף יעדכן קוד שנפרס בשרת. לכל שכבה יש כללים ייחודיים משלה לעדכון.
במשחק Service Workies אנחנו עוברים על הפרטים הרבים של מחזור החיים של שירות העבודה, ומספקים לכם המון תרגול בעבודה איתו.
כלי יעיל, אבל מוגבל
שימוש בקובץ שירות באתר שלכם טומן בחובו יתרונות רבים. האתר שלכם יכול:
- לפעול בצורה חלקה גם כשהמשתמש במצב אופליין.
- לשפר משמעותית את הביצועים באמצעות אחסון במטמון
- להשתמש בהתראות בדחיפה
- להיות מותקנת כ-PWA
למרות כל מה שאפשר לעשות עם שירותי עבודה, הם מוגבלים מבחינה מבנית. הם לא יכולים לעשות שום דבר באופן סינכרוני או באותו חוט כמו האתר שלכם. כלומר, אין גישה ל:
- localStorage
- ה-DOM
- החלון
החדשות הטובות הן שיש כמה דרכים שבהן הדף יכול לתקשר עם ה-service worker שלו, כולל postMessage
ישיר, ערוצי שליחת הודעות אחד-ל-אחד וערוצי שידור אחד-ל-רבים.
לטווח ארוך, אבל לטווח קצר
עובד שירות פעיל ממשיך לפעול גם אחרי שמשתמש יוצא מהאתר או סוגר את הכרטיסייה. הדפדפן שומר את קובץ השירות הזה כדי שהוא יהיה מוכן בפעם הבאה שהמשתמש יחזור לאתר. לפני שהבקשה הראשונה נשלחת, לקובץ השירות יש הזדמנות ליירט אותה ולקחת את השליטה בדף. כך האתר יכול לפעול במצב אופליין – קובץ השירות יכול להציג גרסה ששמורה במטמון של הדף עצמו, גם אם למשתמש אין חיבור לאינטרנט.
בService Workies אנחנו מדגימים את הקונספט הזה באמצעות Kolohe (עובד שירות ידידותי) שמנתב ומטפל בבקשות.
נעצר
למרות ששירותי ה-Worker נראים אלמוות, אפשר להפסיק אותם כמעט בכל שלב. הדפדפן לא רוצה לבזבז משאבים על קובץ שירות שלא עושה כרגע שום דבר. הפסקה היא לא סגירה – ה-service worker נשאר מותקן ומופעל. הוא רק עובר למצב שינה. בפעם הבאה שהוא יידרש (למשל, כדי לטפל בבקשה), הדפדפן יעיר אותו שוב.
waitUntil
בגלל האפשרות המתמדת להעברה למצב שינה, ל-service worker צריכה להיות דרך להודיע לדפדפן מתי הוא עושה משהו חשוב ולא רוצה לנמנם. כאן נכנס לתמונה event.waitUntil()
. השיטה הזו מרחיבה את מחזור החיים שבו הוא משמש, ומונעת ממנו להיפסק או לעבור לשלב הבא במחזור החיים עד שנהיה מוכנים. כך יש לנו זמן להגדיר מטמון, לאחזר משאבים מהרשת וכו'.
הדוגמה הזו מורה לדפדפן שההתקנה של ה-service worker לא הושלמה עד שהמטמון assets
נוצר ותמונה של חרב מאוכלסת בו:
self.addEventListener("install", event => {
event.waitUntil(
caches.open("assets").then(cache => {
return cache.addAll(["/weapons/sword/blade.png"]);
})
);
});
היזהרו ממצבים גלובליים
כשהפעלה/הפסקה הזו מתרחשת, ההיקף הגלובלי של ה-service worker מתאפס. לכן חשוב להיזהר ולא להשתמש במצב גלובלי ב-service worker, אחרת תהיה לכם בעיה בפעם הבאה שהוא יתעורר ותהיה לו סטטוס שונה ממה שציפיתם.
הנה דוגמה לשימוש במצב גלובלי:
const favoriteNumber = Math.random();
let hasHandledARequest = false;
self.addEventListener("fetch", event => {
console.log(favoriteNumber);
console.log(hasHandledARequest);
hasHandledARequest = true;
});
בכל בקשה, ה-service worker הזה ירשום ביומן מספר – נניח 0.13981866382421893
. גם המשתנה hasHandledARequest
ישתנה ל-true
. עכשיו ה-service worker לא פעיל במשך זמן מה, ולכן הדפדפן מפסיק אותו. בפעם הבאה שתהיה בקשה, יהיה צורך שוב ב-service worker, ולכן הדפדפן יעיר אותו. הסקריפט שלו ייבדק שוב. עכשיו hasHandledARequest
מתאפס ל-false
, ו-favoriteNumber
הוא ערך שונה לגמרי – 0.5907281835659033
.
אי אפשר להסתמך על מצב ששמור בקובץ השירות (service worker). בנוסף, יצירת מופעים של דברים כמו 'ערוצי הודעות' עלולה לגרום לבאגים: תקבלו מכונה חדשה לגמרי בכל פעם ש-service worker נעצר או מתחיל.
בפרק 3 בנושא קובצי שירות, אנחנו מדמיינים את קובץ השירות המושבת כשהוא מאבד את כל הצבעים בזמן שהוא מחכה להפעלה.
ביחד, אבל בנפרד
רק קובץ שירות אחד יכול לשלוט בדף בכל רגע נתון. עם זאת, אפשר להתקין בו שני שירותי עובדים בו-זמנית. כשמבצעים שינוי בקוד של קובץ השירות ומרעננים את הדף, לא עורכים את קובץ השירות בכלל. קובצי השירות (service workers) הם immutable. במקום זאת, אתם יוצרים חשבון חדש לגמרי. ה-service worker החדש (נקראים לו SW2) יותקן, אבל הוא עדיין לא יופעל. הוא צריך להמתין לסיום של ה-service worker הנוכחי (SW1) (כשהמשתמש יוצא מהאתר).
שינוי במטמון של שירות אחר
במהלך ההתקנה, SW2 יכול להגדיר דברים – בדרך כלל יצירת מטמון ויישוב שלו. אבל חשוב לזכור: ל-service worker החדש יש גישה לכל מה שיש ל-service worker הנוכחי גישה אליו. אם לא תהיו זהירים, קובץ השירות החדש שממתין יכול לבלבל את קובץ השירות הנוכחי. דוגמאות לפעולות שעלולות לגרום לבעיות:
- SW2 יכול למחוק מטמון ש-SW1 משתמש בו באופן פעיל.
- SW2 יכול לערוך את התוכן של מטמון שבו SW1 משתמש, וכתוצאה מכך SW1 יגיב עם נכסים שהדף לא מצפה להם.
דילוג על skipWaiting
קובץ שירות יכול גם להשתמש בשיטה המסוכנת skipWaiting()
כדי להשתלט על הדף ברגע שההתקנה שלו מסתיימת. בדרך כלל זו לא רעיון טוב, אלא אם אתם מנסים להחליף באופן מכוון service worker עם באגים. יכול להיות שקובץ ה-service worker החדש משתמש במשאבים מעודכנים שהדף הנוכחי לא מצפה להם, וכתוצאה מכך מתרחשות שגיאות ובאגים.
התחלה נקייה
כדי למנוע מ-service workers להפריע זה לזה, צריך לוודא שהם משתמשים במטמון שונה. הדרך הקלה ביותר לעשות זאת היא להוסיף גרסה לשמות המטמון שבהם הם משתמשים.
const version = 1;
const assetCacheName = `assets-${version}`;
self.addEventListener("install", event => {
caches.open(assetCacheName).then(cache => {
// confidently do stuff with your very own cache
});
});
כשפורסים קובץ שירות חדש, צריך להגדיל את הערך של version
כדי שהוא יבצע את הפעולות הנדרשות באמצעות מטמון נפרד לחלוטין מזה של קובץ השירות הקודם.
סיום נקי
כשה-service worker מגיע למצב activated
, סימן שהוא השתלט על העבודה ושה-service worker הקודם לא נדרש יותר. בשלב הזה חשוב לנקות אחרי ה-service worker הישן. הפתרון הזה לא רק מאפשר לכבד את מגבלות האחסון של המטמון של המשתמשים, אלא גם יכול למנוע באגים לא מכוונים.
השיטה caches.match()
היא קיצור דרך נפוץ לאחזור פריט מכל מטמון שבו יש התאמה. אבל הוא עובר על המטמון לפי הסדר שבו הוא נוצר. נניח שיש לכם שתי גרסאות של קובץ סקריפט app.js
בשני מטמון שונים – assets-1
ו-assets-2
. הדף מצפה לסקריפט החדש יותר שנשמר ב-assets-2
. אבל אם לא מוחקים את המטמון הישן, caches.match('app.js')
יחזיר את המטמון הישן מ-assets-1
, וסביר להניח שהאתר יתקלקל.
כדי לנקות אחרי שירותי עובדים קודמים, צריך רק למחוק את כל הנתונים ששמורים במטמון ולא נחוצים לשירות העובד החדש:
const version = 2;
const assetCacheName = `assets-${version}`;
self.addEventListener("activate", event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheName !== assetCacheName){
return caches.delete(cacheName);
}
});
);
});
);
});
כדי למנוע מקובצי השירות להפריע זה לזה, צריך קצת עבודה ומשמעת, אבל זה שווה את המאמץ.
הגישה לשימוש בקובצי שירות (service worker)
חשוב לחשוב על עובדי השירות בדרך הנכונה כדי שתוכלו ליצור אותם בביטחון. אחרי שתתרגלו את השימוש בהם, תוכלו ליצור חוויות משתמש מדהימות למשתמשים שלכם.
רוצים להבין את כל זה במשחק? מזל טוב! מומלץ לשחק בService Workies, שבו תלמדו את הדרכים של ה-service worker כדי להביס את החיות האופליין.