چرخه عمر کارگر خدماتی

چرخه زندگی کارگر خدماتی پیچیده ترین بخش آن است. اگر ندانید سعی دارد چه کاری انجام دهد و چه فوایدی دارد، ممکن است احساس کنید که دارد با شما می جنگد. اما هنگامی که بدانید چگونه کار می کند، می توانید به روز رسانی های یکپارچه و بدون مزاحمت را به کاربران ارائه دهید و بهترین الگوهای وب و بومی را ترکیب کنید.

این یک شیرجه عمیق است، اما گلوله‌های ابتدای هر بخش بیشتر آنچه را که باید بدانید را پوشش می‌دهد.

قصد

هدف چرخه حیات این است که:

  • آفلاین را ممکن کنید.
  • به یک سرویس‌کار جدید اجازه دهید بدون ایجاد اختلال در سرویس فعلی، خود را آماده کند.
  • اطمینان حاصل کنید که یک صفحه در محدوده توسط همان سرویس دهنده (یا هیچ سرویس دهنده) در سراسر آن کنترل می شود.
  • اطمینان حاصل کنید که فقط یک نسخه از سایت شما در یک زمان اجرا می شود.

این آخری خیلی مهم است. بدون سرویس‌دهندگان، کاربران می‌توانند یک برگه را در سایت شما بارگذاری کنند، سپس یکی دیگر را باز کنند. این می تواند منجر به اجرای همزمان دو نسخه از سایت شما شود. گاهی اوقات این مشکلی ندارد، اما اگر با فضای ذخیره‌سازی سروکار دارید، می‌توانید به راحتی با دو برگه نظرات متفاوتی در مورد نحوه مدیریت فضای ذخیره‌سازی مشترکشان داشته باشید. این می تواند منجر به خطا یا بدتر از دست رفتن داده شود.

اولین کارگر خدماتی

به طور خلاصه:

  • رویداد install اولین رویدادی است که یک سرویس‌گر دریافت می‌کند و فقط یک بار اتفاق می‌افتد.
  • وعده ای که به installEvent.waitUntil() داده می شود، مدت زمان و موفقیت یا شکست نصب شما را نشان می دهد.
  • تا زمانی که نصب با موفقیت تمام نشود و «فعال» نشود، یک سرویس‌کار رویدادهایی مانند fetch و push دریافت نمی‌کند.
  • به‌طور پیش‌فرض، واکشی‌های صفحه از طریق یک سرویس‌کار انجام نمی‌شود، مگر اینکه درخواست صفحه از طریق یک سرویس‌کار انجام شود. بنابراین باید صفحه را به‌روزرسانی کنید تا تأثیرات سرویس‌کار را ببینید.
  • clients.claim() می‌تواند این پیش‌فرض را نادیده بگیرد و صفحات غیر کنترل‌شده را کنترل کند.

این HTML را بگیرید:

<!DOCTYPE html>
An image will appear here in 3 seconds:
<script>
  navigator.serviceWorker.register('/sw.js')
    .then(reg => console.log('SW registered!', reg))
    .catch(err => console.log('Boo!', err));

  setTimeout(() => {
    const img = new Image();
    img.src = '/dog.svg';
    document.body.appendChild(img);
  }, 3000);
</script>

یک کارگر خدمات را ثبت می کند و بعد از 3 ثانیه تصویر یک سگ را اضافه می کند.

در اینجا کارگر خدمات آن، sw.js است:

self.addEventListener('install', event => {
  console.log('V1 installing…');

  // cache a cat SVG
  event.waitUntil(
    caches.open('static-v1').then(cache => cache.add('/cat.svg'))
  );
});

self.addEventListener('activate', event => {
  console.log('V1 now ready to handle fetches!');
});

self.addEventListener('fetch', event => {
  const url = new URL(event.request.url);

  // serve the cat SVG from the cache if the request is
  // same-origin and the path is '/dog.svg'
  if (url.origin == location.origin && url.pathname == '/dog.svg') {
    event.respondWith(caches.match('/cat.svg'));
  }
});

