백그라운드에서 데이터를 주기적으로 동기화하는 방법

Cecilia Cong
Cecilia Cong

현대적인 방식

주기적 백그라운드 동기화를 사용하면 프로그레시브 웹 앱 또는 서비스 워커 지원 페이지가 시작될 때 새로운 콘텐츠를 표시할 수 있습니다. 이는 앱이나 페이지가 사용되지 않을 때 백그라운드에서 데이터를 다운로드하는 방식으로 실행됩니다.

Periodic Background Sync API 사용

서비스 워커가 설치된 후 Permissions API를 사용하여 periodic-background-sync를 쿼리합니다. 이 작업은 창 또는 서비스 워커 컨텍스트에서 수행할 수 있습니다.

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은 1일입니다.

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

주기적인 백그라운드 동기화 이벤트에 응답하려면 서비스 워커에 periodicsync 이벤트 핸들러를 추가합니다. 전달되는 이벤트 객체에는 등록 중에 사용된 값과 일치하는 태그 매개변수가 포함됩니다. 예를 들어 주기적 백그라운드 동기화가 'content-sync'라는 이름으로 등록된 경우 event.tag'content-sync'입니다.

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

브라우저 호환성

브라우저 지원

  • 80
  • 80
  • x
  • x

소스

기존의 방식

사용자가 앱을 로드할 때 사용할 수 있도록 백그라운드에서 데이터를 업데이트하는 대신, 일반적인 방법은 로드 시 데이터를 업데이트하는 것입니다.

추가 자료

데모

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