แนวคิด Service Worker

วิธีคิดเมื่อนึกถึงโปรแกรมทำงานของบริการ

Service Worker มีประสิทธิภาพมากและคุ้มค่ากับการเรียนรู้เป็นอย่างยิ่ง ซึ่งช่วยให้คุณมอบประสบการณ์ระดับใหม่แก่ผู้ใช้ เว็บไซต์ของคุณโหลดได้ทันที ซึ่งสามารถทำงานได้แบบออฟไลน์ โดยสามารถติดตั้งเป็นแอปพลิเคชันเฉพาะแพลตฟอร์ม และให้ความรู้สึกว่าดูสวยงาม แต่มีการเข้าถึงและอิสระของเว็บ

แต่โปรแกรมทำงานของบริการนั้นไม่เหมือนกับนักพัฒนาเว็บส่วนใหญ่ที่เราคุ้นเคย บทเรียนเหล่านี้มาพร้อมกับการเรียนรู้ที่ชันขึ้นและอุปสรรคต่างๆ ที่คุณควรระวัง

Google Developers และผมเพิ่งทำงานร่วมกันในโปรเจ็กต์ Service Workies ซึ่งเป็นเกมฟรีที่ช่วยให้เข้าใจโปรแกรมทำงานของบริการ ในขณะที่สร้างแพลตฟอร์มและทำงานกับ Service Worker ที่ซับซ้อน ผมก็พบปัญหาอยู่บ้าง สิ่งที่ช่วยฉันได้มากที่สุดคือการมีคำอุปมาอุปไมยที่เป็นรูปธรรมจำนวนหนึ่ง ในโพสต์นี้ เราจะมาดูโมเดลทางความคิดเหล่านี้และรวบรวมความคิดของเราเกี่ยวกับลักษณะเฉพาะที่ไม่ต่างไปจากเดิมซึ่งทำให้พนักงานบริการทั้งซับซ้อนและยอดเยี่ยม

เหมือนกันแต่ต่างกัน

คุณจะคุ้นเคยกับการเขียนโค้ดของ Service Worker ขณะเขียนโค้ด คุณจะได้ใช้ฟีเจอร์ภาษา JavaScript ใหม่ที่คุณชื่นชอบ คุณจะฟังเหตุการณ์ในวงจรเช่นเดียวกับเหตุการณ์ UI คุณจะจัดการขั้นตอนการควบคุมด้วยสัญญาต่างๆ ได้เหมือนเดิม

แต่พฤติกรรมของ Service Worker อื่นๆ จะทำให้คุณต้องมึนงง โดยเฉพาะเมื่อคุณรีเฟรชหน้าเว็บและไม่เห็นการเปลี่ยนแปลงโค้ดของคุณใช้

เลเยอร์ใหม่

โดยปกติเมื่อสร้างเว็บไซต์ คุณจะมีเพียง 2 เลเยอร์ที่ต้องคำนึงถึง ได้แก่ ไคลเอ็นต์และเซิร์ฟเวอร์ Service Worker เป็นเลเยอร์ใหม่ล่าสุดที่อยู่ตรงกลาง

Service Worker ทำหน้าที่เป็นชั้นกลางระหว่างไคลเอ็นต์กับเซิร์ฟเวอร์

ให้คิดว่าโปรแกรมทำงานของบริการเป็นส่วนขยายเบราว์เซอร์ประเภทหนึ่งที่เว็บไซต์ของคุณติดตั้งในเบราว์เซอร์ของผู้ใช้ได้ เมื่อติดตั้งแล้ว โปรแกรมทำงานของบริการจะextendsเบราว์เซอร์ของเว็บไซต์ด้วยเลเยอร์กลางที่มีประสิทธิภาพ เลเยอร์ Service Worker นี้สามารถสกัดกั้นและจัดการคำขอทั้งหมดที่เว็บไซต์ของคุณสร้างได้

เลเยอร์ Service Worker มีวงจรของตัวเองโดยไม่เกี่ยวข้องกับแท็บเบราว์เซอร์ การรีเฟรชหน้าแบบง่ายๆ นั้นไม่เพียงพอที่จะอัปเดต Service Worker เช่นเดียวกับที่คุณคาดหวังว่าจะไม่มีการรีเฟรชหน้าเว็บเพื่ออัปเดตโค้ดที่ทำให้ใช้งานได้บนเซิร์ฟเวอร์ แต่ละเลเยอร์มีกฎที่ไม่ซ้ำกันสำหรับการอัปเดต

ในเกม Service Workies เราพูดถึงรายละเอียดมากมายเกี่ยวกับวงจรการทำงานของ Service Worker และให้คุณฝึกฝนการทำงานได้มากมาย

มีประสิทธิภาพแต่จำกัด

