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

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

กลยุทธ์การแคชคือการโต้ตอบระหว่างเหตุการณ์ fetch ของ Service Worker และอินเทอร์เฟซ 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 ของ Service Worker ถึงตอนนี้ คุณคงได้ยินเกี่ยวกับ "การสกัดกั้นคำขอของเครือข่าย" มาบ้างในเอกสารนี้ และเหตุการณ์ 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 แบบไม่พร้อมกัน หรือการตอบกลับโดยเมธอด match ของอินเทอร์เฟซ Cache ได้

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

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

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

หากคุณพลิก "แคชก่อน เครือข่ายที่สอง" บนศีรษะ คุณก็จะได้กลยุทธ์ "เครือข่ายแรก แคชที่สอง" ซึ่งมีลักษณะดังนี้

  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 ของ Service Worker รวมถึง API ที่เกี่ยวข้อง ซึ่งหมายความว่าคุณได้เรียนรู้มากเพียงพอเกี่ยวกับวิธีใช้ Service Worker โดยตรงเพื่อเริ่มปรับแต่ง Workbox!