Giới thiệu service worker

Giới thiệu service worker
Photo by Jon Tyson from Unsplash

Service worker là một trong những công nghệ quan trọng của các ứng dụng Progressive Web Application. Hai tính năng quan trọng nhất mà công nghệ này mang lại là cache và push notification. Thông thường, người dùng phải có mạng mới có thể truy cập các trang web và sử dụng các ứng dụng dạng web. Tuy nhiên, giờ đây, service worker có thể mang lại công nghệ web tiên tiến hơn, mang lại trải nghiệm tương tự native app.

Cơ bản về service worker

Service worker là một trong những công nghệ lõi của Progressive Web Application (PWA). Service worker có thể coi là một proxy trung gian giữa trình duyệt web và server, cho phép thay đổi và cache các truy vấn. Nhờ có lớp trung gian này, các ứng dụng web có thể hoạt động ngay cả khi không kết nối mạng (offline).

Progressive Web Application là các ứng dụng web nhưng được xây dựng để có thể mang lại trải nghiệm tương tự như các ứng dụng di động. Các ứng dụng PWA cũng có thể được cài đặt như một ứng dụng thông qua trình duyệt. Sau đó, người dùng có thể truy cập trở lại website thông qua icon ngay trên điện thoại, tương tự như khi họ click vào một icon của native app để truy cập phần mềm vậy.

Service worker là một dạng đặc biệt của web worker. Đây là tính năng mới của JavaScript cho phép thực thi code trong một môi trường riêng, tách biệt với JavaScript của trang web. Biện pháp này sẽ mang lại hiệu suất tốt cho những tính toán nặng mà không làm ảnh hưởng đến giao diện.

Cũng bởi vì thực thi trong một môi trường riêng, worker gặp nhiều hạn chế trong việc tương tác, không thể tác động đến DOM, cũng như không thể sử dụng local storage và các truy vấn XHR (trừ Fetch API). Một giới hạn của service worker là nó chỉ hoạt động với giao thức HTTPS (trừ môi trường local).

Trước đây, các trang web không thể hoạt động khi người dùng không có kết nối mạng. Chỉ có các ứng dụng di động mới có thể đưa ra những trải nghiệm để làm việc offline.

Tuy nhiên, với sự xuất hiện của service worker, các ứng dụng web cũng có thể hỗ trợ hoạt động khi người dùng không có mạng, nhờ khả năng cache.

Service worker có thể hỗ trợ cache các tài nguyên tĩnh như các file ảnh, CSS, JavaScript. Đây là những tài nguyên rất nặng với các ứng dụng ngày nay và sẽ tốn nhiều thời gian để tải về. Không những vậy, service worker còn có thể sử dụng fetch API để gửi truy vấn đến server và sẽ trả về kết quả cache nếu như kết nối gặp vấn đề.

Vòng đời của một service worker

Một service worker sẽ trải qua 3 bước trong vòng đời của nó:

  • Registration
  • Installation
  • Activation

Registration

Registration là bước đầu tiên để “đăng ký” service worker với trình duyệt. Sau bước này, trình duyệt sẽ khởi tạo service worker và mọi xử lý sẽ được thực hiện ngầm ở môi trường riêng.

Dưới đây là một đoạn code đơn giản để đăng ký service worker:

if ('serviceWorker' in navigator) {
    window.addEventListener('load', () => {
        navigator.serviceWorker.register('/worker.js').then(
            (registration) => {
            console.log(
                'Service Worker registration completed with scope: ',
                registration.scope,
            )
            },
            (err) => {
            console.log('Service Worker registration failed', err)
            },
        )
    })
} else {
    console.log('Service Workers not supported')
}

Dù trong trường hợp đoạn code trên được gọi đi gọi lại nhiều lần, trình duyệt sẽ chỉ thực hiện đăng ký service worker một lần duy nhất.

Hàm register() có thể nhận thêm tham số về scope. Mặc định, scope của service worker sẽ là toàn bộ thư mục chứa file thực thi worker. Ví dụ dưới đây, worker được đăng ký với scope là thư mục /notifications/:

navigator.serviceWorker.register('/worker.js', {
    scope: '/notifications/',
})

Lưu ý rằng, ký tự / cuối cùng trong đường dẫn cũng có giá trị. Ví dụ trên hoàn toàn khác với scope: '/notifications'.

Installation

Nếu trình duyệt xác định service worker chưa được cập nhật, hoặc chưa đăng ký, nó sẽ cài đặt một service worker:

self.addEventListener('install', (event) => {
    // ...
})

Có thể dùng event install để khởi tạo service worker, ví dụ thực hiện cache các tài nguyên tĩnh hoặc cache truy vấn API.

Activation

Sau khi service worker đã được đăng ký và cài đặt, nó sẽ chuyển sang giai đoạn tiếp theo: activation. Kể từ lúc này, service worker có thể thực hiện các xử lý của nó.

Service worker không có tác dụng trong lần đầu tiên người dùng truy cập trang web. Người dùng cần phải tương tác hoặc tải lại trang thì service worker mới hoạt động.