تصویری از یک گربه را در حافظه پنهان ذخیره می کند و هر زمان که درخواستی برای /dog.svg وجود داشته باشد، آن را ارائه می دهد. با این حال، اگر مثال بالا را اجرا کنید ، اولین باری که صفحه را بارگذاری می‌کنید، یک سگ را مشاهده خواهید کرد. رفرش را بزنید، گربه را خواهید دید.

دامنه و کنترل

دامنه پیش‌فرض ثبت‌نام کارگر سرویس ./ نسبت به URL اسکریپت است. این بدان معناست که اگر یک سرویس‌کار را در //example.com/foo/bar.js ثبت کنید، محدوده پیش‌فرض آن //example.com/foo/ است.

ما صفحات، کارگران و کارگران اشتراکی را clients می نامیم. کارمند خدمات شما فقط می تواند مشتریانی را کنترل کند که در محدوده هستند. هنگامی که یک مشتری "کنترل می شود"، واکشی های آن از طریق کارگر خدمات داخلی انجام می شود. می‌توانید تشخیص دهید که آیا یک کلاینت از طریق navigator.serviceWorker.controller کنترل می‌شود که تهی است یا یک نمونه سرویس‌کار.

دانلود، تجزیه و اجرا کنید

اولین سرویس کار شما با فراخوانی .register() دانلود می شود. اگر اسکریپت شما نتواند دانلود، تجزیه و یا خطا در اجرای اولیه خود ایجاد کند، وعده ثبات رد می‌شود و سرویس‌کار کنار گذاشته می‌شود.

DevTools کروم خطا را در کنسول و در بخش Service Worker تب برنامه نشان می دهد:

خطا در برگه DevTools service worker نمایش داده شد

نصب

اولین رویدادی که یک سرویس دهنده دریافت می کند install است. به محض اجرای کارگر فعال می‌شود و برای هر سرویس‌گر فقط یک بار فراخوانی می‌شود. اگر اسکریپت Service Worker خود را تغییر دهید، مرورگر آن را یک سرویس‌کار دیگر در نظر می‌گیرد و رویداد install خود را دریافت می‌کند. بعداً به‌روزرسانی‌ها را با جزئیات پوشش خواهم داد.

رویداد install فرصتی است که قبل از اینکه بتوانید کلاینت‌ها را کنترل کنید، همه چیزهایی را که نیاز دارید ذخیره کنید. قولی که به event.waitUntil() می‌دهید به مرورگر اجازه می‌دهد تا از اتمام نصب شما و موفقیت آمیز بودن آن مطلع شود.

اگر وعده شما رد شود، این نشان می‌دهد که نصب ناموفق بوده و مرورگر سرویس‌کار را دور می‌اندازد. هرگز مشتریان را کنترل نخواهد کرد. این بدان معناست که ما نمی‌توانیم به حضور cat.svg در حافظه پنهان در رویدادهای fetch خود تکیه کنیم. این یک وابستگی است.

فعال کنید

هنگامی که کارمند خدمات شما برای کنترل مشتریان و مدیریت رویدادهای کاربردی مانند push و sync آماده شد، یک رویداد activate دریافت خواهید کرد. اما این بدان معنا نیست که صفحه ای که .register() نامیده می شود کنترل خواهد شد.

اولین باری که نسخه آزمایشی را بارگیری می‌کنید، حتی اگر dog.svg مدت‌ها پس از فعال‌سازی سرویس‌دهنده درخواست می‌شود، درخواست را رسیدگی نمی‌کند و همچنان تصویر سگ را می‌بینید. پیش‌فرض سازگاری است، اگر صفحه شما بدون سرویس‌کار بارگیری شود، منابع فرعی آن نیز این کار را نمی‌کنند. اگر نسخه نمایشی را برای بار دوم بارگذاری کنید (به عبارت دیگر، صفحه را تازه سازی کنید)، کنترل می شود. هم صفحه و هم تصویر از رویدادهای fetch عبور می کنند و در عوض یک گربه را می بینید.

مشتریان.دعا

