Xây dựng hệ thống tải hình ảnh thân thiện với ngoại tuyến

theanh

Administrator
Nhân viên
Vì vậy, bạn đang điền vào một biểu mẫu trực tuyến và nó yêu cầu bạn tải lên một tệp. Bạn nhấp vào đầu vào, chọn một tệp từ máy tính để bàn của mình và mọi thứ đã ổn. Nhưng có điều gì đó xảy ra. Mạng bị ngắt, tệp biến mất và bạn phải tải lại tệp. Kết nối mạng kém có thể khiến bạn mất nhiều thời gian vô lý để cố gắng tải tệp lên thành công.

Điều làm hỏng trải nghiệm của người dùng bắt nguồn từ việc phải liên tục kiểm tra tính ổn định của mạng và thử tải lên nhiều lần. Mặc dù chúng ta không thể làm được nhiều về kết nối mạng, nhưng với tư cách là nhà phát triển, chúng ta luôn có thể làm gì đó để giảm bớt nỗi đau đi kèm với vấn đề này.

Một trong những cách chúng ta có thể giải quyết vấn đề này là điều chỉnh hệ thống tải ảnh lên theo cách cho phép người dùng tải ảnh lên ngoại tuyến — loại bỏ nhu cầu về kết nối mạng đáng tin cậy, sau đó hệ thống sẽ thử lại quy trình tải lên khi mạng ổn định mà không cần người dùng can thiệp.

Bài viết này sẽ tập trung vào việc giải thích cách xây dựng hệ thống tải ảnh thân thiện với ngoại tuyến bằng các công nghệ PWA (ứng dụng web tiến bộ) như IndexedDB, trình xử lý dịch vụ và API đồng bộ hóa nền. Chúng tôi cũng sẽ tóm tắt các mẹo để cải thiện trải nghiệm người dùng cho hệ thống này.

Lên kế hoạch cho hệ thống tải ảnh ngoại tuyến​

Sau đây là sơ đồ luồng cho hệ thống tải ảnh thân thiện với ngoại tuyến.



Như thể hiện trong sơ đồ luồng, quy trình diễn ra như sau:
  1. Người dùng chọn image.
    Quá trình bắt đầu bằng cách cho phép người dùng chọn hình ảnh của họ.
  2. Hình ảnh được lưu trữ cục bộ trong IndexedDB.
    Tiếp theo, hệ thống kiểm tra kết nối mạng. Nếu có kết nối mạng, hệ thống sẽ tải hình ảnh trực tiếp lên, tránh sử dụng bộ nhớ cục bộ không cần thiết. Tuy nhiên, nếu mạng không khả dụng, hình ảnh sẽ được lưu trữ trong IndexedDB.
  3. Service worker phát hiện khi mạng được khôi phục.
    Với hình ảnh được lưu trữ trong IndexedDB, hệ thống sẽ chờ phát hiện khi kết nối mạng được khôi phục để tiếp tục bước tiếp theo.
  4. Các quy trình đồng bộ hóa nền đang chờ tải lên.
    Ngay khi kết nối được khôi phục, hệ thống sẽ thử tải hình ảnh lên lại.
  5. Tệp đã được tải lên thành công.
    Ngay khi hình ảnh được tải lên, hệ thống sẽ xóa bản sao cục bộ được lưu trữ trong IndexedDB.

Triển khai hệ thống​

Bước đầu tiên trong quá trình triển khai hệ thống là cho phép người dùng chọn hình ảnh của họ. Có nhiều cách khác nhau để bạn có thể thực hiện điều này:
  • Bạn có thể sử dụng một phần tử đơn giản;
  • Giao diện kéo và thả.
Tôi khuyên bạn nên sử dụng cả hai. Một số người dùng thích sử dụng giao diện kéo và thả, trong khi những người khác cho rằng cách duy nhất để tải hình ảnh lên là thông qua phần tử . Có cả hai tùy chọn sẽ giúp cải thiện trải nghiệm của người dùng. Bạn cũng có thể cân nhắc cho phép người dùng dán hình ảnh trực tiếp vào trình duyệt bằng API Clipboard.

Đăng ký Service Worker​

Trọng tâm của giải pháp này là service worker. Nhân viên dịch vụ của chúng tôi sẽ chịu trách nhiệm lấy hình ảnh từ kho lưu trữ IndexedDB, tải hình ảnh lên khi kết nối internet được khôi phục và xóa kho lưu trữ IndexedDB khi hình ảnh đã được tải lên.

Để sử dụng nhân viên dịch vụ, trước tiên bạn phải đăng ký một nhân viên:
Mã:
if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/service-worker.js') .then(reg => console.log('Service Worker registered', reg)) .catch(err => console.error('Service Worker registration failed', err));
}

Kiểm tra kết nối mạng​