การมี Service Worker ไว้บนเว็บไซต์ช่วยให้คุณได้รับประโยชน์ที่น่าทึ่ง เว็บไซต์ของคุณสามารถทำสิ่งต่อไปนี้ได้

  • ทำงานได้อย่างไร้ที่ติแม้ผู้ใช้จะออฟไลน์
  • ประสิทธิภาพที่ดีขึ้นอย่างมากผ่านการแคช
  • ใช้ข้อความ Push
  • ติดตั้งเป็น PWA

โปรแกรมเหล่านี้มีการออกแบบไม่มากเท่าที่พนักงานบริการสามารถทำได้ แต่จะใช้ในการทำงานแบบพร้อมกันหรือในชุดข้อความเดียวกันกับเว็บไซต์ไม่ได้ นั่นหมายความว่าคุณจะไม่สามารถเข้าถึง

  • localStorage
  • DOM
  • หน้าต่าง

ข่าวดีก็คือหน้าเว็บสามารถสื่อสารกับ Service Worker ได้ด้วยวิธีต่างๆ มากมาย เช่น postMessage โดยตรง ช่องข้อความแบบหนึ่งต่อหนึ่ง และช่องออกอากาศแบบหนึ่งต่อหลาย

อายุน้อย แต่อายุสั้น

โปรแกรมทำงานของบริการที่ใช้งานอยู่จะทำงานต่อไปแม้ว่าผู้ใช้จะออกจากเว็บไซต์ของคุณหรือปิดแท็บก็ตาม โดยเบราว์เซอร์จะเก็บโปรแกรมทำงานของบริการนี้ไว้เพื่อให้พร้อมใช้งานเมื่อผู้ใช้กลับมาที่เว็บไซต์ของคุณครั้งถัดไป ก่อนที่จะส่งคำขอแรก โปรแกรมทำงานของบริการจะมีโอกาสสกัดกั้นคำขอและควบคุมหน้าเว็บ นี่คือสิ่งที่ช่วยให้เว็บไซต์ทำงานแบบออฟไลน์ได้ โปรแกรมทำงานของบริการจะแสดงหน้าเว็บในเวอร์ชันที่แคชไว้ได้ แม้ว่าผู้ใช้จะไม่เชื่อมต่อกับอินเทอร์เน็ตก็ตาม

ใน Service Workies เราแสดงภาพแนวคิดนี้โดยมี Kolohe (พนักงานบริการที่เป็นมิตร) ที่สกัดกั้นและจัดการคำขอ

หยุดทำงานแล้ว

แม้ว่า Service Worker จะเป็นอมตะ แต่ระบบสามารถหยุดพนักงานได้ตลอดเวลา เบราว์เซอร์ไม่ต้องการสิ้นเปลืองทรัพยากรในโปรแกรมทำงานของบริการที่ไม่ได้ทำอะไรอยู่ในขณะนี้ การหยุดทํางานไม่ใช่สิ่งเดียวกันกับการยุติ เพราะ Service Worker จะยังคงติดตั้งและเปิดใช้งานอยู่ เครื่องจะเข้าสู่โหมดสลีป ครั้งต่อไปที่ต้องใช้ (เช่น จัดการคำขอ) เบราว์เซอร์จะปลุกระบบให้กลับมาทำงาน

waitUntil

เนื่องจากเป็นไปได้ที่มักจะหลับบ่อย โปรแกรมทำงานของคุณจึงต้องหาวิธีทำให้เบราว์เซอร์รู้ว่ามีการทำงานสำคัญและไม่รู้สึกงีบหลับ นี่จึงเป็นเหตุผลที่ 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;
});

ในคำขอแต่ละรายการโปรแกรมทำงานของบริการนี้จะบันทึกหมายเลข เช่น 0.13981866382421893 ตัวแปร hasHandledARequest จะเปลี่ยนเป็น true ด้วย ขณะนี้โปรแกรมทำงานของบริการจะไม่มีการใช้งานสักพัก เบราว์เซอร์จึงหยุดการทำงาน ครั้งต่อไปที่มีคำขอ จำเป็นต้องใช้ Service Worker อีกครั้งเพื่อให้เบราว์เซอร์ปลุกระบบ สคริปต์จะได้รับการประเมินอีกครั้ง ตอนนี้ hasHandledARequest จะรีเซ็ตเป็น false และ favoriteNumber แตกต่างจาก 0.5907281835659033

คุณจะพึ่งพาสถานะที่จัดเก็บไว้ในโปรแกรมทำงานของบริการไม่ได้ นอกจากนี้ การสร้างอินสแตนซ์ของสิ่งต่างๆ เช่น ช่องทางข้อความ อาจทำให้เกิดข้อบกพร่องได้ ดังนั้น คุณจะได้รับอินสแตนซ์ใหม่ทุกครั้งที่โปรแกรมทำงานของบริการหยุด/เริ่มต้น

ใน Service Workies บทที่ 3 เราแสดงภาพ Service Worker ที่หยุดไปแล้วว่าสูญเสียสีทั้งหมดขณะที่รอให้ระบบตื่นนอน

