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

Cecilia Cong
Cecilia Cong

Phương pháp hiện đại

Tính năng đồng bộ hoá trong nền định kỳ 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 được hỗ trợ bởi worker dịch vụ được khởi chạy. Ứng dụng này 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 API Đồng bộ hoá định kỳ ở chế độ nền

Sau khi cài đặt worker dịch vụ, hãy sử dụng Permissions API (API Quyền) để 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 ngữ cảnh worker 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ý đồ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 quá trình đồng bộ hoá đã đăng ký để có thể đăng ký nhiều quá trình đồ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 worker dịch vụ. Đối tượng sự kiện được truyền vào sẽ chứa một thông số thẻ khớp với giá trị được sử dụng trong quá trình đăng ký. Ví dụ: nếu một quá trình đồng bộ hoá nền định kỳ được đăng ký với 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

Hỗ trợ trình duyệt

  • Chrome: 80.
  • Edge: 80.
  • Firefox: không được hỗ trợ.
  • Safari: không được hỗ trợ.

Nguồn

Cách truyền thống

Thay vì cập nhật dữ liệu ở chế độ nền để dữ liệu sẵn sàng khi người dùng tải ứng dụng, cách thức cổ điển chỉ bao gồm việc cập nhật dữ liệu khi tải.

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

Bản minh hoạ

HTMLCSSJS
<!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>

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

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

body {
  margin: 1rem;
}
        

        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);
  });
}