پس از فعال شدن می توانید با فراخوانی clients.claim() در سرویس کار خود کنترل کلاینت های کنترل نشده را در دست بگیرید.

در اینجا یک نسخه از نسخه ی نمایشی است که در بالای آن clients.claim() را در رویداد activate آن فراخوانی می کند. شما باید اولین بار یک گربه را ببینید. من می گویم "باید"، زیرا این به زمان حساس است. فقط در صورتی گربه را خواهید دید که سرویس‌کار فعال شود و clients.claim() قبل از بارگذاری تصویر اعمال شود.

اگر از Service Worker خود برای بارگیری صفحات متفاوت با بارگیری آنها از طریق شبکه استفاده می کنید، clients.claim() می تواند مشکل ساز باشد، زیرا سرویس دهنده شما در نهایت برخی از کلاینت هایی را که بدون آن بارگذاری شده اند کنترل می کند.

به روز رسانی کارگر خدمات

به طور خلاصه:

  • در صورت بروز هر یک از موارد زیر، به روز رسانی فعال می شود:
    • پیمایش به یک صفحه در محدوده.
    • رویدادهای کاربردی مانند push و sync ، مگر اینکه در 24 ساعت گذشته بررسی به‌روزرسانی انجام شده باشد.
    • فقط در صورتی که URL کارگر سرویس تغییر کرده باشد، .register() را فراخوانی می کند. با این حال، باید از تغییر URL کارگر خودداری کنید .
  • اکثر مرورگرها، از جمله Chrome 68 و نسخه‌های جدیدتر ، به‌طور پیش‌فرض هنگام بررسی به‌روزرسانی‌های اسکریپت سرویس‌کار ثبت‌شده، سرصفحه‌های ذخیره‌سازی پنهان را نادیده می‌گیرند. آنها همچنان هنگام واکشی منابع بارگذاری شده در یک سرویس دهنده از طریق importScripts() به هدرهای کش احترام می گذارند. می‌توانید با تنظیم گزینه updateViaCache هنگام ثبت نام سرویس‌کار خود، این رفتار پیش‌فرض را لغو کنید.
  • اگر سرویس‌کار شما با بایتی که قبلاً مرورگر دارد متفاوت باشد، به‌روزرسانی شده در نظر گرفته می‌شود. (ما این را گسترش می دهیم تا اسکریپت ها / ماژول های وارداتی را نیز شامل شود.)
  • سرویس کارگر به روز شده در کنار سرویس موجود راه اندازی می شود و رویداد install خود را دریافت می کند.
  • اگر کارگر جدید شما یک کد وضعیت غیر ok داشته باشد (مثلاً 404)، تجزیه نشود، در حین اجرا خطایی ایجاد کند یا در حین نصب رد کند، کارگر جدید دور ریخته می‌شود، اما کد فعلی فعال باقی می‌ماند.
  • هنگامی که با موفقیت نصب شد، کارگر به روز شده wait می ماند تا کارگر موجود صفر مشتری را کنترل کند. (توجه داشته باشید که کلاینت‌ها در حین به‌روزرسانی با هم همپوشانی دارند.)
  • self.skipWaiting() از انتظار جلوگیری می‌کند، به این معنی که سرویس‌کار به محض اتمام نصب فعال می‌شود.

فرض کنید اسکریپت کارگر خدماتی خود را تغییر دادیم تا با تصویری از اسب به جای گربه پاسخ دهیم:

const expectedCaches = ['static-v2'];

self.addEventListener('install', event => {
  console.log('V2 installing…');

  // cache a horse SVG into a new cache, static-v2
  event.waitUntil(
    caches.open('static-v2').then(cache => cache.add('/horse.svg'))
  );
});

self.addEventListener('activate', event => {
  // delete any caches that aren't in expectedCaches
  // which will get rid of static-v1
  event.waitUntil(
    caches.keys().then(keys => Promise.all(
      keys.map(key => {
        if (!expectedCaches.includes(key)) {
          return caches.delete(key);
        }
      })
    )).then(() => {
      console.log('V2 now ready to handle fetches!');
    })
  );
});