self.addEventListener('activate', (event) => {
    // ...
})

Trong giai đoạn này, các xử lý thường gặp là xóa dữ liệu cache đã cũ và không còn được sử dụng nữa.

Cập nhật service worker

Khi cần cập nhật service worker, chỉ cần cập nhật file JavaScript của worker đó là đủ. Một file được coi là cập nhật khi có bất kỳ thay đổi nào, kể cả chỉ 1 byte.

Sau khi cập nhật, service work mới sẽ được đăng ký và cài đặt. Thế nhưng, nó sẽ ở trạng lại awaiting. Sau khi tất cả các trang web sử dụng service worker cũ được đóng hết, service worker cũ bị hủy, lúc đó, service worker mới sẽ được activate là bắt đầu hoạt động.

Cơ chế hoạt động này nhằm đảm bảo việc cập nhật service worker sẽ không ảnh hưởng đến tải nghiệm người dùng. Nếu ngay lập tức triển khai phiên bản mới trong khi phiên bản cũ vẫn đang xử lý dữ liệu thì trang web có thể bị lỗi. Lưu ý rằng, cần phải đóng toàn bộ các trang web thì mới có tác dụng. Tải lại trang không hủy service worker cũ và service worker mới sẽ không được kích hoạt.

Làm việc với service worker

Fetch event

Fetch event sẽ phát sinh khi mà có truy vấn từ trình duyệt lên server. Service worker có thể dùng event này để kiểm tra dữ liệu trong cache trước khi thực sự gửi truy vấn.

Ví dụ đoạn code sau sử dụng cache API để kiểm tra truy vấn và sẽ trả về dữ liệu cache nếu có. Nếu dữ liệu chưa được cache thì mới gửi truy vấn lên server.

self.addEventListener('fetch', (event) => {
    event.respondWith(
        caches.match(event.request).then((response) => {
            if (response) {
                // tìm thấy dữ liệu trong cache
                return response
            }
            return fetch(event.request)
        }),
    )
})

Background Sync

Background sync cho phép trì hoãn các truy vấn từ trình duyệt cho đến khi máy tính kết nối mạng. Đây là tính năng quan trọng, cho phép người dùng có thể sử dụng ứng dụng ngay cả khi offline. Dùng, background sync, ứng dụng có thể đưa các truy vấn vào hàng chờ và cập nhật khi đã có thể kết nối đến server.

Ở phía ứng dụng, chúng ta cần những đoạn code như này:

navigator.serviceWorker.ready.then((swRegistration) => {
    return swRegistration.sync.register('event1')
})

Service worker sẽ trigger event sync:

self.addEventListener('sync', (event) => {
    if (event.tag == 'event1') {
        event.waitUntil(doSomething())
    }
})

Hàm doSomething() trả về một promise. Nếu gặp lỗi, một sync event khác sẽ được đưa vào hàng chờ và worker sẽ tiếp tục thử lại cho đến khi nào thành công. Điều này cho phép ứng dụng có thể ngay lập tức cập nhật dữ liệu từ server khi có kết nối mạng.

Push Event

Service worker cho phép ứng dụng có thể cung cấp thông báo dạng Push Notification tương tự như các ứng dụng di động thông thường bằng cách sử dụng Push API và Notification API.

Push và Notification thực chất là những kỹ thuật riêng biệt, nhưng thường được sử dụng kết hợp với nhau. Do đó, chúng ta thường nghe đến thuật ngữ “Push Notification”.

Push cung cấp khả năng server đưa dữ liệu xuống cho service worker trên client mà không cần truy vấn. Notification là phương thức service worker thông báo đến người dùng.

Vì service worker sẽ được thực thi khi ứng dụng không hoạt đông, nó có thể nhận dữ liệu từ server và thông báo cho người dùng. Ngoài ra, nó cũng có thể cập nhật một vài thành phần của ứng dụng.

Push event được khởi tạo ở backend, thông qua một số dịch vụ push ví dụ Firebase. Dưới đây là một ví dụ service worker nhận thông tin từ server:

self.addEventListener('push', (event) => {
    const options = { body: 'Body of the message' }
    event.waitUntil(
        self.registration.showNotification('Notification', options)
    )
})

console.log

Nếu sử dụng console.log và các hàm tương tự trong service worker, những thông tin này chỉ có thể xem được nếu thiết lập “preserve log” trong DevTools. Nếu không, service worker hoạt động trước khi trang web được tải và mỗi lần tải trang, toàn bộ log sẽ bị xóa.

Kết luận

Trên đây là những kiến thức cơ bản về service worker mà tôi tìm hiểu được, và còn rất nhiều vấn đề liên quan khác cần nghiên cứu thêm. Hy vọng bài viết này đã đem đến những thông tin cơ bản nhất về service worker.

Tôi xin lỗi nếu bài viết có bất kỳ typo nào. Nếu bạn nhận thấy điều gì bất thường, xin hãy cho tôi biết.

Nếu có bất điều gì muốn nói, bạn có thể liên hệ với tôi qua các mạng xã hội, tạo discussion hoặc report issue trên Github.