การแสดงภาพ Service Worker ที่หยุดลง

ใช้ร่วมกัน แต่แยกกัน

โปรแกรมทำงานของบริการจะควบคุมหน้าเว็บของคุณได้ครั้งละ 1 รายการเท่านั้น แต่สามารถติดตั้งโปรแกรมทำงาน 2 รายการได้พร้อมกัน เมื่อเปลี่ยนแปลงโค้ด Service Worker และรีเฟรชหน้า แสดงว่าคุณไม่ได้แก้ไข Service Worker เลย Service Worker จะเปลี่ยนแปลงไม่ได้ เพราะจะเป็นการสร้างใหม่แทน Service Worker ใหม่นี้ (เรียกว่า SW2) จะติดตั้งแต่ยังไม่เปิดใช้งาน และจะต้องรอให้ Service Worker (SW1) ปัจจุบันสิ้นสุดการทำงาน (เมื่อผู้ใช้ออกจากเว็บไซต์)

กำลังยุ่งกับแคชของโปรแกรมทำงานอื่น

ขณะติดตั้ง SW2 จะตั้งค่าสิ่งต่างๆ ได้ โดยทั่วไปจะเป็นการสร้างและป้อนข้อมูลแคช แต่โปรดทราบ: Service Worker ใหม่นี้มีสิทธิ์เข้าถึงทุกอย่างที่ Service Worker ปัจจุบันเข้าถึงได้ หากคุณไม่ระมัดระวัง โปรแกรมทำงานของบริการที่รอคนใหม่ของคุณอาจสร้างความสับสนให้กับ Service Worker ปัจจุบันของคุณได้ ตัวอย่างบางส่วนที่อาจทำให้เกิดปัญหามีดังนี้

  • SW2 อาจลบแคชที่ SW1 ใช้งานอยู่
  • SW2 สามารถแก้ไขเนื้อหาของแคชที่ SW1 ใช้อยู่ ทำให้ SW1 ตอบสนองด้วยเนื้อหาที่หน้าเว็บไม่ได้คาดไว้

ข้ามการข้ามขณะรอ

นอกจากนี้ Service Worker อาจใช้เมธอด skipWaiting() ที่มีความเสี่ยงเพื่อเข้าควบคุมหน้าเว็บทันทีที่ติดตั้งเสร็จ ซึ่งโดยทั่วไปเป็นความคิดที่ไม่ดี เว้นแต่คุณจะตั้งใจพยายามเปลี่ยนโปรแกรมทำงานของบริการที่มีข้อบกพร่อง โปรแกรมทำงานของบริการใหม่อาจใช้ทรัพยากรที่อัปเดตซึ่งหน้าปัจจุบันไม่ต้องการ ทำให้เกิดข้อผิดพลาดและข้อบกพร่อง

เริ่มทำความสะอาด

วิธีที่จะป้องกันไม่ให้โปรแกรมทำงานของบริการทำงานซ้ำซากคือการใช้แคชที่แตกต่างกัน วิธีที่ง่ายที่สุดในการดำเนินการดังกล่าวคือการกำหนดเวอร์ชันของชื่อแคชที่ใช้

const version = 1;
const assetCacheName = `assets-${version}`;

self.addEventListener("install", event => {
  caches.open(assetCacheName).then(cache => {
    // confidently do stuff with your very own cache
  });
});

เมื่อทำให้ Service Worker ใหม่ใช้งานได้ คุณจะต้องเชื่อมต่อ version เพื่อให้ทำงานตามที่ต้องการโดยมีแคชที่แยกต่างหากจาก Service Worker ก่อนหน้า

การแสดงภาพแคช

สิ้นสุดการทำความสะอาด

เมื่อ Service Worker ถึงสถานะของ activated แล้ว คุณจะรู้ว่ามีการเข้าควบคุมงานและ Service Worker ก่อนหน้าซ้ำซ้อนแล้ว ณ จุดนี้ สิ่งสำคัญคือการล้างหลังจากโปรแกรมทำงานของบริการเก่า ฟีเจอร์นี้ไม่เพียงเป็นไปตามขีดจำกัดพื้นที่เก็บข้อมูลแคชของผู้ใช้เท่านั้น แต่ยังป้องกันข้อบกพร่องโดยไม่ตั้งใจอีกด้วย

เมธอด caches.match() เป็นทางลัดที่ใช้บ่อยสำหรับการเรียกข้อมูลรายการจากแคชใดก็ได้ที่ตรงกัน แต่จะทำซ้ำผ่านแคชตามลำดับที่สร้างขึ้น สมมติว่าคุณมีไฟล์สคริปต์ app.js 2 เวอร์ชันในแคชที่ต่างกัน 2 รายการ คือ 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 ในการสังหารสัตว์ออฟไลน์