self.addEventListener('fetch', event => {
  const url = new URL(event.request.url);

  // serve the horse SVG from the cache if the request is
  // same-origin and the path is '/dog.svg'
  if (url.origin == location.origin && url.pathname == '/dog.svg') {
    event.respondWith(caches.match('/horse.svg'));
  }
});

یک نسخه ی نمایشی از موارد بالا را بررسی کنید . هنوز باید تصویر یک گربه را ببینید. در اینجا دلیل…

نصب

توجه داشته باشید که من نام کش را از static-v1 به static-v2 تغییر داده ام. این بدان معناست که می‌توانم کش جدید را بدون بازنویسی چیزهایی در حافظه فعلی که سرویس‌کار قدیمی هنوز از آن استفاده می‌کند، تنظیم کنم.

این الگوها حافظه پنهان مخصوص نسخه را ایجاد می‌کند، شبیه به دارایی‌هایی که یک برنامه بومی با فایل اجرایی خود همراه می‌کند. همچنین ممکن است کش هایی داشته باشید که مختص نسخه نیستند، مانند avatars .

در انتظار

پس از اینکه با موفقیت نصب شد، سرویس‌کار به‌روزرسانی شده فعال‌سازی را به تأخیر می‌اندازد تا زمانی که سرویس‌کار موجود دیگر مشتریان را کنترل نمی‌کند. این حالت "انتظار" نامیده می شود و به این صورت است که مرورگر اطمینان می دهد که فقط یک نسخه از سرویس دهنده شما در یک زمان اجرا می شود.

اگر نسخه نمایشی به روز شده را اجرا کردید، همچنان باید تصویر یک گربه را ببینید، زیرا کارگر V2 هنوز فعال نشده است. می‌توانید سرویس‌کار جدید منتظر را در برگه «برنامه» DevTools ببینید:

DevTools که سرویس‌کار جدید را در انتظار نشان می‌دهد

حتی اگر فقط یک برگه برای نسخه نمایشی باز داشته باشید، باز کردن صفحه کافی نیست تا اجازه دهید نسخه جدید کنترل شود. این به دلیل نحوه عملکرد ناوبری مرورگر است. هنگامی که پیمایش می‌کنید، صفحه فعلی تا زمانی که سرصفحه‌های پاسخ دریافت نشده است، از بین نمی‌رود، و حتی در آن صورت اگر پاسخ دارای سرصفحه Content-Disposition باشد، صفحه فعلی ممکن است باقی بماند. به دلیل این همپوشانی، سرویس‌کار فعلی همیشه مشتری را در حین به‌روزرسانی کنترل می‌کند.

برای دریافت به‌روزرسانی، با استفاده از سرویس‌کار فعلی، همه برگه‌ها را ببندید یا از آن‌ها دور شوید. سپس، هنگامی که دوباره به نسخه آزمایشی هدایت می‌شوید ، باید اسب را ببینید.

این الگو شبیه نحوه به‌روزرسانی Chrome است. به‌روزرسانی‌ها برای بارگیری Chrome در پس‌زمینه، اما تا زمانی که Chrome راه‌اندازی مجدد نشود اعمال نمی‌شود. در عین حال، می توانید بدون اختلال از نسخه فعلی استفاده کنید. با این حال، این مشکل در طول توسعه است، اما DevTools راه‌هایی برای آسان‌تر کردن آن دارد که در ادامه این مقاله به آن‌ها خواهم پرداخت.

فعال کنید

زمانی که سرویس‌کار قدیمی از بین برود، این کار اخراج می‌شود و سرویس‌کار جدید شما می‌تواند مشتریان را کنترل کند. این زمان ایده‌آل برای انجام کارهایی است که نمی‌توانید در زمانی که کارگر قدیمی هنوز در حال استفاده بود، انجام دهید، مانند انتقال پایگاه‌های داده و پاک کردن حافظه پنهان.

در نسخه ی نمایشی بالا، من فهرستی از کش هایی را که انتظار دارم وجود داشته باشند، نگه می دارم، و در رویداد activate ، از شر هر نوع دیگری خلاص می شوم که کش قدیمی static-v1 را حذف می کند.

