กลยุทธ์สำหรับการแคช Service Worker

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

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

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

อินเทอร์เฟซ Cache เทียบกับแคช HTTP

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

  • อินเทอร์เฟซ Cache เป็นกลไกการแคชที่แยกต่างหากจากแคช HTTP
  • อะไรก็ได้ Cache-Control การกำหนดค่าที่คุณใช้ในการกำหนดแคช HTTP จะไม่มีผลต่อเนื้อหาที่จัดเก็บในอินเทอร์เฟซ Cache

ลองคิดว่าแคชของเบราว์เซอร์เป็นเลเยอร์หลายชั้น แคช HTTP เป็นแคชระดับต่ำซึ่งขับเคลื่อนโดยคู่คีย์-ค่าที่มีคำสั่งในส่วนหัว HTTP

ในทางตรงกันข้าม อินเทอร์เฟซ Cache เป็นแคชระดับสูงที่ทำงานด้วย JavaScript API ซึ่งให้ความยืดหยุ่นมากกว่าการใช้คู่คีย์-ค่า HTTP ที่ค่อนข้างเรียบง่าย และเป็นครึ่งหนึ่งของที่ทำให้กลยุทธ์การแคชใช้งานได้ เมธอด API ที่สำคัญบางอย่างกับแคชของ Service Worker ได้แก่

  • CacheStorage.open เพื่อสร้างอินสแตนซ์ Cache ใหม่
  • Cache.add และ Cache.put ในการจัดเก็บการตอบกลับของเครือข่ายในแคชของ Service Worker
  • Cache.match เพื่อค้นหาการตอบกลับที่แคชไว้ในอินสแตนซ์ Cache
  • Cache.delete เพื่อนำการตอบกลับที่แคชไว้ออกจากอินสแตนซ์ Cache

นี่เป็นตัวอย่างเพียงเล็กน้อย ก็ยังมีวิธีที่มีประโยชน์อื่นๆ อีก แต่นี่คือขั้นตอนพื้นฐานที่คุณจะได้ใช้ภายหลังในคู่มือนี้

กิจกรรม fetch แบบเรียบง่าย

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

// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';

self.addEventListener('install', (event) => {
  event.waitUntil(caches.open(cacheName));
});

self.addEventListener('fetch', async (event) => {
  // Is this a request for an image?
  if (event.request.destination === 'image') {
    // Open the cache
    event.respondWith(caches.open(cacheName).then((cache) => {
      // Respond with the image from the cache or from the network
      return cache.match(event.request).then((cachedResponse) => {
        return cachedResponse || fetch(event.request.url).then((fetchedResponse) => {
          // Add the network response to the cache for future visits.
          // Note: we need to make a copy of the response to save it in
          // the cache and use the original as the request response.
          cache.put(event.request, fetchedResponse.clone());

          // Return the network response
          return fetchedResponse;
        });
      });
    }));
  } else {
    return;
  }
});

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

  1. ตรวจสอบพร็อพเพอร์ตี้ destination ของคำขอเพื่อดูว่าเป็นคำขอรูปภาพหรือไม่
  2. หากรูปภาพอยู่ในแคชของ Service Worker ให้แสดงรูปภาพจากที่นั่น หากไม่ ให้ดึงรูปภาพจากเครือข่าย จัดเก็บการตอบสนองในแคช และแสดงผลการตอบสนองของเครือข่าย
  3. ระบบจะส่งคำขออื่นๆ ทั้งหมดผ่าน Service Worker โดยไม่มีการโต้ตอบกับแคช

ออบเจ็กต์ event ของการดึงข้อมูลมี พร็อพเพอร์ตี้ request ซึ่งข้อมูลเล็กๆ น้อยๆ ที่มีประโยชน์ต่อไปนี้จะช่วยให้คุณระบุประเภทของคำขอแต่ละรายการได้

  • url ซึ่งเป็น URL สำหรับคำขอเครือข่ายที่เหตุการณ์ fetch จัดการอยู่
  • method ซึ่งเป็นวิธีการส่งคำขอ (เช่น GET หรือ POST)
  • mode ซึ่งอธิบายโหมดของคำขอ ค่า 'navigate' มักจะใช้เพื่อแยกแยะคำขอเอกสาร HTML กับคำขออื่นๆ
  • destination ซึ่งอธิบายถึงประเภทเนื้อหาที่ขอในลักษณะที่หลีกเลี่ยงการใช้นามสกุลไฟล์ของเนื้อหาที่ขอ

