`Service Worker` trong JavaScript
Bài viết này giải thích khái niệm về Service Worker
trong JavaScript.
Chúng tôi sẽ giải thích từng bước từ căn bản về Service Worker
đến việc kiểm soát bộ nhớ đệm thực tế.
YouTube Video
offline.html
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <title>Offline</title>
6</head>
7<body>
8 <h1>You are offline</h1>
9 <p>This is the offline fallback page.</p>
10</body>
11</html>
style.css
1body {
2 font-family: sans-serif;
3 background-color: #f0f0f0;
4 padding: 20px;
5}
6h1 {
7 color: #333;
8}
javascript-service-worker.html
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <title>JavaScript & HTML</title>
6 <style>
7 * {
8 box-sizing: border-box;
9 }
10
11 body {
12 margin: 0;
13 padding: 1em;
14 padding-bottom: 10em;
15 font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
16 background-color: #f7f9fc;
17 color: #333;
18 line-height: 1.6;
19 }
20
21 .container {
22 max-width: 800px;
23 margin: 0 auto;
24 padding: 1em;
25 background-color: #ffffff;
26 border: 1px solid #ccc;
27 border-radius: 10px;
28 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
29 }
30
31 .container-flex {
32 display: flex;
33 flex-wrap: wrap;
34 gap: 2em;
35 max-width: 1000px;
36 margin: 0 auto;
37 padding: 1em;
38 background-color: #ffffff;
39 border: 1px solid #ccc;
40 border-radius: 10px;
41 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
42 }
43
44 .left-column, .right-column {
45 flex: 1 1 200px;
46 min-width: 200px;
47 }
48
49 h1, h2 {
50 font-size: 1.2rem;
51 color: #007bff;
52 margin-top: 0.5em;
53 margin-bottom: 0.5em;
54 border-left: 5px solid #007bff;
55 padding-left: 0.6em;
56 background-color: #e9f2ff;
57 }
58
59 button {
60 display: block;
61 margin: 1em auto;
62 padding: 0.75em 1.5em;
63 font-size: 1rem;
64 background-color: #007bff;
65 color: white;
66 border: none;
67 border-radius: 6px;
68 cursor: pointer;
69 transition: background-color 0.3s ease;
70 }
71
72 button:hover {
73 background-color: #0056b3;
74 }
75
76 #output {
77 margin-top: 1em;
78 background-color: #1e1e1e;
79 color: #0f0;
80 padding: 1em;
81 border-radius: 8px;
82 min-height: 200px;
83 font-family: Consolas, monospace;
84 font-size: 0.95rem;
85 overflow-y: auto;
86 white-space: pre-wrap;
87 }
88
89 .highlight {
90 outline: 3px solid #ffc107; /* yellow border */
91 background-color: #fff8e1; /* soft yellow background */
92 transition: background-color 0.3s ease, outline 0.3s ease;
93 }
94
95 .active {
96 background-color: #28a745; /* green background */
97 color: #fff;
98 box-shadow: 0 0 10px rgba(40, 167, 69, 0.5);
99 transition: background-color 0.3s ease, box-shadow 0.3s ease;
100 }
101 </style>
102</head>
103<body>
104 <div class="container">
105 <h1>JavaScript Console</h1>
106 <button id="executeBtn">Execute</button>
107 <div id="output"></div>
108 </div>
109
110 <div class="container">
111 <h2>HTML Sample</h2>
112 <button id="fetchBtn">Fetch Test</button>
113 </div>
114
115 <script>
116 // Override console.log to display messages in the #output element
117 (function () {
118 // Override console.log
119 const originalLog = console.log;
120 console.log = function (...args) {
121 originalLog.apply(console, args);
122 const message = document.createElement('div');
123 message.textContent = args.map(String).join(' ');
124 output.appendChild(message);
125 };
126
127 // Override console.error
128 const originalError = console.error;
129 console.error = function (...args) {
130 originalError.apply(console, args);
131 const message = document.createElement('div');
132 message.textContent = args.map(String).join(' ');
133 message.style.color = 'red'; // Color error messages red
134 output.appendChild(message);
135 };
136 })();
137
138 document.getElementById('executeBtn').addEventListener('click', () => {
139 // Prevent multiple loads
140 if (document.getElementById('externalScript')) return;
141
142 const script = document.createElement('script');
143 script.src = 'javascript-service-worker.js';
144 script.id = 'externalScript';
145 //script.onload = () => console.log('javascript-service-worker.js loaded and executed.');
146 //script.onerror = () => console.log('Failed to load javascript-service-worker.js.');
147 document.body.appendChild(script);
148 });
149 </script>
150</body>
151</html>
Service Worker
trong JavaScript
Service Worker
là một tính năng của JavaScript nằm giữa trình duyệt và mạng, cho phép lưu bộ nhớ đệm các yêu cầu và hỗ trợ hoạt động ngoại tuyến. Nó là công nghệ cốt lõi của PWA (Ứng dụng Web Tiến Tiến) và mang lại trải nghiệm giống ứng dụng gốc cho các ứng dụng web.
Service Worker
là gì?
Service Worker
là một tệp JavaScript chạy trên luồng nền của trình duyệt. Nó chạy trên một luồng riêng biệt với trang web, không thể truy cập giao diện người dùng, nhưng có thể chặn các yêu cầu mạng, quản lý bộ nhớ đệm và xử lý thông báo đẩy.
Các tính năng chính của Service Worker
bao gồm:.
- Nó chỉ hoạt động với HTTPS, trừ localhost.
- Nó sử dụng API bất đồng bộ dựa trên Promise.
- Nó dựa trên sự kiện, sử dụng các sự kiện như
install
,activate
,fetch
vàpush
.
Đăng ký một Service Worker
Trước tiên, hãy viết mã để đăng ký Service Worker
trong trình duyệt.
1if ('serviceWorker' in navigator) {
2 window.addEventListener('load', () => {
3 navigator.serviceWorker.register('/sw.js')
4 .then(registration => {
5 console.log(
6 'Service Worker registered with scope:',
7 registration.scope
8 );
9 })
10 .catch(error => {
11 console.error('Service Worker registration failed:', error);
12 });
13 });
14}
Giải thích
- Dùng
navigator.serviceWorker.register()
để đăng ký/sw.js
(tệpService Worker
). - Bạn có thể dùng
then
để xử lý thành công vàcatch
để xử lý lỗi trong quá trình đăng ký. registration.scope
đại diện cho phạm vi đường dẫn (scope) bị ảnh hưởng bởiService Worker
.- Mặc định, phạm vi là thư mục nơi tệp đã được đăng ký (trong trường hợp này là
/sw.js
) và các thư mục con của nó.
Phạm vi của Service Worker
Nếu bạn muốn giới hạn phạm vi, bạn có thể chỉ định scope
bằng cách sử dụng đối số thứ hai của register
.
1navigator.serviceWorker.register('/sw.js', { scope: '/app/' })
2.then(registration => {
3 console.log(
4 'Service Worker registered with scope:',
5 registration.scope
6 );
7});
Giải thích
- Với thiết lập này, chỉ các trang nằm dưới
/app/
mới được kiểm soát bởiService Worker
.
Tạo tệp Service Worker
Tiếp theo, tạo một tệp tên là sw.js
và triển khai các sự kiện cơ bản.
1// sw.js
2const CACHE_NAME = 'my-cache-v1';
3const urlsToCache = [
4 '/',
5 '/index.html',
6 '/styles.css',
7 '/script.js',
8 '/offline.html'
9];
Đoạn mã này định nghĩa một danh sách các tài nguyên sẽ được lưu vào bộ nhớ đệm.
Vai trò và cơ chế của từng sự kiện
install
1// Install event (initial caching)
2self.addEventListener('install', event => {
3 console.log('[ServiceWorker] Install');
4 event.waitUntil(
5 caches.open(CACHE_NAME).then(cache => {
6 console.log('[ServiceWorker] Caching app shell');
7 return cache.addAll(urlsToCache);
8 })
9 );
10});
self.addEventListener('install')
được kích hoạt khiService Worker
được đăng ký lần đầu tiên. Ở giai đoạn này, các tệp cần thiết sẽ được lưu vào bộ nhớ đệm trước.
activate
1// Activation event (delete old caches)
2self.addEventListener('activate', event => {
3 console.log('[ServiceWorker] Activate');
4 event.waitUntil(
5 caches.keys().then(keyList => {
6 return Promise.all(keyList.map(key => {
7 if (key !== CACHE_NAME) {
8 console.log('[ServiceWorker] Removing old cache:', key);
9 return caches.delete(key);
10 }
11 }));
12 })
13 );
14 return self.clients.claim();
15});
- Trong sự kiện
activate
, các bộ nhớ đệm cũ sẽ bị xóa để tối ưu hóa lưu trữ. Chỉ bộ nhớ đệm của phiên bản mới được giữ lại.
fetch
1// Fetch event (cache-first strategy)
2self.addEventListener('fetch', event => {
3 console.log('[ServiceWorker] Fetch', event.request.url);
4 event.respondWith(
5 caches.match(event.request).then(response => {
6 return response || fetch(event.request).catch(() => caches.match('/offline.html'));
7 })
8 );
9});
Tất cả các yêu cầu HTTP đều được chặn—nếu có phiên bản trong bộ nhớ đệm thì trả về; nếu không, sẽ lấy từ mạng. Khi ngoại tuyến, một trang thay thế (ví dụ: offline.html
) sẽ được trả về.
Xác nhận thao tác
Hãy cùng kiểm tra thực tế cách hoạt động của Service Worker
.
1document.getElementById('fetchBtn').addEventListener('click', () => {
2 fetch('/style.css')
3 .then(response => response.text())
4 .then(data => {
5 console.log('Fetched data:', data);
6 })
7 .catch(error => {
8 console.error('Fetch failed:', error);
9 });
10});
- Tại đây, chúng tôi kiểm tra việc đăng ký
Service Worker
và hành vi truy xuất tài nguyên bằng cách nhấn nút kiểm tra.
Ví dụ về các chiến lược lưu bộ nhớ đệm
Dưới đây là các chiến lược lưu bộ nhớ đệm phổ biến:.
Ưu tiên Bộ nhớ đệm (Cache First)
Dưới đây là một ví dụ triển khai cho chiến lược Cache First:.
1self.addEventListener('fetch', event => {
2 event.respondWith(
3 caches.match(event.request).then(response => {
4 return response || fetch(event.request);
5 })
6 );
7});
- Đoạn mã này triển khai chiến lược ưu tiên bộ nhớ đệm (cache-first), trong đó tài nguyên được yêu cầu sẽ được lấy từ bộ nhớ đệm nếu có; nếu không có thì sẽ lấy từ mạng.
Ưu tiên Mạng (Network First)
Dưới đây là một ví dụ triển khai cho chiến lược Network First:.
1self.addEventListener('fetch', event => {
2 event.respondWith(
3 fetch(event.request)
4 .then(response => {
5 return caches.open(CACHE_NAME).then(cache => {
6 cache.put(event.request, response.clone());
7 return response;
8 });
9 })
10 .catch(() => caches.match(event.request))
11 );
12});
- Đoạn mã này triển khai chiến lược ưu tiên mạng (network-first), trong đó tài nguyên được yêu cầu sẽ được lấy từ mạng trước; nếu thất bại thì sẽ lấy từ bộ nhớ đệm.
Chỉ lưu bộ nhớ đệm các tệp style và JavaScript, truy cập API theo thời gian thực
Dưới đây là ví dụ triển khai khi chỉ lưu bộ nhớ đệm các tệp style và JavaScript, còn API được truy cập theo thời gian thực:.
1self.addEventListener('fetch', event => {
2 if (event.request.url.includes('/api/')) {
3 // Fetch API responses in real-time without caching
4 return;
5 }
6
7 // Use cache-first strategy for static files
8 event.respondWith(
9 caches.match(event.request).then(response => {
10 return response || fetch(event.request);
11 })
12 );
13});
- Đoạn mã này luôn truy cập các yêu cầu API theo thời gian thực và áp dụng chiến lược ưu tiên bộ nhớ đệm cho các tệp tĩnh như stylesheet và JavaScript.
Quy trình cập nhật
Quy trình cập nhật của Service Worker như sau:.
- Một tệp
sw.js
mới được phát hiện. - Sự kiện
install
được kích hoạt. - Đợi cho đến khi
Service Worker
trước đó trở nên không hoạt động. - Sự kiện
activate
được kích hoạt. - Chuyển sang
Service Worker
mới. - Sự kiện
controllerchange
được kích hoạt.
Phát hiện cập nhật
Một khi Service Worker
đã được cài đặt, phiên bản cũ vẫn tiếp tục được sử dụng cho đến lần truy cập tiếp theo. Để áp dụng cập nhật, thông thường sử dụng mã phát hiện cập nhật và tải lại trang.
1navigator.serviceWorker.addEventListener('controllerchange', () => {
2 window.location.reload();
3});
- Sự kiện
controllerchange
được kích hoạt khi bộ điều khiển (controller) của Service Worker, tức là Service Worker đang kiểm soát trang hiện tại, thay đổi. - Các trang đã mở vẫn tiếp tục sử dụng Service Worker hiện tại, và Service Worker mới được cài đặt sẽ không có hiệu lực ngay lập tức trên những trang đó. Do đó, một kỹ thuật được sử dụng là tận dụng sự kiện
controllerchange
để phát hiện khi bộ điều khiển mới trở nên hoạt động và sau đó tải lại trang để áp dụng cập nhật ngay lập tức.
Lưu ý và Thực hành tốt nhất
Khi sử dụng Service Worker
, hãy lưu ý các điểm sau:.
-
Yêu cầu HTTPS Do giới hạn bảo mật, nó không hoạt động trên
http://
trừ khi ởlocalhost
. -
Tên tệp được băm (Hashed File Names) Tên bộ nhớ đệm có thể bao gồm tên tệp, URL và thông tin phiên bản.
-
Giao tiếp với Client Sử dụng
postMessage
để giao tiếp giữaService Worker
và JavaScript của trang.
Tóm tắt
Service Worker
là một công nghệ thiết yếu để hỗ trợ hoạt động ngoại tuyến và cải thiện hiệu suất cho các ứng dụng web. Bằng cách hiểu quy trình cơ bản về cài đặt, kích hoạt, xử lý truy xuất dữ liệu và triển khai chiến lược lưu bộ nhớ đệm phù hợp, bạn có thể xây dựng các ứng dụng web chất lượng cao hơn.
Bạn có thể làm theo bài viết trên bằng cách sử dụng Visual Studio Code trên kênh YouTube của chúng tôi. Vui lòng ghé thăm kênh YouTube.