Service Worker ช่วยให้นักพัฒนาแอปแก้ปัญหาการเชื่อมต่อเครือข่ายได้ คุณจะควบคุมการแคชและวิธีจัดการคำขอได้ ซึ่งหมายความว่าคุณสามารถสร้างรูปแบบของคุณเองได้ ลองดูรูปแบบที่เป็นไปได้ 2-3 รูปแบบแยกกัน แต่ในทางปฏิบัติ คุณน่าจะใช้รูปแบบเหล่านี้ร่วมกันโดยขึ้นอยู่กับ URL และบริบท
ดูการสาธิตการทำงานของรูปแบบเหล่านี้ได้ที่ Trained-to-thrill
ควรจัดเก็บทรัพยากรเมื่อใด
Service Worker ช่วยให้คุณจัดการคำขอได้อย่างอิสระจากการแคช ดังนั้นฉันจะสาธิตแยกกัน ก่อนอื่นมาดูว่าเมื่อใดที่คุณควรใช้แคช
เมื่อติดตั้งเป็นทรัพยากร Dependency
Service Worker API จะให้เหตุการณ์ install แก่คุณ คุณใช้ฟีเจอร์นี้เพื่อเตรียม
สิ่งต่างๆ ที่ต้องพร้อมก่อนที่คุณจะจัดการกิจกรรมอื่นๆ ได้ ในระหว่าง
install Service Worker เวอร์ชันก่อนหน้าจะยังคงทำงานและแสดงหน้าเว็บ
ต่อไป ไม่ว่าคุณจะทำอะไรในขณะนี้ก็ไม่ควรขัดขวาง
Service Worker ที่มีอยู่
เหมาะสำหรับ: CSS, รูปภาพ, แบบอักษร, JS, เทมเพลต หรือสิ่งอื่นๆ ที่คุณ พิจารณาว่าคงที่สำหรับเว็บไซต์เวอร์ชันนั้น
ดึงข้อมูลที่อาจทำให้เว็บไซต์ใช้งานไม่ได้โดยสิ้นเชิงหากดึงข้อมูลไม่สำเร็จ ซึ่งเป็นข้อมูลที่แอปเฉพาะแพลตฟอร์มที่เทียบเท่าจะทำให้เป็นส่วนหนึ่งของการดาวน์โหลดครั้งแรก
self.addEventListener('install', function (event) {
event.waitUntil(
caches.open('mysite-static-v3').then(function (cache) {
return cache.addAll([
'/css/whatever-v3.css',
'/css/imgs/sprites-v6.png',
'/css/fonts/whatever-v8.woff',
'/js/all-min-v4.js',
// etc.
]);
}),
);
});
event.waitUntil ใช้สัญญาเพื่อกำหนดระยะเวลาและความสำเร็จของการติดตั้ง หาก Promise ปฏิเสธ ระบบจะถือว่าการติดตั้งล้มเหลวและจะละทิ้ง Service Worker นี้ (หากเวอร์ชันเก่ากว่ากำลังทำงานอยู่ ระบบจะปล่อยให้ทำงานต่อไป) caches.open() และcache.addAll()สัญญาการคืนสินค้า
หากดึงข้อมูลทรัพยากรใดไม่สำเร็จ การเรียกใช้ cache.addAll() จะถูกปฏิเสธ
ใน trained-to-thrill ฉันใช้สิ่งนี้เพื่อ แคชเนื้อหาแบบคงที่
เมื่อติดตั้ง ไม่ใช่เป็นการอ้างอิง
ซึ่งคล้ายกับการติดตั้งเป็นทรัพยากร Dependency แต่จะไม่ทำให้การติดตั้ง เสร็จสมบูรณ์ล่าช้า และจะไม่ทำให้การติดตั้งล้มเหลวหากการแคชล้มเหลว
เหมาะสำหรับ: ทรัพยากรขนาดใหญ่ที่ไม่จำเป็นต้องใช้ในทันที เช่น เนื้อหาสำหรับเกมในเลเวลต่อๆ ไป
self.addEventListener('install', function (event) {
event.waitUntil(
caches.open('mygame-core-v1').then(function (cache) {
cache
.addAll
// levels 11-20
();
return cache
.addAll
// core assets and levels 1-10
();
}),
);
});
ตัวอย่างนี้ไม่ส่งcache.addAllสัญญาสำหรับเลเวล 11–20 กลับไปที่
event.waitUntil ดังนั้นแม้ว่าการดำเนินการจะล้มเหลว เกมก็จะยังเล่นแบบออฟไลน์ได้ แน่นอนว่าคุณจะต้อง
เตรียมพร้อมสำหรับกรณีที่ระดับเหล่านั้นอาจไม่มีอยู่ และลองแคชอีกครั้งหากระดับเหล่านั้น
หายไป
ระบบอาจหยุดการทำงานของ Service Worker ขณะที่ดาวน์โหลดระดับ 11-20 เนื่องจากจัดการเหตุการณ์เสร็จแล้ว ซึ่งหมายความว่าจะไม่มีการแคชระดับดังกล่าว Web Periodic Background Synchronization API สามารถจัดการกรณีเช่นนี้ รวมถึงการดาวน์โหลดขนาดใหญ่ เช่น ภาพยนตร์
เมื่อเปิดใช้งาน
เหมาะสำหรับ: การล้างข้อมูลและการย้ายข้อมูล
เมื่อติดตั้ง Service Worker ใหม่และไม่ได้ใช้เวอร์ชันก่อนหน้า Service Worker ใหม่จะ
เปิดใช้งาน และคุณจะได้รับเหตุการณ์ activate เนื่องจากเวอร์ชันก่อนหน้าไม่ได้ใช้งานแล้ว จึงเป็นเวลาที่เหมาะสมในการจัดการการย้ายข้อมูลสคีมาใน IndexedDB
และลบแคชที่ไม่ได้ใช้ด้วย
self.addEventListener('activate', function (event) {
event.waitUntil(
caches.keys().then(function (cacheNames) {
return Promise.all(
cacheNames
.filter(function (cacheName) {
// Return true if you want to remove this cache,
// but remember that caches are shared across
// the whole origin
})
.map(function (cacheName) {
return caches.delete(cacheName);
}),
);
}),
);
});
ในระหว่างการเปิดใช้งาน ระบบจะใส่เหตุการณ์ต่างๆ เช่น fetch ลงในคิว ดังนั้นการเปิดใช้งานที่ใช้เวลานานอาจบล็อกการโหลดหน้าเว็บ พยายามเปิดใช้งานให้น้อยที่สุด และใช้เฉพาะในกรณีที่คุณทำไม่ได้ขณะที่เวอร์ชันก่อนหน้ายังใช้งานอยู่
ใน trained-to-thrill ฉันใช้คำสั่งนี้เพื่อ นำแคชเก่าออก
ในการโต้ตอบของผู้ใช้
เหมาะสำหรับ: เมื่อไม่สามารถนำทั้งเว็บไซต์ไปใช้งานแบบออฟไลน์ได้ และคุณเลือกที่จะอนุญาตให้ผู้ใช้เลือกเนื้อหาที่ต้องการให้พร้อมใช้งานแบบออฟไลน์ เช่น วิดีโอใน YouTube, บทความใน Wikipedia หรือแกลเลอรีหนึ่งๆ ใน Flickr
ให้ปุ่ม "อ่านภายหลัง" หรือ "บันทึกไว้ดูแบบออฟไลน์" แก่ผู้ใช้ เมื่อคลิกแล้ว ให้ดึงข้อมูลที่ต้องการ จากเครือข่ายและใส่ไว้ในแคช
document.querySelector('.cache-article').addEventListener('click', function (event) {
event.preventDefault();
var id = this.dataset.articleId;
caches.open('mysite-article-' + id).then(function (cache) {
fetch('/get-article-urls?id=' + id)
.then(function (response) {
// /get-article-urls returns a JSON-encoded array of
// resource URLs that a given article depends on
return response.json();
})
.then(function (urls) {
cache.addAll(urls);
});
});
});
Cache API พร้อมใช้งาน จากหน้าเว็บและ Service Worker ซึ่งหมายความว่าคุณเพิ่มลงในแคชได้โดยตรง จากหน้าเว็บ
ในการตอบสนองของเครือข่าย
เหมาะสำหรับ: การอัปเดตทรัพยากรบ่อยๆ เช่น กล่องจดหมายของผู้ใช้หรือเนื้อหาบทความ นอกจากนี้ ยังมีประโยชน์สำหรับเนื้อหาที่ไม่จำเป็น เช่น อวาตาร์ แต่ต้องระมัดระวัง
หากคำขอไม่ตรงกับรายการใดๆ ในแคช ให้รับคำขอจากเครือข่าย ส่งไปยังหน้าเว็บ และ เพิ่มลงในแคชพร้อมกัน
หากดำเนินการนี้กับ URL หลายรายการ เช่น อวตาร คุณจะต้องระมัดระวังไม่ให้พื้นที่เก็บข้อมูลของต้นทางมีขนาดใหญ่เกินไป หากผู้ใช้ต้องการเรียกคืนพื้นที่ดิสก์ คุณไม่ควรเป็นตัวเลือกแรก ตรวจสอบว่าคุณได้นำรายการในแคชที่ไม่ต้องการออกแล้ว
self.addEventListener('fetch', function (event) {
event.respondWith(
caches.open('mysite-dynamic').then(function (cache) {
return cache.match(event.request).then(function (response) {
return (
response ||
fetch(event.request).then(function (response) {
cache.put(event.request, response.clone());
return response;
})
);
});
}),
);
});
คุณอ่านเนื้อหาของการตอบกลับ/คำขอได้เพียงครั้งเดียวเพื่อให้ใช้หน่วยความจำได้อย่างมีประสิทธิภาพ โค้ด
ตัวอย่างใช้ .clone() เพื่อสร้างสำเนาเพิ่มเติม
ที่อ่านแยกกันได้
ใน trained-to-thrill ฉันใช้เครื่องมือนี้เพื่อ แคชรูปภาพ Flickr
Stale-while-revalidate
เหมาะสำหรับ: แหล่งข้อมูลที่อัปเดตบ่อยๆ ซึ่งไม่จำเป็นต้องมี เวอร์ชันล่าสุด อวตารอาจจัดอยู่ในหมวดหมู่นี้
หากมีเวอร์ชันที่แคชไว้ ให้ใช้เวอร์ชันนั้น แต่ดึงข้อมูลอัปเดตสำหรับครั้งถัดไป
self.addEventListener('fetch', function (event) {
event.respondWith(
caches.open('mysite-dynamic').then(function (cache) {
return cache.match(event.request).then(function (response) {
var fetchPromise = fetch(event.request).then(function (networkResponse) {
cache.put(event.request, networkResponse.clone());
return networkResponse;
});
return response || fetchPromise;
});
}),
);
});
ซึ่งคล้ายกับ stale-while-revalidate ของ HTTP เป็นอย่างมาก
ในข้อความพุช
Push API เป็นอีกฟีเจอร์หนึ่งที่สร้างขึ้นบน Service Worker ซึ่งจะช่วยให้ Service Worker ตื่นขึ้นมาเพื่อตอบสนองต่อข้อความจากบริการรับส่งข้อความของระบบปฏิบัติการได้ ซึ่งจะเกิดขึ้นแม้ว่าผู้ใช้จะไม่ได้เปิดแท็บเว็บไซต์ของคุณไว้ก็ตาม ระบบจะปลุกเฉพาะ Service Worker คุณขอสิทธิ์ดำเนินการนี้จากหน้าเว็บและระบบจะแจ้งให้ผู้ใช้ทราบ
เหมาะสำหรับ: เนื้อหาที่เกี่ยวข้องกับการแจ้งเตือน เช่น ข้อความแชท ข่าวล่าสุด หรือ อีเมล รวมถึงเนื้อหาที่เปลี่ยนแปลงไม่บ่อยนักซึ่งได้รับประโยชน์จากการซิงค์ทันที เช่น การอัปเดตรายการสิ่งที่ต้องทำ หรือการแก้ไขปฏิทิน
ผลลัพธ์สุดท้ายที่พบบ่อยคือการแจ้งเตือน ซึ่งเมื่อแตะแล้วจะเปิดและโฟกัสหน้าเว็บที่เกี่ยวข้อง และการอัปเดตแคชล่วงหน้าเป็นสิ่งสำคัญอย่างยิ่ง ผู้ใช้ออนไลน์ในขณะที่ได้รับข้อความพุช แต่อาจไม่ได้ออนไลน์เมื่อโต้ตอบกับการแจ้งเตือนในที่สุด ดังนั้นการทำให้เนื้อหานี้พร้อมใช้งานแบบออฟไลน์จึงมีความสำคัญอย่างยิ่ง
โค้ดนี้จะอัปเดตแคชก่อนแสดงการแจ้งเตือน
self.addEventListener('push', function (event) {
if (event.data.text() == 'new-email') {
event.waitUntil(
caches
.open('mysite-dynamic')
.then(function (cache) {
return fetch('/inbox.json').then(function (response) {
cache.put('/inbox.json', response.clone());
return response.json();
});
})
.then(function (emails) {
registration.showNotification('New email', {
body: 'From ' + emails[0].from.name,
tag: 'new-email',
});
}),
);
}
});
self.addEventListener('notificationclick', function (event) {
if (event.notification.tag == 'new-email') {
// Assume that all of the resources needed to render
// /inbox/ have previously been cached, e.g. as part
// of the install handler.
new WindowClient('/inbox/');
}
});
เกี่ยวกับ Background Sync
การซิงค์ในแบ็กกราวด์เป็นอีกฟีเจอร์หนึ่งที่สร้างขึ้นบน Service Worker ซึ่งจะช่วยให้คุณขอซิงค์ข้อมูลในเบื้องหลังแบบครั้งเดียวหรือตามช่วงเวลา (ฮิวริสติกอย่างยิ่ง) ได้ ซึ่งจะเกิดขึ้นแม้ว่าผู้ใช้จะไม่ได้เปิดแท็บเว็บไซต์ของคุณไว้ก็ตาม ระบบจะปลุกเฉพาะ Service Worker เท่านั้น คุณขอสิทธิ์ดำเนินการนี้จากหน้าเว็บและระบบจะแจ้งให้ผู้ใช้ทราบ
เหมาะสำหรับ: การอัปเดตที่ไม่เร่งด่วน โดยเฉพาะการอัปเดตที่เกิดขึ้นเป็นประจำจนการส่งข้อความพุชต่อการอัปเดต 1 ครั้งจะถี่เกินไปสำหรับผู้ใช้ เช่น ไทม์ไลน์โซเชียลหรือบทความข่าว
self.addEventListener('sync', function (event) {
if (event.id == 'update-leaderboard') {
event.waitUntil(
caches.open('mygame-dynamic').then(function (cache) {
return cache.add('/leaderboard.json');
}),
);
}
});
ความต่อเนื่องของแคช
โดยต้นทางจะได้รับพื้นที่ว่างจำนวนหนึ่งเพื่อทำสิ่งที่ต้องการ พื้นที่ว่างดังกล่าวจะ แชร์ระหว่างที่เก็บข้อมูลต้นทางทั้งหมด ได้แก่ ที่เก็บข้อมูล(ในเครื่อง) IndexedDB การเข้าถึงระบบไฟล์ และแน่นอนว่า แคช
ระบบไม่ได้ระบุจำนวนเงินที่คุณได้รับ ซึ่งจะแตกต่างกันไปตามอุปกรณ์และสภาพพื้นที่เก็บข้อมูล คุณดูยอดเงินคงเหลือได้โดยทำดังนี้
if (navigator.storage && navigator.storage.estimate) {
const quota = await navigator.storage.estimate();
// quota.usage -> Number of bytes used.
// quota.quota -> Maximum number of bytes available.
const percentageUsed = (quota.usage / quota.quota) * 100;
console.log(`You've used ${percentageUsed}% of the available storage.`);
const remaining = quota.quota - quota.usage;
console.log(`You can write up to ${remaining} more bytes.`);
}
อย่างไรก็ตาม เช่นเดียวกับพื้นที่เก็บข้อมูลของเบราว์เซอร์ทั้งหมด เบราว์เซอร์สามารถทิ้งข้อมูลของคุณได้หากอุปกรณ์มีพื้นที่เก็บข้อมูลไม่เพียงพอ น่าเสียดายที่เบราว์เซอร์ไม่สามารถแยกแยะระหว่างภาพยนตร์ที่คุณ ต้องการเก็บไว้ไม่ว่าอย่างไรก็ตามกับเกมที่คุณไม่ได้สนใจมากนัก
หากต้องการหลีกเลี่ยงปัญหานี้ ให้ใช้อินเทอร์เฟซ StorageManager โดยทำดังนี้
// From a page:
navigator.storage.persist()
.then(function(persisted) {
if (persisted) {
// Hurrah, your data is here to stay!
} else {
// So sad, your data may get chucked. Sorry.
});
แน่นอนว่าผู้ใช้ต้องให้สิทธิ์ โดยให้ใช้ Permissions API
การทำให้ผู้ใช้เป็นส่วนหนึ่งของขั้นตอนการทำงานนี้เป็นสิ่งสำคัญ เนื่องจากตอนนี้เราคาดหวังได้ว่าผู้ใช้จะควบคุมการลบได้ หากอุปกรณ์มีพื้นที่เก็บข้อมูลเหลือน้อยและ การล้างข้อมูลที่ไม่จำเป็นไม่ช่วยแก้ปัญหา ผู้ใช้จะต้องเป็นผู้ตัดสินใจว่าจะเก็บรายการใดไว้และ นำรายการใดออก
เพื่อให้ทำงานได้ ระบบปฏิบัติการต้องถือว่าต้นทางที่ "คงทน" เทียบเท่ากับแอปเฉพาะแพลตฟอร์ม ในการแบ่งย่อยการใช้พื้นที่เก็บข้อมูล แทนที่จะรายงานเบราว์เซอร์เป็นรายการเดียว
การแสดงคำแนะนำ
ไม่ว่าคุณจะแคชมากแค่ไหนก็ตาม Service Worker จะใช้แคชก็ต่อเมื่อคุณบอกให้ใช้และบอกวิธีใช้ รูปแบบบางอย่างในการจัดการคำขอมีดังนี้
แคชเท่านั้น
เหมาะสำหรับ: ทุกอย่างที่คุณถือว่าเป็นแบบคงที่สำหรับ "เวอร์ชัน" ใดเวอร์ชันหนึ่งของเว็บไซต์ คุณควร แคชข้อมูลเหล่านี้ในเหตุการณ์การติดตั้ง เพื่อให้มั่นใจว่าข้อมูลจะอยู่ที่นั่น
self.addEventListener('fetch', function (event) {
// If a match isn't found in the cache, the response
// will look like a connection error
event.respondWith(caches.match(event.request));
});
…แม้ว่าคุณจะไม่ค่อยต้องจัดการกรณีนี้โดยเฉพาะ แคชที่กลับไปใช้เครือข่ายก็ครอบคลุมกรณีนี้
เครือข่ายเท่านั้น
เหมาะสำหรับ: สิ่งที่ไม่มีค่าเทียบเท่าแบบออฟไลน์ เช่น คำสั่ง ping ของข้อมูลวิเคราะห์ คำขอที่ไม่ใช่ GET
self.addEventListener('fetch', function (event) {
event.respondWith(fetch(event.request));
// or don't call event.respondWith, which
// will result in default browser behavior
});
…แม้ว่าคุณจะไม่ค่อยต้องจัดการกรณีนี้โดยเฉพาะ แคชที่กลับไปใช้เครือข่ายก็ครอบคลุมกรณีนี้
แคช กลับไปใช้เครือข่าย
เหมาะสำหรับ: การสร้างแอปที่ทำงานแบบออฟไลน์เป็นอันดับแรก ในกรณีเช่นนี้ คุณจะจัดการคำขอส่วนใหญ่ได้ดังนี้ รูปแบบอื่นๆ เป็นข้อยกเว้นตามคำขอขาเข้า
self.addEventListener('fetch', function (event) {
event.respondWith(
caches.match(event.request).then(function (response) {
return response || fetch(event.request);
}),
);
});
ซึ่งจะทำให้คุณมีลักษณะการทำงานแบบ "แคชเท่านั้น" สำหรับรายการในแคช และลักษณะการทำงานแบบ "เครือข่ายเท่านั้น" สำหรับรายการที่ไม่ได้แคช (ซึ่งรวมถึงคำขอที่ไม่ใช่ GET ทั้งหมด เนื่องจากแคชไม่ได้)
การแข่งขันระหว่างแคชและเครือข่าย
เหมาะสำหรับ: ชิ้นงานขนาดเล็กที่คุณต้องการเพิ่มประสิทธิภาพในอุปกรณ์ที่มีการเข้าถึงดิสก์ช้า
การใช้ฮาร์ดไดรฟ์รุ่นเก่าร่วมกับเครื่องมือสแกนไวรัสและการเชื่อมต่ออินเทอร์เน็ตที่เร็วขึ้น อาจทำให้การรับทรัพยากรจากเครือข่ายเร็วกว่าการไปที่ดิสก์ อย่างไรก็ตาม การไปที่เครือข่าย เมื่อผู้ใช้มีเนื้อหาในอุปกรณ์อาจทำให้สิ้นเปลืองข้อมูล ดังนั้นโปรดคำนึงถึงเรื่องนี้
// Promise.race rejects when a promise rejects before fulfilling.
// To make a race function:
function promiseAny(promises) {
return new Promise((resolve, reject) => {
// make sure promises are all promises
promises = promises.map((p) => Promise.resolve(p));
// resolve this promise as soon as one resolves
promises.forEach((p) => p.then(resolve));
// reject if all promises reject
promises.reduce((a, b) => a.catch(() => b)).catch(() => reject(Error('All failed')));
});
}
self.addEventListener('fetch', function (event) {
event.respondWith(promiseAny([caches.match(event.request), fetch(event.request)]));
});
เครือข่ายกลับไปใช้แคช
เหมาะสำหรับ: การแก้ไขอย่างรวดเร็วสำหรับทรัพยากรที่อัปเดตบ่อยๆ นอก "เวอร์ชัน" ของเว็บไซต์ เช่น บทความ รูปโปรไฟล์ ไทม์ไลน์โซเชียลมีเดีย และลีดเดอร์บอร์ดของเกม
ซึ่งหมายความว่าผู้ใช้ออนไลน์จะได้รับเนื้อหาล่าสุด แต่ผู้ใช้ออฟไลน์จะได้รับเนื้อหาเวอร์ชันเก่าที่แคชไว้ หากคำขอเครือข่ายสำเร็จ คุณอาจต้องการอัปเดตรายการแคช
อย่างไรก็ตาม วิธีนี้มีข้อบกพร่อง หากผู้ใช้มีการเชื่อมต่อที่ไม่สม่ำเสมอหรือช้า ผู้ใช้จะต้อง รอให้เครือข่ายล้มเหลวก่อนจึงจะได้รับเนื้อหาที่ยอมรับได้อย่างสมบูรณ์ซึ่งอยู่ในอุปกรณ์ อยู่แล้ว ซึ่งอาจใช้เวลานานมากและทำให้ผู้ใช้ได้รับประสบการณ์การใช้งานที่น่าหงุดหงิด ดูรูปแบบถัดไป Cache then network เพื่อดูโซลูชันที่ดีกว่า
self.addEventListener('fetch', function (event) {
event.respondWith(
fetch(event.request).catch(function () {
return caches.match(event.request);
}),
);
});
แคชแล้วเครือข่าย
เหมาะสำหรับ: เนื้อหาที่อัปเดตบ่อย เช่น บทความ ไทม์ไลน์โซเชียลมีเดีย และลีดเดอร์บอร์ดของเกม
ซึ่งต้องให้หน้าเว็บส่งคำขอ 2 รายการ รายการหนึ่งไปยังแคช และอีกรายการหนึ่งไปยังเครือข่าย แนวคิดคือการแสดงข้อมูลที่แคชไว้ก่อน จากนั้นจึงอัปเดตหน้าเว็บเมื่อได้รับข้อมูลเครือข่าย
บางครั้งคุณอาจแทนที่ข้อมูลปัจจุบันเมื่อได้รับข้อมูลใหม่ (เช่น ลีดเดอร์บอร์ดของเกม) แต่ การดำเนินการดังกล่าวอาจขัดขวางการทำงานของเนื้อหาขนาดใหญ่ กล่าวโดยสรุปคือ อย่า "ซ่อน" สิ่งที่ผู้ใช้อาจกำลังอ่านหรือโต้ตอบด้วย
Twitter จะเพิ่มเนื้อหาใหม่ไว้เหนือเนื้อหาเก่าและปรับตำแหน่งการเลื่อน เพื่อให้ผู้ใช้ได้รับประสบการณ์อย่างต่อเนื่อง ซึ่งเป็นไปได้เนื่องจาก Twitter จะเก็บ เนื้อหาไว้ในลำดับเชิงเส้นเป็นส่วนใหญ่ ฉันคัดลอกรูปแบบนี้สำหรับ trained-to-thrill เพื่อให้เนื้อหาปรากฏบนหน้าจอโดยเร็วที่สุด ขณะเดียวกันก็ แสดงเนื้อหาล่าสุดทันทีที่มาถึง
โค้ดในหน้าเว็บ
var networkDataReceived = false;
startSpinner();
// fetch fresh data
var networkUpdate = fetch('/data.json')
.then(function (response) {
return response.json();
})
.then(function (data) {
networkDataReceived = true;
updatePage(data);
});
// fetch cached data
caches
.match('/data.json')
.then(function (response) {
if (!response) throw Error('No data');
return response.json();
})
.then(function (data) {
// don't overwrite newer network data
if (!networkDataReceived) {
updatePage(data);
}
})
.catch(function () {
// we didn't get cached data, the network is our last hope:
return networkUpdate;
})
.catch(showErrorMessage)
.then(stopSpinner);
โค้ดใน Service Worker:
คุณควรไปที่เครือข่ายและอัปเดตแคชเสมอ
self.addEventListener('fetch', function (event) {
event.respondWith(
caches.open('mysite-dynamic').then(function (cache) {
return fetch(event.request).then(function (response) {
cache.put(event.request, response.clone());
return response;
});
}),
);
});
ใน trained-to-thrill ฉันได้แก้ปัญหานี้ด้วยการใช้ XHR แทนการดึงข้อมูล และใช้ส่วนหัว Accept ในทางที่ผิดเพื่อบอก Service Worker ว่าจะรับผลลัพธ์จากที่ใด (โค้ดหน้าเว็บ โค้ด Service Worker)
การสำรองข้อมูลทั่วไป
หากแสดงผลจากแคชหรือเครือข่ายไม่ได้ ให้ระบุ การสำรองข้อมูลทั่วไป
เหมาะสำหรับ: รูปภาพรอง เช่น อวตาร คำขอ POST ที่ล้มเหลว และหน้า "ไม่พร้อมใช้งานขณะออฟไลน์"
self.addEventListener('fetch', function (event) {
event.respondWith(
// Try the cache
caches
.match(event.request)
.then(function (response) {
// Fall back to network
return response || fetch(event.request);
})
.catch(function () {
// If both fail, show a generic fallback:
return caches.match('/offline.html');
// However, in reality you'd have many different
// fallbacks, depending on URL and headers.
// Eg, a fallback silhouette image for avatars.
}),
);
});
รายการที่คุณใช้เป็นรายการสำรองมักจะเป็นการขึ้นต่อกันในการติดตั้ง
หากหน้าเว็บโพสต์อีเมล Service Worker อาจกลับไปจัดเก็บอีเมลในกล่องจดหมายขาออกของ IndexedDB และตอบกลับโดยแจ้งให้หน้าเว็บทราบว่าการส่งไม่สำเร็จ แต่ระบบเก็บรักษาข้อมูลไว้เรียบร้อยแล้ว
การใช้เทมเพลตฝั่ง Service Worker
เหมาะสำหรับ: หน้าเว็บที่แคชการตอบกลับของเซิร์ฟเวอร์ไม่ได้
การแสดงหน้าเว็บในเซิร์ฟเวอร์จะเร็วกว่า แต่ก็อาจหมายถึงการรวมข้อมูลสถานะที่อาจไม่สมเหตุสมผลในแคช เช่น สถานะการลงชื่อเข้าใช้ หากหน้าเว็บควบคุมโดย Service Worker คุณสามารถเลือกที่จะขอข้อมูล JSON พร้อมกับเทมเพลตและแสดงผลแทนได้
importScripts('templating-engine.js');
self.addEventListener('fetch', function (event) {
var requestURL = new URL(event.request.url);
event.respondWith(
Promise.all([
caches.match('/article-template.html').then(function (response) {
return response.text();
}),
caches.match(requestURL.path + '.json').then(function (response) {
return response.json();
}),
]).then(function (responses) {
var template = responses[0];
var data = responses[1];
return new Response(renderTemplate(template, data), {
headers: {
'Content-Type': 'text/html',
},
});
}),
);
});
นำข้อมูลทุกอย่างมารวมกัน
คุณไม่จำเป็นต้องใช้วิธีใดวิธีหนึ่งเท่านั้น ในความเป็นจริง คุณอาจใช้พารามิเตอร์หลายรายการ โดยขึ้นอยู่กับ URL ของคำขอ ตัวอย่างเช่น trained-to-thrill ใช้
- แคชเมื่อติดตั้งสำหรับ UI และลักษณะการทำงานแบบคงที่
- แคชในการตอบกลับของเครือข่ายสำหรับรูปภาพและข้อมูล Flickr
- ดึงข้อมูลจากแคชและกลับไปใช้เครือข่ายสำหรับคำขอส่วนใหญ่
- ดึงข้อมูลจากแคช แล้วจึงดึงจากเครือข่ายสำหรับผลการค้นหาของ Flickr
เพียงดูคำขอและตัดสินใจว่าจะดำเนินการอย่างไร
self.addEventListener('fetch', function (event) {
// Parse the URL:
var requestURL = new URL(event.request.url);
// Handle requests to a particular host specifically
if (requestURL.hostname == 'api.example.com') {
event.respondWith(/* some combination of patterns */);
return;
}
// Routing for local URLs
if (requestURL.origin == location.origin) {
// Handle article URLs
if (/^\/article\//.test(requestURL.pathname)) {
event.respondWith(/* some other combination of patterns */);
return;
}
if (/\.webp$/.test(requestURL.pathname)) {
event.respondWith(/* some other combination of patterns */);
return;
}
if (request.method == 'POST') {
event.respondWith(/* some other combination of patterns */);
return;
}
if (/cheese/.test(requestURL.pathname)) {
event.respondWith(
new Response('Flagrant cheese error', {
status: 512,
}),
);
return;
}
}
// A sensible default pattern
event.respondWith(
caches.match(event.request).then(function (response) {
return response || fetch(event.request);
}),
);
});
อ่านเพิ่มเติม
- Service Worker และ Cache Storage API
- JavaScript Promises - บทนำ: คู่มือเกี่ยวกับ Promise
เครดิต
สำหรับไอคอนที่น่ารัก
- Code โดย buzzyrobot
- ปฏิทิน โดย Scott Lewis
- Network by Ben Rizzo
- SD โดย Thomas Le Bas
- CPU โดย iconsmind.com
- ถังขยะ โดย trasnik
- การแจ้งเตือนโดย @daosme
- เลย์เอาต์โดย Mister Pixel
- Cloud โดย P.J. Onori
และขอขอบคุณ Jeff Posnick ที่ช่วยตรวจหาข้อผิดพลาดมากมาย ก่อนที่ฉันจะกด "เผยแพร่"