Tính năng tải trước điều hướng giúp bạn khắc phục thời gian khởi động của worker dịch vụ bằng cách thực hiện song song các yêu cầu.
Tóm tắt
- Trong một số trường hợp, thời gian khởi động trình chạy dịch vụ có thể làm chậm phản hồi mạng.
- Có trong 3 công cụ trình duyệt chính, tính năng tải trước điều hướng khắc phục vấn đề này bằng cách cho phép bạn đưa ra yêu cầu song song với quá trình khởi động trình chạy dịch vụ.
- Bạn có thể phân biệt các yêu cầu tải trước với các thao tác điều hướng thông thường bằng cách sử dụng một tiêu đề và phân phát nội dung khác.
Vấn đề
Khi bạn chuyển đến một trang web sử dụng service worker để xử lý các sự kiện tìm nạp, trình duyệt sẽ yêu cầu service worker phản hồi. Thao tác này liên quan đến việc khởi động trình chạy dịch vụ (nếu trình chạy này chưa chạy) và gửi sự kiện tìm nạp.
Thời gian khởi động phụ thuộc vào thiết bị và điều kiện. Độ trễ này thường vào khoảng 50 mili giây. Trên thiết bị di động, thời gian này thường là 250 mili giây. Trong trường hợp nghiêm trọng (thiết bị chậm, CPU gặp sự cố), thời gian này có thể vượt quá 500 mili giây. Tuy nhiên, vì trình chạy dịch vụ vẫn hoạt động trong một khoảng thời gian do trình duyệt xác định giữa các sự kiện, nên bạn chỉ gặp phải độ trễ này trong một số trường hợp, chẳng hạn như khi người dùng chuyển đến trang web của bạn từ một thẻ mới hoặc một trang web khác.
Thời gian khởi động không phải là vấn đề nếu bạn đang phản hồi từ bộ nhớ đệm, vì lợi ích của việc bỏ qua mạng lớn hơn độ trễ khởi động. Nhưng nếu bạn đang phản hồi bằng mạng…
Yêu cầu mạng bị trì hoãn do quá trình khởi động trình chạy dịch vụ.
Chúng tôi sẽ tiếp tục giảm thời gian khởi động bằng cách sử dụng tính năng lưu mã vào bộ nhớ đệm trong V8, bằng cách bỏ qua những worker dịch vụ không có sự kiện tìm nạp, bằng cách khởi chạy worker dịch vụ một cách suy đoán và các hoạt động tối ưu hoá khác. Tuy nhiên, thời gian khởi động sẽ luôn lớn hơn 0.
Facebook đã lưu ý chúng tôi về tác động của vấn đề này và yêu cầu một cách để thực hiện các yêu cầu điều hướng song song:
Tính năng tải trước thông tin chỉ đường sẽ giúp bạn
Tải trước dữ liệu điều hướng là một tính năng cho phép bạn nói rằng "Khi người dùng đưa ra yêu cầu điều hướng GET, hãy bắt đầu yêu cầu mạng trong khi trình chạy dịch vụ đang khởi động".
Độ trễ khi khởi động vẫn còn, nhưng không chặn yêu cầu mạng, vì vậy người dùng sẽ nhận được nội dung sớm hơn.
Dưới đây là video minh hoạ hoạt động này, trong đó service worker được cố ý trì hoãn khởi động 500 mili giây bằng cách sử dụng vòng lặp while:
Đây là bản minh hoạ. Để tận hưởng lợi ích của tính năng tải trước điều hướng, bạn cần có một trình duyệt hỗ trợ tính năng này.
Kích hoạt tính năng tải trước điều hướng
addEventListener('activate', event => {
event.waitUntil(async function() {
// Feature-detect
if (self.registration.navigationPreload) {
// Enable navigation preloads!
await self.registration.navigationPreload.enable();
}
}());
});
Bạn có thể gọi navigationPreload.enable()
bất cứ khi nào bạn muốn hoặc tắt chế độ này bằng cách nhấn navigationPreload.disable()
. Tuy nhiên, vì sự kiện fetch
của bạn cần sử dụng sự kiện này, nên tốt nhất là bạn nên bật và tắt sự kiện này trong sự kiện activate
của worker dịch vụ.
Sử dụng câu trả lời được tải sẵn
Giờ đây, trình duyệt sẽ thực hiện các thao tác tải trước cho các thao tác điều hướng, nhưng bạn vẫn cần sử dụng phản hồi:
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
là một lời hứa sẽ được thực hiện bằng một phản hồi, nếu:
- Đã bật tính năng tải trước thông tin chỉ đường.
- Đây là yêu cầu
GET
. - Yêu cầu này là yêu cầu điều hướng (trình duyệt tạo yêu cầu này khi tải trang, kể cả khi tải iframe).
Nếu không, event.preloadResponse
vẫn ở đó, nhưng sẽ phân giải bằng undefined
.
Câu trả lời tuỳ chỉnh cho các tệp tải trước
Nếu trang của bạn cần dữ liệu từ mạng, cách nhanh nhất là yêu cầu dữ liệu đó trong trình chạy dịch vụ và tạo một phản hồi truyền trực tuyến duy nhất chứa các phần từ bộ nhớ đệm và các phần từ mạng.
Giả sử chúng ta muốn hiển thị một bài viết:
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;
}());
}
});
Trong ví dụ trên, mergeResponses
là một hàm nhỏ hợp nhất các luồng của từng yêu cầu. Điều này có nghĩa là chúng ta có thể hiển thị tiêu đề được lưu vào bộ nhớ đệm trong khi nội dung mạng phát trực tuyến.
Cách này nhanh hơn mô hình "vỏ ứng dụng" vì yêu cầu mạng được thực hiện cùng với yêu cầu trang và nội dung có thể truyền trực tuyến mà không cần các giải pháp lớn.
Tuy nhiên, yêu cầu đối với includeURL
sẽ bị trì hoãn do thời gian khởi động của trình chạy dịch vụ. Chúng ta cũng có thể sử dụng tính năng tải trước điều hướng để khắc phục vấn đề này. Tuy nhiên, trong trường hợp này, chúng ta không muốn tải trước toàn bộ trang mà chỉ muốn tải trước một phần.
Để hỗ trợ việc này, một tiêu đề sẽ được gửi kèm theo mọi yêu cầu tải trước:
Service-Worker-Navigation-Preload: true
Máy chủ có thể dùng thông tin này để gửi nội dung khác cho các yêu cầu tải trước điều hướng so với yêu cầu điều hướng thông thường. Bạn chỉ cần nhớ thêm tiêu đề Vary: Service-Worker-Navigation-Preload
để bộ nhớ đệm biết rằng các phản hồi của bạn khác nhau.
Giờ đây, chúng ta có thể sử dụng yêu cầu tải trước:
// 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')
];
Thay đổi tiêu đề
Theo mặc định, giá trị của tiêu đề Service-Worker-Navigation-Preload
là true
, nhưng bạn có thể đặt thành bất kỳ giá trị nào bạn muốn:
navigator.serviceWorker.ready.then(registration => {
return registration.navigationPreload.setHeaderValue(newValue);
}).then(() => {
console.log('Done!');
});
Ví dụ: bạn có thể đặt tham số này thành mã nhận dạng của bài đăng gần đây nhất mà bạn đã lưu vào bộ nhớ đệm cục bộ, để máy chủ chỉ trả về dữ liệu mới hơn.
Lấy trạng thái
Bạn có thể tra cứu trạng thái tải trước điều hướng bằng cách sử dụng getState
:
navigator.serviceWorker.ready.then(registration => {
return registration.navigationPreload.getState();
}).then(state => {
console.log(state.enabled); // boolean
console.log(state.headerValue); // string
});
Xin chân thành cảm ơn Matt Falkenhagen và Tsuyoshi Horo vì những đóng góp của họ cho tính năng này cũng như sự trợ giúp của họ đối với bài viết này. Xin chân thành cảm ơn tất cả những người tham gia nỗ lực tiêu chuẩn hoá