แนวคิด Service Worker

วิธีคิดเกี่ยวกับ Service Worker

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

แต่ Service Worker นั้นแตกต่างจากสิ่งที่นักพัฒนาเว็บส่วนใหญ่คุ้นเคย การใช้งานเครื่องมือเหล่านี้ต้องอาศัยการเรียนรู้และอาจพบปัญหาบางอย่างที่คุณควรระวัง

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

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

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

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

เลเยอร์ใหม่

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

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

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

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

ในเกม Service Workies เราจะอธิบายรายละเอียดต่างๆ ของวงจร Service Worker และให้คุณฝึกฝนการใช้งาน Service Worker มากมาย

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

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

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

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

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

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

มีอายุยาวนานแต่มีอายุสั้น

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

ใน Service Workies เราแสดงแนวคิดนี้ด้วย Kolohe (Service Worker ที่แสนน่ารัก) ที่คอยรับและจัดการคําขอ

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

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

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

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

ภาพ Service Worker ที่หยุดทำงาน

อยู่ด้วยกันแต่แยกกัน

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

การรับส่งข้อความกับแคชของโปรแกรมทำงานของบริการรายอื่น

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

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

ข้าม skipWaiting

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

เริ่มล้าง

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

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 ดังกล่าวได้เริ่มทํางานแล้ว และ Service Worker ก่อนหน้านั้นไม่จําเป็นแล้ว ขั้นตอนนี้สำคัญมากในการล้างข้อมูลหลังจาก Service Worker เก่า ซึ่งไม่เพียงเป็นไปตามขีดจำกัดพื้นที่เก็บข้อมูลแคชของผู้ใช้ แต่ยังป้องกันข้อบกพร่องที่ไม่ได้ตั้งใจได้อีกด้วย

เมธอด caches.match() เป็นทางลัดที่ใช้บ่อยในการดึงข้อมูลรายการจากแคชใดก็ได้ที่ตรงกัน แต่ระบบจะวนดูแคชตามลําดับที่สร้าง สมมติว่าคุณมีไฟล์สคริปต์ app.js 2 เวอร์ชันในแคช 2 รายการที่แตกต่างกัน ได้แก่ assets-1 และ assets-2 หน้าเว็บของคุณกําลังรอสคริปต์ที่ใหม่กว่าซึ่งจัดเก็บไว้ใน assets-2 แต่หากคุณยังไม่ได้ลบแคชเก่า caches.match('app.js') จะแสดงแคชเก่าจาก assets-1 และอาจทำให้เว็บไซต์ใช้งานไม่ได้

เพียงลบแคชที่ Service Worker ใหม่ไม่ต้องการ คุณก็ล้างข้อมูล 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 Worker จะช่วยให้คุณสร้าง Service Worker ได้อย่างมั่นใจ เมื่อคุ้นเคยกับฟีเจอร์เหล่านี้แล้ว คุณจะสามารถมอบประสบการณ์การใช้งานที่ยอดเยี่ยมให้แก่ผู้ใช้

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