เช่นเดียวกัน ชื่อเกมไม่พร้อมกัน คุณจะจำได้ว่ากิจกรรม install เสนอ event.waitUntil ซึ่งใช้เวลาตามที่สัญญาไว้ และรอให้การแก้ไขได้รับการแก้ไขก่อนที่จะดำเนินการเปิดใช้งานต่อ กิจกรรม fetch เสนอกิจกรรมที่คล้ายกัน event.respondWith วิธี ซึ่งคุณสามารถใช้เพื่อส่งคืนผลลัพธ์ของแบบอะซิงโครนัส คำขอ fetch หรือการตอบกลับจากอินเทอร์เฟซ Cache matchวิธี

กลยุทธ์การแคช

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

แคชเท่านั้น

แสดงโฟลว์จากหน้าไปยัง Service Worker ไปยังแคช

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

// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';

// Assets to precache
const precachedAssets = [
  '/possum1.jpg',
  '/possum2.jpg',
  '/possum3.jpg',
  '/possum4.jpg'
];

self.addEventListener('install', (event) => {
  // Precache assets on install
  event.waitUntil(caches.open(cacheName).then((cache) => {
    return cache.addAll(precachedAssets);
  }));
});

self.addEventListener('fetch', (event) => {
  // Is this one of our precached assets?
  const url = new URL(event.request.url);
  const isPrecachedRequest = precachedAssets.includes(url.pathname);

  if (isPrecachedRequest) {
    // Grab the precached asset from the cache
    event.respondWith(caches.open(cacheName).then((cache) => {
      return cache.match(event.request.url);
    }));
  } else {
    // Go to the network
    return;
  }
});

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

เครือข่ายเท่านั้น

แสดงโฟลว์จากหน้าไปยังโปรแกรมทำงานของบริการไปยังเครือข่าย

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

การตรวจสอบว่าคำขอส่งผ่านไปยังเครือข่ายหมายความว่าคุณจะไม่เรียกใช้ event.respondWith สำหรับคำขอที่ตรงกัน ถ้าคุณต้องการให้โจ่งแจ้ง คุณสามารถตบ return; ที่ว่างเปล่าใน Callback ของเหตุการณ์ fetch สำหรับคำขอที่ต้องการส่งผ่านไปยังเครือข่ายได้ นี่คือสิ่งที่เกิดขึ้นใน "แคชเท่านั้น" การสาธิตกลยุทธ์สำหรับคำขอที่ไม่ได้แคชล่วงหน้า

แคชก่อน โดยกลับไปใช้เครือข่าย

แสดงโฟลว์จากหน้าไปยัง Service Worker ไปยังแคช และไปยังเครือข่ายหากไม่อยู่ในแคช

กลยุทธ์นี้เป็นจุดที่สิ่งต่างๆ จะเข้ามาเกี่ยวข้องมากขึ้น สำหรับคำขอที่ตรงกัน กระบวนการจะมีลักษณะดังนี้

  1. คำขอจะเข้าสู่แคช หากเนื้อหาอยู่ในแคช ให้แสดงจากที่นั่น
  2. หากคำขอไม่ได้อยู่ในแคช ให้ไปที่เครือข่าย
  3. เมื่อส่งคำขอเครือข่ายเสร็จแล้ว ให้เพิ่มไปยังแคช จากนั้นแสดงการตอบสนองจากเครือข่าย

ลองดูตัวอย่างของกลยุทธ์นี้ ซึ่งสามารถทดสอบได้ใน การสาธิตแบบสด

// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';