اگر وعده ای را به event.waitUntil() بفرستید، رویدادهای عملکردی ( fetch ، push ، sync و غیره) را تا زمانی که وعده حل شود بافر می کند. بنابراین وقتی fetch واکشی شما فعال می شود، فعال سازی کاملاً کامل می شود.

از مرحله انتظار بگذرید

مرحله انتظار به این معنی است که شما فقط یک نسخه از سایت خود را به طور همزمان اجرا می کنید، اما اگر به آن ویژگی نیاز ندارید، می توانید با فراخوانی self.skipWaiting() سرویس کارگر جدید خود را زودتر فعال کنید.

این باعث می‌شود که کارگر خدماتی شما کارگر فعال فعلی را اخراج کند و به محض ورود به مرحله انتظار (یا اگر قبلاً در مرحله انتظار باشد، بلافاصله خود را فعال کند). این باعث نمی شود که کارگر شما از نصب صرف نظر کند، فقط منتظر است.

زمانی که skipWaiting() تماس می گیرید، مهم نیست، تا زمانی که در حین یا قبل از انتظار باشد. فراخوانی آن در رویداد install بسیار معمول است:

self.addEventListener('install', event => {
  self.skipWaiting();

  event.waitUntil(
    // caching etc
  );
});

اما ممکن است بخواهید آن را به عنوان نتیجه یک postMessage() به سرویس‌کار فراخوانی کنید. همانطور که در اینجا، شما می خواهید به skipWaiting() پس از تعامل کاربر.

در اینجا یک نسخه نمایشی است که از skipWaiting() استفاده می کند . شما باید تصویری از یک گاو را بدون نیاز به دور زدن ببینید. مانند clients.claim() این یک مسابقه است، بنابراین شما فقط در صورتی گاو را خواهید دید که کارگر سرویس جدید قبل از اینکه صفحه تلاش کند تصویر را بارگذاری کند، واکشی، نصب و فعال کند.

به روز رسانی های دستی

همانطور که قبلاً اشاره کردم، مرورگر به‌روزرسانی‌ها را به‌طور خودکار پس از پیمایش و رویدادهای کاربردی بررسی می‌کند، اما می‌توانید آنها را به صورت دستی نیز فعال کنید:

navigator.serviceWorker.register('/sw.js').then(reg => {
  // sometime later…
  reg.update();
});

اگر انتظار دارید کاربر برای مدت طولانی بدون بارگیری مجدد از سایت شما استفاده کند، ممکن است بخواهید در یک بازه زمانی (مانند ساعتی) update() را فراخوانی کنید.

از تغییر URL اسکریپت سرویس کارمند خود خودداری کنید

اگر پست من را در مورد بهترین شیوه های ذخیره سازی در حافظه پنهان خوانده اید، ممکن است به هر نسخه از سرویس دهنده خود یک URL منحصر به فرد بدهید. این کار را نکن! این معمولاً برای کارکنان خدمات بد است، فقط اسکریپت را در مکان فعلی خود به روز کنید.

می تواند شما را با مشکلی مانند زیر مواجه کند:

  1. index.html sw-v1.js به عنوان یک سرویس دهنده ثبت می کند.
  2. sw-v1.js ذخیره می کند و index.html ارائه می دهد، بنابراین ابتدا به صورت آفلاین کار می کند.
  3. شما index.html را به روز می کنید تا sw-v2.js جدید و درخشان شما را ثبت کند.

اگر موارد فوق را انجام دهید، کاربر هرگز sw-v2.js دریافت نمی کند، زیرا sw-v1.js نسخه قدیمی index.html را از حافظه پنهان خود ارائه می دهد. شما خود را در موقعیتی قرار داده اید که برای به روز رسانی کارگر خدماتی خود باید سرویسکار خود را به روز کنید. ایو

با این حال، برای نسخه ی نمایشی بالا ، URL کارمند سرویس را تغییر داده ام. به این ترتیب، به خاطر نسخه ی نمایشی، می توانید بین نسخه ها جابجا شوید. این کاری نیست که من در تولید انجام دهم.

