แนวคิด Service Worker

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

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

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

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

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

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

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

เลเยอร์ใหม่

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

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

ในบทที่ 3 ของ Service Worker เราแสดงภาพ Service Worker ที่หยุดทำงานโดยทำให้ไม่มีสีใดๆ ขณะรอการตื่น

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

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

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

การแทรกแซงแคชของ Service Worker อื่น

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

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