self.addEventListener('fetch', (event) => {
  // Check if this is a request for an image
  if (event.request.destination === 'image') {
    event.respondWith(caches.open(cacheName).then((cache) => {
      // Go to the cache first
      return cache.match(event.request.url).then((cachedResponse) => {
        // Return a cached response if we have one
        if (cachedResponse) {
          return cachedResponse;
        }

        // Otherwise, hit the network
        return fetch(event.request).then((fetchedResponse) => {
          // Add the network response to the cache for later visits
          cache.put(event.request, fetchedResponse.clone());

          // Return the network response
          return fetchedResponse;
        });
      });
    }));
  } else {
    return;
  }
});

แม้ว่าตัวอย่างนี้จะครอบคลุมเฉพาะรูปภาพ กลยุทธ์นี้เหมาะสำหรับเนื้อหาแบบคงที่ทั้งหมด (เช่น CSS, JavaScript, รูปภาพ และแบบอักษร) โดยเฉพาะเวอร์ชันที่มีแฮช โดยจะเพิ่มความเร็วของเนื้อหาที่เปลี่ยนแปลงไม่ได้โดยการตรวจสอบความใหม่ของเนื้อหากับเซิร์ฟเวอร์ที่อาจเริ่มต้นแคชจาก HTTP ยิ่งไปกว่านั้น เนื้อหาที่แคชไว้ทั้งหมดจะใช้งานได้แบบออฟไลน์

เครือข่ายทำงานก่อน โดยกลับไปใช้แคช

แสดงโฟลว์จากหน้าไปยัง Service Worker ไปยังเครือข่าย และไปยังแคชหากเครือข่ายไม่พร้อมใช้งาน

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

  1. คุณต้องไปที่เครือข่ายก่อนเพื่อขอคำขอ แล้ววางคำตอบไว้ในแคช
  2. หากคุณออฟไลน์ในภายหลัง คุณจะกลับไปใช้การตอบกลับเวอร์ชันล่าสุดในแคช

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

// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';

self.addEventListener('fetch', (event) => {
  // Check if this is a navigation request
  if (event.request.mode === 'navigate') {
    // Open the cache
    event.respondWith(caches.open(cacheName).then((cache) => {
      // Go to the network first
      return fetch(event.request.url).then((fetchedResponse) => {
        cache.put(event.request, fetchedResponse.clone());

        return fetchedResponse;
      }).catch(() => {
        // If the network is unavailable, get
        return cache.match(event.request.url);
      });
    }));
  } else {
    return;
  }
});

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

ในสถานการณ์ที่ความสามารถในการใช้งานออฟไลน์เป็นสิ่งสำคัญ แต่คุณต้องรักษาสมดุลระหว่างความสามารถนั้น ด้วยการเข้าถึงข้อมูลมาร์กอัปหรือข้อมูล API เวอร์ชันล่าสุด "เครือข่ายก่อน แคชวินาที" เป็นกลยุทธ์ที่แข็งแกร่งที่ช่วยให้บรรลุเป้าหมายนั้น

ไม่มีอัปเดตขณะตรวจสอบความถูกต้องอีกครั้ง

แสดงโฟลว์จากหน้าไปยัง Service Worker ไปยังแคช และจากเครือข่ายไปยังแคช

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

  1. ในคำขอแรกสำหรับเนื้อหา ให้ดึงข้อมูลจากเครือข่าย ให้วางไว้ในแคช และแสดงการตอบสนองของเครือข่าย
  2. ในคำขอที่ตามมา ให้แสดงเนื้อหาจากแคชก่อน จากนั้นแสดง "ในเบื้องหลัง" ส่งคำขอจากเครือข่ายอีกครั้งและอัปเดตรายการแคชของเนื้อหา
  3. สำหรับคำขอหลังจากนั้น คุณจะได้รับเวอร์ชันล่าสุดที่ดึงข้อมูลจากเครือข่ายที่อยู่ในแคชในขั้นตอนก่อนหน้า

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

// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';

self.addEventListener('fetch', (event) => {
  if (event.request.destination === 'image') {
    event.respondWith(caches.open(cacheName).then((cache) => {
      return cache.match(event.request).then((cachedResponse) => {
        const fetchedResponse = fetch(event.request).then((networkResponse) => {
          cache.put(event.request, networkResponse.clone());

          return networkResponse;
        });

        return cachedResponse || fetchedResponse;
      });
    }));
  } else {
    return;
  }
});

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

ไปยังกล่องทำงานกัน!

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