آسان کردن توسعه

چرخه عمر کارگر سرویس با در نظر گرفتن کاربر ساخته شده است، اما در طول توسعه کمی دردناک است. خوشبختانه چند ابزار برای کمک وجود دارد:

به روز رسانی در بارگذاری مجدد

این مورد مورد علاقه من است.

ابزارهای برنامه‌نویس «به‌روزرسانی در بارگذاری مجدد» را نشان می‌دهند

این چرخه عمر را تغییر می‌دهد تا برنامه‌نویس پسند باشد. هر ناوبری:

  1. کارمند خدمات را دوباره واکشی کنید.
  2. آن را به عنوان یک نسخه جدید نصب کنید، حتی اگر بایت یکسان باشد، به این معنی که رویداد install شما اجرا می شود و حافظه پنهان شما به روز می شود.
  3. از مرحله انتظار بگذرید تا سرویس‌کار جدید فعال شود.
  4. صفحه را پیمایش کنید.

این بدان معناست که به‌روزرسانی‌های خود را در هر پیمایش (از جمله بازخوانی) بدون نیاز به بارگیری مجدد یا بستن برگه دریافت خواهید کرد.

از انتظار بگذر

ابزارهای برنامه‌نویس «انتظار رد شدن» را نشان می‌دهند

اگر کارگری دارید که منتظر است، می‌توانید «پرش از انتظار» را در DevTools بزنید تا فوراً آن را به «فعال» ارتقا دهید.

Shift-Reload

اگر صفحه را به اجبار مجدداً بارگیری کنید (shift-reload) به طور کامل سرویس کار را دور می زند. کنترل نشده خواهد بود این ویژگی در مشخصات است، بنابراین در سایر مرورگرهای پشتیبانی کننده سرویس کار می کند.

مدیریت به روز رسانی ها

کارگر خدمات به عنوان بخشی از وب توسعه پذیر طراحی شده است. ایده این است که ما، به عنوان توسعه دهندگان مرورگر، تصدیق می کنیم که در توسعه وب بهتر از توسعه دهندگان وب نیستیم. و به این ترتیب، ما نباید APIهای باریک سطح بالا را ارائه کنیم که یک مشکل خاص را با استفاده از الگوهایی که ما دوست داریم حل کند، و در عوض به شما اجازه می دهد تا به مغز مرورگر دسترسی داشته باشید و به شما اجازه دهیم آن را همانطور که می خواهید انجام دهید، به روشی که بهترین کار را انجام دهد. برای کاربران شما

بنابراین، برای فعال کردن الگوهای بسیاری که می توانیم، کل چرخه به روز رسانی قابل مشاهده است:

navigator.serviceWorker.register('/sw.js').then(reg => {
  reg.installing; // the installing worker, or undefined
  reg.waiting; // the waiting worker, or undefined
  reg.active; // the active worker, or undefined

  reg.addEventListener('updatefound', () => {
    // A wild service worker has appeared in reg.installing!
    const newWorker = reg.installing;

    newWorker.state;
    // "installing" - the install event has fired, but not yet complete
    // "installed"  - install complete
    // "activating" - the activate event has fired, but not yet complete
    // "activated"  - fully active
    // "redundant"  - discarded. Either failed install, or it's been
    //                replaced by a newer version

    newWorker.addEventListener('statechange', () => {
      // newWorker.state has changed
    });
  });
});

navigator.serviceWorker.addEventListener('controllerchange', () => {
  // This fires when the service worker controlling this page
  // changes, eg a new worker has skipped waiting and become
  // the new active worker.
});

چرخه زندگی همیشه ادامه دارد

همانطور که می بینید، درک چرخه عمر کارگر خدماتی مفید است – و با این درک، رفتارهای کارکنان خدماتی باید منطقی تر و کمتر مرموز به نظر برسند. این دانش با استقرار و به روز رسانی کارکنان خدمات به شما اعتماد به نفس بیشتری می دهد.