如何在后台定期同步数据

Cecilia Cong
Cecilia Cong

现代方式

借助定期后台同步,您可以在启动渐进式 Web 应用或由服务工作器支持的页面时显示新鲜内容。它会在应用或网页未使用时在后台下载数据。

使用 Periodic Background Sync API

安装服务工件后,请使用 Permissions API 查询 periodic-background-sync。您可以通过窗口或 Service Worker 上下文执行此操作。

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.
}

注册定期同步需要同时提供标记和最小同步间隔 (minInterval)。标记用于标识已注册的同步,以便注册多个同步。在以下示例中,标记名称为 'content-sync'minInterval 为一天。

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

调用 periodicSync.getTags() 可检索注册标记数组。以下示例使用标记名称来确认缓存更新处于活动状态,以免再次更新。

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

如需响应定期后台同步事件,请向服务 worker 添加 periodicsync 事件处理脚本。传递给它的事件对象将包含与注册期间使用的值匹配的代码参数。例如,如果定期后台同步已注册为名称为 'content-sync' 的任务,则 event.tag 将为 'content-sync'

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

浏览器兼容性

浏览器支持

  • Chrome:80。
  • Edge:80.
  • Firefox:不受支持。
  • Safari:不受支持。

来源

传统方式

传统方法只是在加载时更新数据,而不是在后台更新数据以便在用户加载应用时准备就绪。

深入阅读

演示

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