การโหลดล่วงหน้าของการนำทางช่วยให้คุณข้ามระยะเวลาเริ่มต้นของโปรแกรมทำงานของบริการได้ด้วยการส่งคำขอไปพร้อมๆ กัน
สรุป
- ในบางสถานการณ์ เวลาเปิดเครื่องของโปรแกรมทำงานของบริการอาจทำให้การตอบสนองของเครือข่ายล่าช้า
- การโหลดล่วงหน้าสำหรับการนำทางมีพร้อมให้ใช้งานในเครื่องมือเบราว์เซอร์หลักๆ 3 อย่าง โดยช่วยให้คุณส่งคำขอควบคู่ไปกับการเปิดเครื่อง Service Worker ได้
- คุณสามารถแยกคำขอโหลดล่วงหน้าออกจากการนำทางแบบปกติได้โดยใช้ส่วนหัว และแสดงเนื้อหาที่แตกต่างกัน
ปัญหา
เมื่อคุณไปยังเว็บไซต์ที่ใช้ Service Worker เพื่อจัดการเหตุการณ์การดึงข้อมูล เบราว์เซอร์จะขอให้โปรแกรมทำงานของบริการตอบกลับ ซึ่งเกี่ยวข้องกับการเปิดเครื่อง Service Worker (หากไม่ได้ทำงานอยู่) และจ่ายเหตุการณ์การดึงข้อมูล
เวลาเปิดเครื่องขึ้นอยู่กับอุปกรณ์และเงื่อนไขการใช้งาน ซึ่งโดยทั่วไปจะอยู่ที่ประมาณ 50 มิลลิวินาที บนอุปกรณ์เคลื่อนที่ จะใช้เวลามากกว่า 250 มิลลิวินาที ในกรณีที่รุนแรง (อุปกรณ์ทำงานช้า, CPU ที่ได้รับผลกระทบ) อาจใช้เวลามากกว่า 500 มิลลิวินาที อย่างไรก็ตาม เนื่องจากโปรแกรมทำงานของบริการยังคงทำงานตามช่วงเวลาที่เบราว์เซอร์กำหนดระหว่างเหตุการณ์ต่างๆ คุณจะได้รับการหน่วงเวลานี้เป็นครั้งคราวเท่านั้น เช่น เมื่อผู้ใช้ไปยังเว็บไซต์ของคุณจากแท็บใหม่หรือเว็บไซต์อื่น
เวลาเปิดเครื่องจะไม่เป็นปัญหาหากคุณตอบสนองจากแคช เนื่องจากการข้ามเครือข่ายมีมากกว่าความล่าช้าเมื่อเปิดเครื่อง แต่ถ้าคุณกำลังตอบโดยใช้เครือข่าย...
คำขอเครือข่ายล่าช้าเนื่องจากการเปิดเครื่องของ Service Worker
เราจะยังคงลดเวลาเปิดเครื่องอย่างต่อเนื่องโดยการใช้การแคชโค้ดใน V8, การข้าม Service Worker ที่ไม่มีเหตุการณ์การดึงข้อมูล, การเปิดตัว Service Worker แบบคาดเดา และการเพิ่มประสิทธิภาพอื่นๆ แต่เวลาเปิดเครื่องจะมากกว่า 0 เสมอ
Facebook แจ้งผลของปัญหานี้ให้เราทราบ และถามถึงวิธีส่งคำขอการนำทางไปพร้อมๆ กัน ดังนี้
การโหลดการนำทางล่วงหน้าเพื่อช่วยเหลือ
การโหลดล่วงหน้าของการนำทางเป็นฟีเจอร์ที่ให้คุณพูดว่า "เมื่อผู้ใช้ส่งคำขอการนำทาง GET ให้เริ่มคำขอเครือข่ายขณะที่โปรแกรมทำงานของบริการกำลังเปิดเครื่อง"
ความล่าช้าของการเริ่มต้นใช้งานจะยังคงอยู่ แต่จะไม่บล็อกคำขอเครือข่าย ดังนั้นผู้ใช้จะได้รับเนื้อหาได้เร็วขึ้น
นี่เป็นวิดีโอแสดงการทำงานจริง ซึ่งโปรแกรมทำงานของบริการได้ระบุความล่าช้าในการเริ่มต้นใช้งาน 500 มิลลิวินาทีที่ตั้งใจไว้โดยใช้เวลาวนซ้ำ
นี่คือการสาธิต เพื่อใช้ประโยชน์จากการโหลดการนำทางล่วงหน้า คุณจะต้องมีเบราว์เซอร์ที่รองรับ
เปิดใช้งานการโหลดการนำทางล่วงหน้า
addEventListener('activate', event => {
event.waitUntil(async function() {
// Feature-detect
if (self.registration.navigationPreload) {
// Enable navigation preloads!
await self.registration.navigationPreload.enable();
}
}());
});
คุณสามารถโทรหา navigationPreload.enable()
ได้ทุกเมื่อที่ต้องการ หรือปิดใช้ด้วย navigationPreload.disable()
อย่างไรก็ตาม เนื่องจากเหตุการณ์ fetch
ของคุณจำเป็นต้องใช้ประโยชน์จากเหตุการณ์ดังกล่าว คุณจึงควรเปิดและปิดใช้เหตุการณ์ในเหตุการณ์ activate
ของโปรแกรมทำงานของบริการ
การใช้คำตอบที่โหลดล่วงหน้า
ตอนนี้เบราว์เซอร์จะทำการโหลดล่วงหน้าสำหรับการนำทาง แต่คุณยังต้องใช้การตอบสนองต่อไปนี้
addEventListener('fetch', event => {
event.respondWith(async function() {
// Respond from the cache if we can
const cachedResponse = await caches.match(event.request);
if (cachedResponse) return cachedResponse;
// Else, use the preloaded response, if it's there
const response = await event.preloadResponse;
if (response) return response;
// Else try the network.
return fetch(event.request);
}());
});
event.preloadResponse
เป็นสัญญาที่จะแก้ปัญหาได้ด้วยการตอบกลับในกรณีต่อไปนี้
- การโหลดการนำทางล่วงหน้าเปิดอยู่
- คำขอนี้เป็นคำขอ
GET
- คำขอนี้เป็นคำขอการนำทาง (เบราว์เซอร์ที่สร้างเมื่อหน้าเว็บกำลังโหลดหน้าเว็บรวมถึง iframe)
มิฉะนั้น event.preloadResponse
จะยังคงอยู่ แต่แก้ไขได้ด้วย undefined
คำตอบที่กำหนดเองสำหรับการโหลดล่วงหน้า
หากหน้าเว็บต้องการข้อมูลจากเครือข่าย วิธีที่เร็วที่สุดคือการส่งคำขอใน Service Worker และสร้างการตอบสนองแบบสตรีมรายการเดียวที่มีส่วนต่างๆ จากแคชและส่วนต่างๆ จากเครือข่าย
สมมติว่าเราต้องการแสดงบทความ:
addEventListener('fetch', event => {
const url = new URL(event.request.url);
const includeURL = new URL(url);
includeURL.pathname += 'include';
if (isArticleURL(url)) {
event.respondWith(async function() {
// We're going to build a single request from multiple parts.
const parts = [
// The top of the page.
caches.match('/article-top.include'),
// The primary content
fetch(includeURL)
// A fallback if the network fails.
.catch(() => caches.match('/article-offline.include')),
// The bottom of the page
caches.match('/article-bottom.include')
];
// Merge them all together.
const {done, response} = await mergeResponses(parts);
// Wait until the stream is complete.
event.waitUntil(done);
// Return the merged response.
return response;
}());
}
});
ข้างต้น mergeResponses
คือฟังก์ชันเล็กๆ ที่ผสานสตรีมของคำขอแต่ละรายการ ซึ่งหมายความว่าเราสามารถแสดงส่วนหัวที่แคชไว้ในขณะที่เนื้อหาของเครือข่ายสตรีมเข้ามาได้
ซึ่งเร็วกว่า "App Shell" เมื่อมีการสร้างคำขอเครือข่ายพร้อมกับคำขอหน้าเว็บ และเนื้อหาจะสามารถสตรีมโดยไม่มีการแฮ็กหลัก
แต่คำขอสำหรับ includeURL
จะล่าช้าเนื่องจากเวลาเริ่มต้นของโปรแกรมทำงานของบริการ เราสามารถใช้การโหลดการนำทางล่วงหน้าเพื่อแก้ไขปัญหานี้ได้เช่นกัน แต่ในกรณีนี้เราไม่ต้องการโหลดหน้าเว็บแบบเต็มล่วงหน้า เราต้องการโหลดการรวมล่วงหน้า
เพื่อรองรับความต้องการนี้ ระบบจะส่งส่วนหัวไปพร้อมกับคำขอโหลดล่วงหน้าทุกรายการ ดังนี้
Service-Worker-Navigation-Preload: true
เซิร์ฟเวอร์จะใช้วิธีนี้ในการส่งเนื้อหาสำหรับคำขอการโหลดการนำทางล่วงหน้าแตกต่างจากที่ส่งสำหรับคำขอการนำทางปกติ เพียงอย่าลืมเพิ่มส่วนหัว Vary: Service-Worker-Navigation-Preload
เพื่อให้แคชทราบว่าการตอบกลับของคุณแตกต่างกัน
ตอนนี้เราสามารถใช้คำขอโหลดล่วงหน้าได้แล้ว:
// Try to use the preload
const networkContent = Promise.resolve(event.preloadResponse)
// Else do a normal fetch
.then(r => r || fetch(includeURL))
// A fallback if the network fails.
.catch(() => caches.match('/article-offline.include'));
const parts = [
caches.match('/article-top.include'),
networkContent,
caches.match('/article-bottom')
];
เปลี่ยนส่วนหัว
ตามค่าเริ่มต้น ค่าของส่วนหัว Service-Worker-Navigation-Preload
คือ true
แต่คุณกำหนดเป็นอะไรก็ได้ตามต้องการ
navigator.serviceWorker.ready.then(registration => {
return registration.navigationPreload.setHeaderValue(newValue);
}).then(() => {
console.log('Done!');
});
ตัวอย่างเช่น คุณอาจตั้งค่าให้เป็นรหัสของโพสต์ล่าสุดที่คุณแคชไว้ในเครื่อง เพื่อให้เซิร์ฟเวอร์แสดงผลเฉพาะข้อมูลที่ใหม่กว่า
การรับรัฐ
คุณดูสถานะการโหลดการนำทางล่วงหน้าได้โดยใช้ getState
ดังนี้
navigator.serviceWorker.ready.then(registration => {
return registration.navigationPreload.getState();
}).then(state => {
console.log(state.enabled); // boolean
console.log(state.headerValue); // string
});
ขอขอบคุณ Matt Falkenhagen และ Tsuyoshi Horo ที่ทำงานเกี่ยวกับฟีเจอร์นี้ รวมถึงความช่วยเหลือเกี่ยวกับบทความนี้ และขอขอบคุณทุกคนที่เกี่ยวข้องในความพยายามในการกําหนดมาตรฐาน