Cách đồng bộ hoá dữ liệu định kỳ ở chế độ nền

Cecilia Cong
Cecilia Cong

Cách làm hiện đại

Tính năng đồng bộ hoá định kỳ trong nền cho phép bạn hiển thị nội dung mới khi một ứng dụng web tiến bộ hoặc trang do service worker hỗ trợ được khởi chạy. Ứng dụng thực hiện việc này bằng cách tải dữ liệu xuống ở chế độ nền khi ứng dụng hoặc trang không được sử dụng.

Sử dụng Periodic Background Sync API

Sau khi cài đặt trình chạy dịch vụ, hãy dùng Permissions API để truy vấn periodic-background-sync. Bạn có thể thực hiện việc này từ một cửa sổ hoặc một ngữ cảnh của trình chạy dịch vụ.

const status = await navigator.permissions.query({
  name: 'periodic-background-sync',
});
if (status.state === 'granted') {
  // Periodic background sync can be used.
} else {
  // Periodic background sync cannot be used.
}

Để đăng ký một hoạt động đồng bộ hoá định kỳ, bạn cần có cả thẻ và khoảng thời gian đồng bộ hoá tối thiểu (minInterval). Thẻ này xác định hoạt động đồng bộ hoá đã đăng ký để bạn có thể đăng ký nhiều hoạt động đồng bộ hoá. Trong ví dụ bên dưới, tên thẻ là 'content-sync'minInterval là một ngày.

navigator.serviceWorker.ready.then(async registration => {
  try {
    await registration.periodicSync.register('get-cats', { minInterval: 24 * 60 * 60 * 1000 });
    console.log('Periodic background sync registered.');
  } catch (err) {
    console.error(err.name, err.message);
  }
});

Gọi periodicSync.getTags() để truy xuất một mảng thẻ đăng ký. Ví dụ bên dưới sử dụng tên thẻ để xác nhận rằng quá trình cập nhật bộ nhớ đệm đang hoạt động để tránh cập nhật lại.

const registration = await navigator.serviceWorker.ready;
if ('periodicSync' in registration) {
  const tags = await registration.periodicSync.getTags();
  // Only update content if sync isn't set up.
  if (!tags.includes('content-sync')) {
    updateContentOnPageLoad();
  }
} else {
  // If periodic background sync isn't supported, always update.
  updateContentOnPageLoad();
}

Để phản hồi một sự kiện đồng bộ hoá định kỳ ở chế độ nền, hãy thêm trình xử lý sự kiện periodicsync vào trình chạy dịch vụ của bạn. Đối tượng sự kiện được truyền đến đối tượng này sẽ chứa một thông số thẻ khớp với giá trị được dùng trong quá trình đăng ký. Ví dụ: nếu một hoạt động đồng bộ hoá định kỳ ở chế độ nền được đăng ký bằng tên 'content-sync', thì event.tag sẽ là 'content-sync'.

self.addEventListener('periodicsync', (event) => {
  if (event.tag === 'content-sync') {
    event.waitUntil(syncContent());
  }
});

Khả năng tương thích với trình duyệt

Browser Support

  • Chrome: 80.
  • Edge: 80.
  • Firefox: not supported.
  • Safari: not supported.

Source

Cách cổ điển

Thay vì cập nhật dữ liệu ở chế độ nền để sẵn sàng khi người dùng tải ứng dụng, cách thức truyền thống chỉ đơn giản là cập nhật dữ liệu khi tải.

Tài liệu đọc thêm

Bản minh hoạ

HTML

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link
      rel="icon"
      href=""
    />
    <link rel="manifest" href="./manifest.json" />
    <title>How to periodically synchronize data in the background</title>
    <link rel="stylesheet" href="/style.css" />
    <!-- TODO: Devsite - Removed inline handlers -->
    <!-- <script src="/script.js" defer></script> -->
  </head>
  <body>
    <h1>How to periodically synchronize data in the background</h1>
    <p class="available">Periodic background sync can be used. Install the app first.</p>
    <p class="not-available">Periodic background sync cannot be used.</p>
    <h2>Last updated</h2>
    <p class="last-updated">Never</p>
    <h2>Registered tags</h2>
    <ul>
      <li>None yet.</li>
    </ul>
  </body>
</html>

CSS


        html {
  box-sizing: border-box;
  font-family: system-ui, sans-serif;
  color-scheme: dark light;
}

*,
*:before,
*:after {
  box-sizing: inherit;
}

body {
  margin: 1rem;
}
        

JS


        const available = document.querySelector('.available');
const notAvailable = document.querySelector('.not-available');
const ul = document.querySelector('ul');
const lastUpdated = document.querySelector('.last-updated');

const updateContent = async () => {
  const data = await fetch(
    'https://worldtimeapi.org/api/timezone/Europe/London.json'
  ).then((response) => response.json());
  return new Date(data.unixtime * 1000);
};

const registerPeriodicBackgroundSync = async (registration) => {
  const status = await navigator.permissions.query({
    name: 'periodic-background-sync',
  });
  if (status.state === 'granted' && 'periodicSync' in registration) {
    try {
      // Register the periodic background sync.
      await registration.periodicSync.register('content-sync', {
        // An interval of one day.
        minInterval: 24 * 60 * 60 * 1000,
      });
      available.hidden = false;
      notAvailable.hidden = true;

      // List registered periodic background sync tags.
      const tags = await registration.periodicSync.getTags();
      if (tags.length) {
        ul.innerHTML = '';
      }
      tags.forEach((tag) => {
        const li = document.createElement('li');
        li.textContent = tag;
        ul.append(li);
      });

      // Update the user interface with the last periodic background sync data.
      const backgroundSyncCache = await caches.open('periodic-background-sync');
      if (backgroundSyncCache) {
        const backgroundSyncResponse =
          backgroundSyncCache.match('/last-updated');
        if (backgroundSyncResponse) {
          lastUpdated.textContent = `${await fetch('/last-updated').then(
            (response) => response.text()
          )} (periodic background-sync)`;
        }
      }

      // Listen for incoming periodic background sync messages.
      navigator.serviceWorker.addEventListener('message', async (event) => {
        if (event.data.tag === 'content-sync') {
          lastUpdated.textContent = `${await updateContent()} (periodic background sync)`;
        }
      });
    } catch (err) {
      console.error(err.name, err.message);
      available.hidden = true;
      notAvailable.hidden = false;
      lastUpdated.textContent = 'Never';
    }
  } else {
    available.hidden = true;
    notAvailable.hidden = false;
    lastUpdated.textContent = `${await updateContent()} (manual)`;
  }
};

if ('serviceWorker' in navigator) {
  window.addEventListener('load', async () => {
    const registration = await navigator.serviceWorker.register('./sw.js');
    console.log('Service worker registered for scope', registration.scope);

    await registerPeriodicBackgroundSync(registration);
  });
}