เมื่อใช้ Service Worker เราเลิกพยายามแก้ปัญหาแบบออฟไลน์แล้ว และมอบชิ้นส่วนที่เคลื่อนไหวให้กับนักพัฒนาแอปเพื่อแก้ปัญหาด้วยตนเอง ซึ่งจะช่วยให้คุณควบคุมการแคชและวิธีจัดการคําขอได้ ซึ่งหมายความว่าคุณจะสร้างลายของคุณเองได้ มาดูรูปแบบที่เป็นไปได้ 2-3 รูปแบบกัน แต่ในทางปฏิบัติ คุณอาจใช้รูปแบบหลายรูปแบบร่วมกัน ทั้งนี้ขึ้นอยู่กับ URL และบริบท
ดูการสาธิตการใช้งานรูปแบบเหล่านี้บางส่วนได้ที่Trained-to-thrill และวิดีโอนี้ที่แสดงผลกระทบต่อประสิทธิภาพ
เครื่องแคช - กรณีที่ควรจัดเก็บทรัพยากร
Service Worker ช่วยให้คุณจัดการคำขอได้โดยไม่เกี่ยวข้องกับการแคช ดังนั้นเราจะสาธิตแยกกัน อันดับแรกคือแคช ควรทำเมื่อใด
ขณะติดตั้ง - ใช้เป็นทรัพยากร Dependency
Service Worker ให้เหตุการณ์ install
แก่คุณ คุณสามารถใช้คำสั่งนี้เพื่อเตรียมสิ่งต่างๆ ซึ่งต้องพร้อมก่อนจัดการเหตุการณ์อื่นๆ ในระหว่างนี้ เวอร์ชันก่อนหน้าของ 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()
จะปฏิเสธ
ในความตื่นเต้น ฉันใช้สิ่งนี้เพื่อแคชเนื้อหาแบบคงที่
เมื่อติดตั้ง - ไม่ใช่ทรัพยากร 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 จะจัดการกับกรณีเช่นนี้และการดาวน์โหลดขนาดใหญ่ เช่น ภาพยนตร์ ปัจจุบัน API ดังกล่าวรองรับเฉพาะใน Chromium Fork เท่านั้น
เปิดใช้งาน
เหมาะสำหรับการล้างข้อมูลและการย้ายข้อมูล
เมื่อติดตั้ง 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);
});
});
});
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()
เพื่อสร้างสำเนาเพิ่มเติมที่อ่านแยกกันได้
ในการชวนตื่นเต้น ฉันใช้สิ่งนี้เพื่อแคชรูปภาพ 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
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/');
}
});
เมื่อซิงค์ในเบื้องหลัง
การซิงค์ในเบื้องหลังเป็นฟีเจอร์อีกรายการหนึ่งที่สร้างขึ้นจาก Service Worker ซึ่งช่วยให้คุณส่งคําขอซิงค์ข้อมูลในเบื้องหลังแบบครั้งเดียวหรือเป็นช่วง (แบบเฮิวริสติกมาก) ได้ ซึ่งจะเกิดขึ้นแม้ว่าผู้ใช้จะไม่ได้เปิดแท็บเว็บไซต์ของคุณไว้ก็ตาม มีเพียง Service Worker เท่านั้นที่ปลุกระบบ คุณจะขอสิทธิ์ดำเนินการนี้ได้จากหน้าเว็บและระบบจะแสดงข้อความแจ้งให้ผู้ใช้ทราบ
เหมาะสําหรับ: การอัปเดตที่ไม่เร่งด่วน โดยเฉพาะการอัปเดตที่เกิดขึ้นเป็นประจำจนทำให้ผู้ใช้ได้รับข้อความ Push มากเกินไป เช่น ไทม์ไลน์โซเชียลหรือบทความข่าว
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 ของ Analytics, คำขอที่ไม่ใช่ GET
self.addEventListener('fetch', function (event) {
event.respondWith(fetch(event.request));
// or simply 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 is no good to us because it rejects if
// a promise rejects before fulfilling. Let's make a proper
// 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)]));
});
เครือข่ายใช้แคชสำรอง
เหมาะสำหรับการแก้ไขด่วนสำหรับทรัพยากรที่อัปเดตบ่อยครั้งนอก "เวอร์ชัน" ของเว็บไซต์ เช่น บทความ รูปโปรไฟล์ ไทม์ไลน์โซเชียลมีเดีย และลีดเดอร์บอร์ดของเกม
ซึ่งหมายความว่าคุณให้เนื้อหาล่าสุดแก่ผู้ใช้ออนไลน์ แต่ผู้ใช้ออฟไลน์จะได้รับเวอร์ชันที่แคชไว้เก่ากว่า หากคำขอเครือข่ายดำเนินการสำเร็จ คุณอาจต้องอัปเดตรายการแคช
อย่างไรก็ตาม วิธีนี้มีข้อบกพร่อง หากการเชื่อมต่อของผู้ใช้ขาดตอนหรือช้า ผู้ใช้จะต้องรอจนกว่าเครือข่ายจะใช้งานไม่ได้จึงจะได้รับเนื้อหาที่ยอมรับได้อย่างสมบูรณ์ในอุปกรณ์ ซึ่งอาจใช้เวลานานมากและทำให้ผู้ใช้ได้รับประสบการณ์การใช้งานที่ไม่ดี ดูรูปแบบถัดไปที่ชื่อแคชแล้วเครือข่ายเพื่อดูวิธีแก้ปัญหาที่ดีกว่า
self.addEventListener('fetch', function (event) {
event.respondWith(
fetch(event.request).catch(function () {
return caches.match(event.request);
}),
);
});
แคชแล้วเชื่อมต่อกับเครือข่าย
เหมาะสำหรับ: เนื้อหาที่มีการอัปเดตบ่อย เช่น บทความ ไทม์ไลน์โซเชียลมีเดีย และเกม ลีดเดอร์บอร์ด
ซึ่งทำให้หน้าเว็บต้องส่งคำขอ 2 รายการ ได้แก่ 1 รายการไปยังแคช และอีก 1 รายการไปยังเครือข่าย แนวคิดคือแสดงข้อมูลที่แคชไว้ก่อน จากนั้นอัปเดตหน้าเว็บเมื่อ/หากข้อมูลเครือข่ายมาถึง
บางครั้งคุณอาจแทนที่ข้อมูลปัจจุบันได้เมื่อมีข้อมูลใหม่เข้ามา (เช่น ตารางอันดับเกม) แต่วิธีนี้อาจรบกวนเนื้อหาขนาดใหญ่ โดยทั่วไปแล้ว อย่า "ซ่อน" เนื้อหาที่ผู้ใช้อาจกำลังอ่านหรือโต้ตอบอยู่
Twitter จะเพิ่มเนื้อหาใหม่ไว้เหนือเนื้อหาเก่าและปรับตำแหน่งการเลื่อนเพื่อให้ผู้ใช้เลื่อนดูเนื้อหาได้อย่างต่อเนื่อง ซึ่งเป็นไปได้เนื่องจาก Twitter ยังคงจัดเรียงเนื้อหาแบบเป็นเส้นตรงเป็นส่วนใหญ่ ฉันได้คัดลอกรูปแบบนี้สำหรับการฝึกฝนให้ตื่นเต้นแล้วเพื่อแสดงเนื้อหาบนหน้าจอโดยเร็วที่สุด พร้อมทั้งแสดงเนื้อหาล่าสุดทันทีที่มาถึง
โค้ดในหน้าเว็บ:
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 แทน fetch และการใช้ส่วนหัว 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 คุณอาจเลือกขอข้อมูล 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);
}),
);
});
...คุณจะได้ภาพ
เครดิต
…สำหรับไอคอนน่ารักๆ
- โค้ด โดย buzzyrobot
- ปฏิทินโดย Scott Lewis
- เครือข่ายโดย Ben Rizzo
- SD โดย Thomas Le Bas
- CPU โดย Iconmind.com
- Trash โดย trasnik
- การแจ้งเตือน โดย @daosme
- เลย์เอาต์โดย Mister Pixel
- Cloud โดย P.J. Onori
ต้องขอบคุณ Jeff Posnick ที่ตรวจหาข้อผิดพลาด จำนวนมากก่อนที่จะกด "เผยแพร่"
อ่านเพิ่มเติม
- ผู้ปฏิบัติงานบริการ - บทนำ
- Service Worker พร้อมหรือยัง - ติดตามสถานะการใช้งานในเบราว์เซอร์หลัก
- JavaScript Promises - บทนำ - คู่มือ เพื่อสัญญา