איך לחשוב על שירותי ה-Workers.
קובצי שירות (service worker) הם עוצמתיים ובהחלט כדאי להם ללמוד. הם מאפשרים לכם לספק למשתמשים רמה חדשה לגמרי של חוויה. האתר יכול להיטען מיידית. היא יכולה לעבוד אופליין. אפשר להתקין אותה כאפליקציה ספציפית לפלטפורמה, והיא תהיה מדויקת ומלוטשת באותה מידה – אבל עם פוטנציאל החשיפה והחופש של האינטרנט.
אבל רוב מפתחי האינטרנט לא רגילים לעבוד עם שירותי עבודה. הן מגיעות עם זמן למידה ארוך ועם כמה בעיות שצריך להיזהר מהן.
לאחרונה שיתפת פעולה עם Google Developers בפרויקט – Service Workies – משחק חינמי להבנת קובצי שירות (service worker). במהלך הבנייה והעבודה עם עובדי השירות השונים, נתקלתי בכמה תקלות. הדבר שהכי עזר לי היה לחשוב על כמה מטאפורות תיאוריות. במאמר הזה נבחן את המודלים המנטליים האלה וננסה להבין את המאפיינים הפרדוקסליים שגורמים לשירותי העבודה להיות גם מורכבים וגם מדהימים.
אותו דבר, אבל שונה
בזמן הכתיבה של הקוד של ה-service worker, הרבה דברים ייראו מוכרים. אתם יכולים להשתמש בתכונות החדשות של שפת JavaScript שאתם אוהבים. אפשר להאזין לאירועים במחזור החיים בדיוק כמו לאירועים בממשק המשתמש. מנהלים את זרימת הבקרה באמצעות הבטחות, כמו שאתם רגילים.
אבל יש התנהגויות אחרות של קובצי שירות (service worker) שמגרדות את הראש ומבלבלות אתכם. במיוחד כאשר מרעננים את הדף ולא רואים את שינויי הקוד מוחלים.
שכבה חדשה
בדרך כלל, כשיוצרים אתר, צריך להתייחס רק לשתי שכבות: הלקוח והשרת. ה-service worker הוא שכבה חדש לגמרי שנמצא באמצע.
אפשר לחשוב על קובץ השירות כסוג של תוסף לדפדפן – תוסף שהאתר יכול להתקין בדפדפן של המשתמש. לאחר ההתקנה, Service Worker מרחיב את הדפדפן לאתר באמצעות שכבה אמצעית עוצמתית. שכבת ה-service worker הזו יכולה ליירט ולטפל בכל הבקשות שהאתר שולח.
לשכבת ה-service worker יש מחזור חיים משלה, בלתי תלוי בכרטיסייה בדפדפן. רענון פשוט של הדף לא מספיק כדי לעדכן את ה-service worker, בדיוק כמו שלא מצפים שרענון דף יעדכן קוד שנפרס בשרת. לכל שכבה יש כללים ייחודיים משלה לעדכון.
במשחק Service Workies אנחנו עוברים על הפרטים הרבים של מחזור החיים של שירות העבודה, ומספקים לכם המון תרגול בעבודה איתו.
כלי יעיל, אבל מוגבל
שימוש ב-Service Worker באתר שלכם מספק יתרונות מדהימים. האתר שלכם יכול:
- לפעול בצורה חלקה גם כשהמשתמש במצב אופליין.
- לשפר משמעותית את הביצועים באמצעות אחסון במטמון
- להשתמש בהתראות
- להיות מותקנת כ-PWA
למרות כל מה שאפשר לעשות עם שירותי עבודה, הם מוגבלים מבחינה מבנית. הם לא יכולים לעשות שום דבר באופן סינכרוני או באותו חוט כמו האתר שלכם. כלומר, אין גישה ל:
- localStorage
- ה-DOM
- החלון
החדשות הטובות הן שיש כמה דרכים שבהן הדף יכול לתקשר עם ה-service worker שלו, כולל postMessage
ישיר, ערוצי הודעות אחד-ל-אחד וערוצי שידור אחד-ל-רבים.
לטווח ארוך אבל לטווח קצר
עובד שירות פעיל ממשיך לפעול גם אחרי שמשתמש יוצא מהאתר או סוגר את הכרטיסייה. הדפדפן שומר את קובץ השירות הזה כדי שהוא יהיה מוכן בפעם הבאה שהמשתמש יחזור לאתר. לפני שליחת הבקשה הראשונה, ה-Service Worker מקבל הזדמנות ליירט אותו ולשלוט בדף. כך האתר יכול לפעול במצב אופליין – קובץ השירות יכול להציג גרסה ששמורה במטמון של הדף עצמו, גם אם למשתמש אין חיבור לאינטרנט.
ב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, אחרת תהיה לכם בעיה בפעם הבאה שהוא יתעורר ותהיה לו מצב שונה ממה שציפיתם.
הנה דוגמה לשימוש במצב גלובלי:
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 worker) 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
, וסביר להניח שהאתר יתקלקל.
כל מה שצריך כדי לנקות אחרי שעובדי השירות הקודמים צריכים למחוק את המטמון שלא נדרש ל-Service Worker החדש:
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 כדי להביס את החיות האופליין.