Hãy nhớ rằng, vấn đề chúng ta đang cố gắng giải quyết là do kết nối mạng không đáng tin cậy. Nếu vấn đề này không tồn tại, thì việc cố gắng giải quyết bất cứ điều gì cũng vô nghĩa. Do đó, sau khi chọn hình ảnh, chúng ta cần kiểm tra xem người dùng có kết nối internet đáng tin cậy hay không trước khi đăng ký sự kiện đồng bộ hóa và lưu trữ hình ảnh trong IndexedDB.
Mã:
function uploadImage() { if (navigator.onLine) { // Upload Image } else { // register Sync Event // Store Images in IndexedDB }
}
Lưu ý: Tôi chỉ sử dụng thuộc tính navigator.onLine tại đây để chứng minh cách hệ thống sẽ hoạt động. Thuộc tính navigator.onLine không đáng tin cậy và tôi khuyên bạn nên đưa ra giải pháp tùy chỉnh để kiểm tra xem người dùng có kết nối với internet hay không. Một cách bạn có thể thực hiện là gửi yêu cầu ping đến điểm cuối máy chủ mà bạn đã tạo.

Đăng ký sự kiện đồng bộ​

Sau khi thử nghiệm mạng không thành công, bước tiếp theo là đăng ký sự kiện đồng bộ. Sự kiện đồng bộ cần được đăng ký tại thời điểm hệ thống không tải được hình ảnh lên do kết nối internet kém.
Mã:
async function registerSyncEvent() { if ('SyncManager' in window) { const registration = await navigator.serviceWorker.ready; await registration.sync.register('uploadImages'); console.log('Background Sync registered'); }
}
Sau khi đăng ký sự kiện đồng bộ, bạn cần lắng nghe sự kiện đó trong service worker.
Mã:
self.addEventListener('sync', (event) => { if (event.tag === 'uploadImages') { event.waitUntil(sendImages()); }
});
Hàm sendImages sẽ là một quy trình không đồng bộ sẽ truy xuất hình ảnh từ IndexedDB và tải lên máy chủ. Đây là giao diện sẽ như sau:
Mã:
async function sendImages() { try { // await image retrieval and upload } catch (error) { // throw error }
}

Mở Cơ sở dữ liệu​

Điều đầu tiên chúng ta cần làm để lưu trữ hình ảnh cục bộ là mở một cửa hàng IndexedDB. Như bạn có thể thấy từ đoạn mã bên dưới, chúng ta đang tạo một biến toàn cục để lưu trữ phiên bản cơ sở dữ liệu. Lý do để làm như vậy là, sau đó, khi chúng ta muốn lấy lại hình ảnh của mình từ IndexedDB, chúng ta không cần phải viết mã để mở lại cơ sở dữ liệu.
Mã:
let database; // Global variable to store the database instance
function openDatabase() { return new Promise((resolve, reject) => { if (database) return resolve(database); // Return existing database instance const request = indexedDB.open("myDatabase", 1); request.onerror = (event) => { console.error("Database error:", event.target.error); reject(event.target.error); // Reject the promise on error }; request.onupgradeneeded = (event) => { const db = event.target.result; // Create the "images" object store if it doesn't exist. if (!db.objectStoreNames.contains("images")) { db.createObjectStore("images", { keyPath: "id" }); } console.log("Database setup complete."); }; request.onsuccess = (event) => { database = event.target.result; // Store the database instance globally resolve(database); // Resolve the promise with the database instance }; });
}

Lưu trữ hình ảnh trong IndexedDB​

Với cửa hàng IndexedDB mở, giờ đây chúng ta có thể lưu trữ hình ảnh của mình.
Bây giờ, bạn có thể tự hỏi tại sao một giải pháp dễ dàng hơn như localStorage lại không được sử dụng cho mục đích này.

Lý do cho điều đó là IndexedDB hoạt động không đồng bộ và không chặn luồng JavaScript chính, trong khi localStorage chạy đồng bộ và có thể chặn luồng JavaScript chính nếu luồng này đang được sử dụng.
Sau đây là cách bạn có thể lưu trữ hình ảnh trong IndexedDB:
Mã:
async function storeImages(file) { // Open the IndexedDB database. const db = await openDatabase(); // Create a transaction with read and write access. const transaction = db.transaction("images", "readwrite"); // Access the "images" object store. const store = transaction.objectStore("images"); // Define the image record to be stored. const imageRecord = { id: IMAGE_ID, // a unique ID image: file // Store the image file (Blob) }; // Add the image record to the store. const addRequest = store.add(imageRecord); // Handle successful addition. addRequest.onsuccess = () => console.log("Image added successfully!"); // Handle errors during insertion. addRequest.onerror = (e) => console.error("Error storing image:", e.target.error);
}
Với hình ảnh đã lưu trữ và đồng bộ hóa nền được thiết lập, hệ thống đã sẵn sàng tải hình ảnh lên bất cứ khi nào kết nối mạng được khôi phục.

Truy xuất và tải hình ảnh lên​

Sau khi kết nối mạng được khôi phục, sự kiện đồng bộ sẽ kích hoạt và trình xử lý dịch vụ sẽ lấy hình ảnh từ IndexedDB và tải lên.
Mã:
async function retrieveAndUploadImage(IMAGE_ID) { try { const db = await openDatabase(); // Ensure the database is open const transaction = db.transaction("images", "readonly"); const store = transaction.objectStore("images"); const request = store.get(IMAGE_ID); request.onsuccess = function (event) { const image = event.target.result; if (image) { // upload Image to server here } else { console.log("No image found with ID:", IMAGE_ID); } }; request.onerror = () => { console.error("Error retrieving image."); }; } catch (error) { console.error("Failed to open database:", error); }
}

Xóa IndexedDB Cơ sở dữ liệu​

Sau khi hình ảnh được tải lên, kho lưu trữ IndexedDB không còn cần thiết nữa. Do đó, bạn nên xóa nó cùng với nội dung của nó để giải phóng dung lượng lưu trữ.
Mã:
function deleteDatabase() { // Check if there's an open connection to the database. if (database) { database.close(); // Close the database connection console.log("Database connection closed."); } // Request to delete the database named "myDatabase". const deleteRequest = indexedDB.deleteDatabase("myDatabase"); // Handle successful deletion of the database. deleteRequest.onsuccess = function () { console.log("Database deleted successfully!"); }; // Handle errors that occur during the deletion process. deleteRequest.onerror = function (event) { console.error("Error deleting database:", event.target.error); }; // Handle cases where the deletion is blocked (e.g., if there are still open connections). deleteRequest.onblocked = function () { console.warn("Database deletion blocked. Close open connections and try again."); };
}
Như vậy là toàn bộ quá trình đã hoàn tất!

Những cân nhắc và hạn chế​

Mặc dù chúng tôi đã nỗ lực rất nhiều để giúp cải thiện trải nghiệm bằng cách hỗ trợ tải lên ngoại tuyến, nhưng hệ thống vẫn có những hạn chế. Tôi nghĩ mình sẽ nêu cụ thể những điều đó vì bạn nên biết giải pháp này có thể không đáp ứng được nhu cầu của bạn ở đâu.
  • Không có khả năng phát hiện kết nối Internet đáng tin cậy
    JavaScript không cung cấp cách hoàn hảo để phát hiện trạng thái trực tuyến. Vì lý do này, bạn cần đưa ra giải pháp tùy chỉnh để phát hiện trạng thái trực tuyến.
  • Giải pháp chỉ dành cho Chromium
    API đồng bộ hóa nền hiện chỉ giới hạn ở các trình duyệt dựa trên Chromium. Do đó, giải pháp này chỉ được hỗ trợ bởi các trình duyệt Chromium. Điều đó có nghĩa là bạn sẽ cần một giải pháp mạnh mẽ hơn nếu phần lớn người dùng của bạn sử dụng trình duyệt không phải Chromium.
  • IndexedDB Chính sách lưu trữ
    Trình duyệt áp đặt giới hạn lưu trữ và chính sách trục xuất đối với IndexedDB. Ví dụ, trong Safari, dữ liệu được lưu trữ trong IndexedDB có tuổi thọ là bảy ngày nếu người dùng không tương tác với trang web. Đây là điều bạn nên ghi nhớ nếu bạn đưa ra một giải pháp thay thế cho API đồng bộ hóa nền hỗ trợ Safari.

Nâng cao trải nghiệm người dùng​

Vì toàn bộ quá trình diễn ra ở chế độ nền, chúng ta cần một cách để thông báo cho người dùng khi hình ảnh được lưu trữ, chờ tải lên hoặc đã tải lên thành công. Việc triển khai một số thành phần UI nhất định cho mục đích này thực sự sẽ nâng cao trải nghiệm cho người dùng. Các thành phần UI này có thể bao gồm thông báo toast, chỉ báo trạng thái tải lên như spinner (để hiển thị các quy trình đang hoạt động), thanh tiến trình (để hiển thị tiến trình trạng thái), chỉ báo trạng thái mạng hoặc các nút để cung cấp tùy chọn thử lại và hủy.

Kết thúc​

Kết nối internet kém có thể làm gián đoạn trải nghiệm của người dùng đối với ứng dụng web. Tuy nhiên, bằng cách tận dụng các công nghệ PWA như IndexedDB, service worker và Background Sync API, các nhà phát triển có thể giúp cải thiện độ tin cậy của các ứng dụng web cho người dùng của họ, đặc biệt là những người ở những khu vực có kết nối internet không ổn định.
 
Back
Bên trên