Background Sync - team-yaza/mozi-client GitHub Wiki

๊ฐœ์š”

์ด ๊ธ€์€ MOZI์—์„œ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ๋™๊ธฐํ™”๋ฅผ ๊ตฌํ˜„ํ•  ๋•Œ์˜ ๊ณ ๋ฏผ์ด ๋‹ด๊ธด ๊ธ€์ด๋‹ค.
๋ฐฑ๊ทธ๋ผ์šด๋“œ ๋™๊ธฐํ™”์˜ ๊ฐœ๋…์€ ์‚ฌ์šฉ์ž์˜ ์ธํ„ฐ๋„ท ์—ฐ๊ฒฐ์ด ์•ˆ์ •์ ์ด์ง€ ์•Š์€ ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

image

๋ฐฑ๊ทธ๋ผ์šด๋“œ ๋™๊ธฐํ™”๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์•ˆ์ •์ ์ธ ์ธํ„ฐ๋„ท ์—ฐ๊ฒฐ ์ „๊นŒ์ง€ ํ–‰๋™์„ ๋ฏธ๋ฃจ๋Š” ์›น API์ด๋‹ค.
์„œ๋น„์Šค ์›Œ์ปค๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์˜คํ”„๋ผ์ธ ํ™˜๊ฒฝ์„ ์œ„ํ•ด asset์„ ์บ์‹œํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ IndexedDB๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ ์ €์žฅ์†Œ๋กœ ์ž‘์—…ํ•  ์ˆ˜ ์žˆ๋Š” ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ œ๊ณตํ•œ๋‹ค.

๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•˜๋ฉด, ์‰ก๊ฒŒ ํœ˜๋ฐœ๋˜๋Š” ๊ฐœ๋ณ„ ํŽ˜์ด์ง€์˜ ํŠน์„ฑ์—์„œ ๋ฒ—์–ด๋‚  ์ˆ˜ ์žˆ๋‹ค. ์›นํŽ˜์ด์ง€๋Š” ์–ด์ œ๋“ ์ง€ ๋‹ซํž ์ˆ˜ ์žˆ๊ณ , ์‚ฌ์šฉ์ž ๋„คํŠธ์›Œํฌ ์—ฐ๊ฒฐ์€ ๋Š์–ด์งˆ ์ˆ˜ ์žˆ์œผ๋ฉฐ ์„œ๋ฒ„๊ฐ€ ์ฃฝ์„ ์ˆ˜ ์žˆ๋‹ค.
์‚ฌ์šฉ์ž ๊ธฐ๊ธฐ์— ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์„ค์น˜๋˜์–ด์žˆ๋Š” ํ•œ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ๋™๊ธฐํ™” ์ž‘์—…์€ ์„ฑ๊ณต์ ์œผ๋กœ ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ์‚ฌ๋ผ์ง€์ง€ ์•Š๋Š”๋‹ค.

๋”ฐ๋ผ์„œ ํŽ˜์ด์ง€๊ฐ€ ๋‹ซํ˜€๋„ ๊ณ„์† ์ง„ํ–‰๋˜์–ด์•ผ ํ•˜๋Š” ๋ชจ๋“  ์ž‘์—…์— ๋ฐฑ๊ทธ๋ผ์šด๋“œ ๋™๊ธฐํ™” ์‚ฌ์šฉ์„ ๊ณ ๋ คํ•ด ๋ณผ ์ˆ˜ ์žˆ๋‹ค.
์‚ฌ์šฉ์ž๊ฐ€ ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋‚ด๊ฑฐ๋‚˜ ํ•ด์•ผํ•  ์ผ ๋ชฉ๋ก์ค‘ ํ•˜๋‚˜๋ฅผ ์™„๋ฃŒ ํ‘œ์‹œํ•˜๊ฑฐ๋‚˜ ์บ˜๋ฆฐ๋”์— ์ด๋ฒคํŠธ๋ฅผ ์ถ”๊ฐ€ํ•  ๋•Œ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ๋™๊ธฐํ™”๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ด ์ž‘์—…๋“ค์ด ์„ฑ๊ณต์ ์œผ๋กœ ์™„๋ฃŒ๋  ๊ฒƒ์„ ๋ณด์žฅํ•  ์ˆ˜ ์žˆ๋‹ค.

image

ํŽ˜์ด์ง€์—์„œ Ajax ํ˜ธ์ถœ๊ณผ ๊ฐ™์€ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋Œ€์‹  ๋™๊ธฐํ™” ์ด๋ฒคํŠธ๋ฅผ ๋“ฑ๋กํ•œ๋‹ค.

navigator.serviceWorker.ready.then(function(registration) {
    registration.sync.register('send-messages');
})

์œ„ ์ฝ”๋“œ๋Š” ์›นํŽ˜์ด์ง€์—์„œ ์‹คํ–‰๋  ์ˆ˜ ์žˆ๋‹ค. ํ™œ์„ฑํ™”๋œ ์„œ๋น„์Šค ์›Œ์ปค์˜ ๋“ฑ๋ก ๊ฐ์ฒด๋ฅผ ๋ฐ›์•„์™€ 'send-messages'๋ผ๋Š” ๋™๊ธฐํ™” ์ด๋ฒคํŠธ๋ฅผ ๋“ฑ๋กํ•œ๋‹ค.
๊ทธ ํ›„ ์„œ๋น„์Šค ์›Œ์ปค์— ๋™๊ธฐํ™” ์ด๋ฒคํŠธ๋ฅผ ์ˆ˜์‹ ํ•  sync ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค. ์ด ์ด๋ฒคํŠธ๋Š” ํŽ˜์ด์ง€๊ฐ€ ์•„๋‹Œ ์„œ๋น„์Šค ์›Œ์ปค์—์„œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ๋•Œ ํ•„์š”ํ•œ ๋กœ์ง์„ ํฌํ•จํ•œ๋‹ค.

self.addEventListener("sync", function (event) {
  if (event.tag === "send-messages") {
    event.waitUntil(function () {
      var sent = sendMessages()

      if (sent) {
        return Promise.resolve()
      } else {
        return Promise.reject()
      }
    })
  }
})

sync ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ์—์„œ waitUntil์„ ์‚ฌ์šฉํ•ด ์ด๋ฒคํŠธ ์ข…๋ฃŒ๋ฅผ ์š”์ฒญํ•˜๊ธฐ ์ „๊นŒ์ง€ ์ด๋ฒคํŠธ๊ฐ€ ์œ ์ง€๋  ์ˆ˜ ์žˆ๋„๋ก ์ฒ˜๋ฆฌํ•œ๋‹ค.
์ด๋ ‡๊ฒŒ ํ•จ์œผ๋กœ์จ ํ•„์š”ํ•œ ์ž‘์—…์„ ์‹œ๋„ํ•˜๊ณ , ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ์‹œ๊ฐ„์„ ๋ฒŒ ์ˆ˜ ์žˆ๊ณ  ์ฒ˜๋ฆฌ ๊ฒฐ๊ณผ์— ๋”ฐ๋ผ ์ด๋ฒคํŠธ๋ฅผ ์„ฑ๊ณต์ ์œผ๋กœ ๋ฆฌ์กธ๋ธŒํ•˜๊ฑฐ๋‚˜ ๋ฆฌ์ ํ•  ์ˆ˜ ์žˆ๋‹ค.

sync ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ์—์„œ ๋ฆฌ์ ๋œ ํ”„๋กœ๋ฏธ์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉด ๋ธŒ๋ผ์šฐ์ €๋Š” ํ•ด๋‹น ๋™๊ธฐํ™” ์ž‘์—…์„ ํ์— ์Œ“์•„ ๋‹ค์Œ๋ฒˆ์— ๋‹ค์‹œ ์‹œ๋„๋˜๋„๋ก ํ•  ๊ฒƒ์ด๋‹ค. ๋‹ค์‹œ ๋งํ•ด์„œ send-messages๋ผ๋Š” sync ์ด๋ฒคํŠธ๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์•ฑ์„ ์ข…๋ฃŒํ•œ ํ›„์—๋„ ๋‹ค์‹œ ์‹œ๋„๋  ๊ฒƒ์ด๋‹ค.

์‚ฌ์šฉ์ž๋Š” ์•ฑ์„ ์ข…๋ฃŒํ•˜๋”๋ผ๋„ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ๋ฉ”์‹œ์ง€๊ฐ€ ์ „์†ก๋  ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ•œ๋‹ค.
์˜คํ”„๋ผ์ธ ์—์„œ ๋ณด๋‚ธ ๋ฉ”์‹œ์ง€๋Š” ๋‹ค๋ฅธ ๋ฉ”์‹œ์ง€๋“ค ์ฒ˜๋Ÿผ ์ŠคํŠธ๋ฆผ์— ์œ„์น˜ํ•˜๋ฉด์„œ ์‹œ๊ณ„์•„์ด์ฝ˜์ด ํ‘œ์‹œ๋˜์–ด ์ด ๋ฉ”์‹œ์ง€๊ฐ€ ์ „์†ก๋  ๊ฒƒ์ด๋ผ๋Š” ๊ฒƒ์„ ์•Œ๋ ค์ค€๋‹ค. ๋ฉ”์‹œ์ง€๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ „์†ก๋˜๋Š” ์ฆ‰์‹œ ์‹œ๊ณ„ ์•„์ด์ฝ˜์€ ์ฒดํฌ ์•„์ด์ฝ˜์œผ๋กœ ๋ฐ”๋€๋‹ค.

SyncManager

๋™๊ธฐํ™” ์ด๋ฒคํŠธ์™€ ๊ด€๋ จ๋œ ๋ชจ๋“  ์ƒํ˜ธ ์ž‘์šฉ์€ SyncManager๋ฅผ ํ†ตํ•ด ์ด๋ฃจ์–ด์ง„๋‹ค.
SyncManager๋Š” ๋™๊ธฐํ™” ์ด๋ฒคํŠธ๋ฅผ ๋“ฑ๋กํ•˜๊ณ  ํ˜„์žฌ ๋“ฑ๋ก๋œ ๋™๊ธฐํ™” ์ž‘์—…์„ ๊ฐ€์ ธ์˜ค๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๋Š” ์„œ๋น„์Šค ์›Œ์ปค ์ธํ„ฐํŽ˜์ด์Šค์ด๋‹ค.

ํ™œ์„ฑํ™”๋œ ์„œ๋น„์Šค ์›Œ์ปค ๋“ฑ๋ก ๊ฐ์ฒด๋ฅผ ํ†ตํ•ด SyncManager์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ค. ๋“ฑ๋ก ๊ฐ์ฒด๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐฉ๋ฒ•์€ ์„œ๋น„์Šค ์›Œ์ปค์—์„œ ๊ฐ€์ ธ์˜ค๋Š”์ง€, ์•„๋‹ˆ๋ฉด ํŽ˜์ด์ง€์—์„œ ์ง์ ‘ ๊ฐ€์ ธ์˜ค๋Š”์ง€์— ๋”ฐ๋ผ ์กฐ๊ธˆ ๋‹ฌ๋ผ์ง„๋‹ค.

์„œ๋น„์Šค ์›Œ์ปค ๋‚ด์—์„œ๋Š” ๊ธ€๋กœ๋ฒŒ ๊ฐ์ฒด๋ฅผ ํ†ตํ•ด ์‰ฝ๊ฒŒ ์„œ๋น„์Šค ์›Œ์ปค ๋“ฑ๋ก ๊ฐ์ฒด์— ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜๋‹ค.

self.registration

์„œ๋น„์Šค ์›Œ์ปค๊ฐ€ ๊ด€๋ฆฌํ•˜๋Š” ํŽ˜์ด์ง€์—์„œ๋Š” navigator.serviceWorker.ready๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ํ˜„์žฌ ํ™œ์„ฑํ™”๋œ ์„œ๋น„์Šค ์›Œ์ปค ๋“ฑ๋ก ๊ฐ์ฒด์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ค. ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ์„ฑ๊ณต์ ์œผ๋กœ ๋ฆฌ์กธ๋ธŒ ๋  ๋•Œ ์„œ๋น„์Šค ์›Œ์ปค ๋“ฑ๋ก ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ”„๋กœ๋ฏธ์Šค๊ฐ€ ๋ฐ˜ํ™˜๋œ๋‹ค.

navigator.serviceWorker.ready.then(function(registration){});

const registration: ServiceWorkerRegistration = await navigator.serviceWorker.ready;

์ผ๋‹จ ์„œ๋น„์Šค ์›Œ์ปค ๋“ฑ๋ก ๊ฐ์ฒด๋ฅผ ๊ฐ€์ ธ์™”๋‹ค๋ฉด SyncManager๋ฅผ ํ†ตํ•œ ๋‚˜๋จธ์ง€ ์ƒํ˜ธ์ž‘์šฉ์€ ์„œ๋น„์Šค ์›Œ์ปค์—์„œ ํ•˜๋˜ ํŽ˜์ด์ง€์—์„œ ํ•˜๋˜ ์ƒ๊ด€์—†์ด ๋™์ผํ•˜๋‹ค.

์„œ๋น„์Šค ์›Œ์ปค์—์„œ 'send-messages' ์ด๋ฒคํŠธ๋ฅผ ๋“ฑ๋กํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ฝ”๋“œ๋ฅผ ์ž…๋ ฅํ•œ๋‹ค.

self.registration.sync.register('send-messages')

์„œ๋น„์Šค ์›Œ์ปค๊ฐ€ ์ œ์–ดํ•˜๋Š” ํŽ˜์ด์ง€์— ๊ฐ™์€ ์ด๋ฒคํŠธ๋ฅผ ๋“ฑ๋กํ•˜๋ ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

navigator.serviceWorker.ready.then(function(registration){
    registration.sync.register('send-messages')
})

SyncManager๋Š” ๊ฐ„๋‹จํ•œ ๋™๊ธฐํ™” ์ด๋ฒคํŠธ ํƒœ๊ทธ ๋ชฉ๋ก์„ ์œ ์ง€ํ•œ๋‹ค.
์ด ๋ชฉ๋ก์—๋Š” ๊ฐ๊ฐ์˜ ์ด๋ฒคํŠธ๊ฐ€ ์–ด๋–ค ์ด๋ฒคํŠธ์ธ์ง€, ๋ฌด์—‡์„ ํ•˜๋Š”์ง€์— ๋Œ€ํ•œ ๋กœ์ง์€ ํฌํ•จ๋˜์–ด ์žˆ์ง€์•Š๋‹ค. ๊ตฌํ˜„์€ ์ „์ ์œผ๋กœ ์„œ๋น„์Šค ์›Œ์ปค์˜ sync ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ์ฝ”๋“œ์— ๋‹ฌ๋ ค์žˆ๋‹ค.
SyncManager๊ฐ€ ์•Œ๊ณ  ์žˆ๋Š” ๊ฒƒ์€ ์–ด๋–ค ์ด๋ฒคํŠธ๊ฐ€ ๋“ฑ๋ก๋˜์—ˆ๋Š”์ง€, ์–ธ์ œ ํ˜ธ์ถœ๋˜์—ˆ๋Š”์ง€, ์–ด๋–ป๊ฒŒ ๋™๊ธฐํ™” ์ด๋ฒคํŠธ๋ฅผ ์ „๋‹ฌํ• ์ง€์— ๋Œ€ํ•œ ๊ฒƒ์ด๋‹ค.

SyncManager๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฒฝ์šฐ์— sync์ด๋ฒคํŠธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚จ๋‹ค.

  1. ๋™๊ธฐํ™” ์ด๋ฒคํŠธ ๋“ฑ๋ก ์งํ›„
  2. ์‚ฌ์šฉ์ž ์ƒํƒœ๊ฐ€ ์˜คํ”„๋ผ์ธ์—์„œ ์˜จ๋ผ์ธ์œผ๋กœ ๋ณ€๊ฒฝ๋  ๋•Œ
  3. ์„ฑ๊ณต์ ์œผ๋กœ ์™„๋ฃŒ๋˜์ง€ ์•Š์€ ๋™๊ธฐํ™” ์ด๋ฒคํŠธ๊ฐ€ ์žˆ์„ ๊ฒฝ์šฐ, ๋งค ๋ถ„๋งˆ๋‹ค

์„œ๋น„์Šค ์›Œ์ปค๋Š” ๋ฐœ์†ก๋œ ๋™๊ธฐํ™” ์ด๋ฒคํŠธ๋ฅผ ํ”„๋กœ๋ฏธ์Šค ํ˜•์‹์œผ๋กœ ์ˆ˜์‹ ํ•˜๊ณ  ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค. ํ”„๋กœ๋ฏธ์Šค๊ฐ€ ๋ฆฌ์กธ๋ธŒ ๋˜๋ฉด SyncManager์—์„œ ํ•ด๋‹น ๋™๊ธฐํ™” ์ด๋ฒคํŠธ๊ฐ€ ์‚ญ์ œ๋œ๋‹ค. ํ”„๋กœ๋ฏธ์Šค๊ฐ€ ๋ฆฌ์ ๋˜๋ฉด ๋‹ค์Œ ๋ฒˆ ๋™๊ธฐํ™” ์‹œ์ ์— ๋‹ค์‹œ ์‹œ๋„๋  ์ˆ˜ ์žˆ๋„๋ก SyncManager์— ๋‚จ๊ฒŒ ๋œ๋‹ค.

์ด๋ฒคํŠธ ํƒœ๊ทธ๋Š” ์œ ์ผํ•ด์•ผํ•œ๋‹ค. SyncManager์— ์ด๋ฏธ ์กด์žฌํ•˜๋Š” ํƒœ๊ทธ๋ช…์œผ๋กœ sync ์ด๋ฒคํŠธ๋ฅผ ๋“ฑ๋กํ•˜๋ฉด SyncManager๋Š” ์ด๋ฅผ ๋ฌด์‹œํ•˜๊ณ  ์ค‘๋ณต์œผ๋กœ ์ถ”๊ฐ€ํ•˜์ง€ ์•Š๋Š”๋‹ค. ์ฒ˜์Œ์—๋Š” ์ด๊ฒƒ์ด ์ œ์•ฝ์ฒ˜๋Ÿผ ๋Š๊ปด์งˆ ์ˆ˜ ์žˆ์ง€๋งŒ ์‚ฌ์‹ค SyncManager์˜ ๊ฐ€์žฅ ์œ ์šฉํ•œ ํŠน์ง• ์ค‘ ํ•˜๋‚˜์ด๋‹ค. ์ด ํŠน์ง•์€ ๋งŽ์€ ์ˆ˜์˜ ๋น„์Šทํ•œ ์ž‘์—…์„ ํ•˜๋‚˜์˜ ์ด๋ฒคํŠธ๋กœ ๊ทธ๋ฃนํ™”ํ•˜์—ฌ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค.

SyncManager์˜ getTags() ๋ฉ”์„œ๋“œ๋ฅผ ํ™œ์šฉํ•˜๋ฉด ์‹คํ–‰ ์˜ˆ์ •์ธ ์ „์ฒด ๋™๊ธฐํ™” ์ด๋ฒคํŠธ ๋ชฉ๋ก์„ ๋ฐ›์•„์˜ฌ ์ˆ˜ ์žˆ๋‹ค.
ํ˜„์žฌ ๋“ฑ๋ก๋œ ๋ชจ๋“  ์ด๋ฒคํŠธ ๋ชฉ๋ก์„ ์ถœ๋ ฅํ•˜๋ ค๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ดํ•œ๋‹ค.

์„œ๋น„์Šค ์›Œ์ปค ์ธํ„ฐํŽ˜์ด์Šค์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ getTags()๋„ ํ”„๋กœ๋ฏธ์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ํ”„๋กœ๋ฏธ์Šค๊ฐ€ ๋ฆฌ์กธ๋ธŒ๋˜๋ฉด ๋™๊ธฐํ™” ์ด๋ฒคํŠธ ํƒœ๊ทธ ์ด๋ฆ„์ด ์ฑ„์›Œ์ง„ ๋ฐฐ์—ด์„ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค.

์•„๋ž˜ ์ฝ”๋“œ๋Š” ์ด๋ฒคํŠธ๊ฐ€ ๋“ฑ๋ก๋˜๋ฉด ํ˜„์žฌ ๋“ฑ๋ก๋œ ๋ชจ๋“  ์ด๋ฒคํŠธ ๋ชฉ๋ก์ด ์ฝ˜์†”์— ์ถœ๋ ฅ๋œ๋‹ค.

self.registration
  .sync()
  .register("hello-sync")
  .then(function () {
    return self.registration.sync.getTags()
  })
  .then(function(tags) {console.log(tags)})

์„œ๋น„์Šค ์›Œ์ปค๊ฐ€ ์ œ์–ดํ•˜๋Š” ํŽ˜์ด์ง€์—์„œ๋Š” ready๋ฅผ ์‚ฌ์šฉํ•ด ๋“ฑ๋ก ๊ฐ์ฒด๋ฅผ ๋จผ์ € ๋ฐ›์•„์˜ค๋Š” ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•ด ๋น„์Šทํ•œ ๊ฒฐ๊ณผ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค.

navigator.serviceWorker.ready.then((registration) => {
  registration.sync
    .register('hello-sync')
    .then(() => {
      return registration.sync.getTags();
    })
    .then((tags) => {
      console.log(tags);
    });
});

์„œ๋น„์Šค ์›Œ์ปค๊ฐ€ ์ œ์–ดํ•˜๋Š” ํŽ˜์ด์ง€์—์„œ ์ด ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ["hello-sync"]๊ฐ€ ์ฝ˜์†”์— ์ถœ๋ ฅ๋˜์–ด์•ผ ํ•œ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด ์ด๋ฉ”์ผ ์„œ๋น„์Šค๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ณด์ž.
์‚ฌ์šฉ์ž๊ฐ€ ์ด๋ฉ”์ผ์„ ๋ณด๋‚ผ๋•Œ๋งˆ๋‹ค IndexdDB์˜ ๋ณด๋‚ธ ํŽธ์ง€ํ•จ์— ์ด๋ฉ”์ผ์„ ์ €์žฅํ•˜๊ณ , send-unsent-messages ๋ฐฑ๊ทธ๋ผ์šด๋“œ ๋™๊ธฐํ™” ์ด๋ฒคํŠธ๋ฅผ ๋“ฑ๋กํ•˜๋„๋ก ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.
์ด์— ๋Œ€์‘๋˜๋Š” ์„œ๋น„์Šค์›Œ์ปค์ชฝ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ๋Š” IndexedDB์˜ ๋ณด๋‚ธ ํŽธ์ง€ํ•จ์˜ ๋ชจ๋“  ์ด๋ฉ”์ผ์„ ์ˆœํšŒํ•˜๋ฉฐ ์ด๋ฉ”์ผ ์ „์†ก์„ ์‹œ๋„ํ•˜๊ณ , ์„ฑ๊ณต์ ์œผ๋กœ ๋ฐœ์†กํ•˜์ง€ ๋ชปํ•œ ์ด๋ฉ”์ผ์ด ํ•˜๋‚˜๋ผ๋„ ์žˆ์œผ๋ฉด, ์ „์ฒด sync ์ด๋ฒคํŠธ๊ฐ€ ๋ฆฌ์ ๋  ๊ฒƒ์ด๋‹ค. ์ดํ›„ SyncManager๋Š” ์‚ฌ์šฉ์ž์˜ ๋„คํŠธ์›Œํฌ ํ™˜๊ฒฝ์ด ๋ฐ”๋€Œ๊ฑฐ๋‚˜ ์ผ์ • ์‹œ๊ฐ„์ด ์ง€๋‚œ๊ฒฝ์šฐ, ๋‹ค์‹œ ์ด๋ฒคํŠธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๊ณ , ๋‹ค์‹œ ํ•œ๋ฒˆ IndexedDB์˜ ๋ณด๋‚ธ ํŽธ์ง€ํ•จ์„ ๋Œ๋ฉฐ, ์•ž์„œ ์ „์†ก๋˜์ง€ ์•Š์•˜๋˜ ์ด๋ฉ”์ผ๊ณผ ๊ทธ ์ดํ›„์— ์ƒˆ๋กœ ์ž‘์„ฑํ•œ ์ด๋ฉ”์ผ์„ ๋‹ค์‹œ ๋ฐœ์†กํ•˜๊ณ  ๋ณด๋‚ธ ํŽธ์ง€ํ•จ์„ ๋น„์šด๋‹ค.

์ด๋ ‡๊ฒŒ ๊ตฌ์„ฑํ•˜๋ฉด ๋ฉ”์ผ์ด ๋ณด๋‚ธ ํŽธ์ง€ํ•จ์— ์žˆ๋Š”์ง€ ์—†๋Š”์ง€ ์ฒดํฌํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค. ๋ณด๋‚ธ ํŽธ์ง€ํ•จ์— ์ „์†ก๋˜์ง€ ์•Š์€ ์ด๋ฉ”์ผ์ด ์žˆ๋Š” ํ•œ, ๋™๊ธฐํ™” ์ด๋ฒคํŠธ๋Š” ๋“ฑ๋ก๋œ ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•˜๋ฉฐ, ์ฃผ๊ธฐ์ ์œผ๋กœ ํ•ด๋‹น ์ด๋ฉ”์ผ์„ ์ „์†กํ•˜๋ ค๊ณ  ์‹œ๋„ํ•  ๊ฒƒ์ด๋‹ค.

์‚ฌ์šฉ์ž๊ฐ€ ์ƒˆ ๋ฉ”์ผ์„ ์ž‘์„ฑํ•œ ๊ฒฝ์šฐ์—๋„ ๊ฐ™์€ ํƒœ๊ทธ๋ช…์„ ๊ฐ–๋Š” ๋™๊ธฐํ™” ์ด๋ฒคํŠธ๋Š” ์ค‘๋ณตํ•ด์„œ ๋“ฑ๋ก๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— send-unsent-messages๊ฐ€ ์ด๋ฏธ ์žˆ๋Š”์ง€ ์•„๋‹ˆ๋ฉด ์‹คํ–‰์ค‘์ธ์ง€ ํ™•์ธํ•  ํ•„์š”๋„ ์—†๋‹ค.

๊ฐ€๋”์”ฉ SyncManager๊ฐ€ ํŠน์ • sync ์ด๋ฒคํŠธ๊ฐ€ ๊ณ„์† ์‹คํŒจํ•œ๋‹ค๊ณ  ํŒ๋‹จํ•˜๊ณ  ์ž์› ๋‚ญ๋น„๋ฅผ ๋ง‰๊ธฐ ์œ„ํ•ด ์ด๋ฒคํŠธ๋ฅผ ์ œ๊ฑฐํ•˜๊ธฐ ์ „ ๋งˆ์ง€๋ง‰์œผ๋กœ ํ•œ๋ฒˆ ๋” sync ์ด๋ฒคํŠธ๋ฅผ ๋ณด๋‚ด๊ธฐ๋กœ ๊ฒฐ์ •ํ•  ์ˆ˜๋„ ์žˆ๋‹ค. ์ด๋Ÿฐ ๊ฒฝ์šฐ ์ „๋‹ฌ๋œ Sync ์ด๋ฒคํŠธ์˜ lastChance ์†์„ฑ์„ ํ™•์ธํ•ด ํ•ด๋‹น ์ด๋ฒคํŠธ๊ฐ€ SyncManager๊ฐ€ ๋งˆ์ง€๋ง‰์œผ๋กœ ๋ณด๋‚ธ ์ด๋ฒคํŠธ๋ผ๋Š” ๊ฒƒ์„ ๊ฐ์ง€ํ•  ์ˆ˜ ์žˆ๊ณ  ์ด์—๋”ฐ๋ผ ํ•„์š”ํ•œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค.

self.addEventListener("sync", event=> {
  if (event.tag === "add-reservation") {
    event.waitUntil(
      addReservation()
      .then(() => {
        return Promise.resolve()
      })
      .catch((error) => {
        if (event.lastChance) {
          return removeReservation()
        } else {
          return Promise.reject()
        }
      })
    )
  }
})

์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ํŽ˜์ด์ง€์—์„œ ์„œ๋น„์Šค์›Œ์ปค๋กœ ์˜ฎ๊ธฐ๋ฉด, ๋ฌด์Šจ ์ผ์ด ์žˆ์–ด๋„ ์ž‘์—…์ด ์ˆ˜ํ–‰๋˜๋„๋ก ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด๋กœ์ธํ•ด ์ƒˆ๋กœ์šด ๋ณต์žก์„ฑ์ด ์ƒ๊ธฐ๊ฒŒ ๋œ๋‹ค.

๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ ํŽ˜์ด์ง€์—์„œ ์ˆ˜ํ–‰๋˜๋Š” ์ž‘์—…์„ ์™„๋ฃŒํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ ํ•„์š”ํ•˜๋‹ค. ๋ฉ”์‹œ์ง€๋ฅผ ์ „์†กํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ํŽ˜์ด์ง€๋Š” ๋ฉ”์‹œ์ง€ ํ…์ŠคํŠธ๊ฐ€ ํ•„์š”ํ•˜๋‹ค.
ํฌ์ŠคํŒ…์— ์ข‹์•„์š”๋ฅผ ๋ˆ„๋ฅด๋Š” ํ•จ์ˆ˜๋Š” ํฌ์ŠคํŒ…์˜ ID๊ฐ€ ํ•„์š”ํ•˜๋‹ค. ํ•˜์ง€๋งŒ ๋™๊ธฐํ™” ์ด๋ฒคํŠธ๋ฅผ ๋“ฑ๋กํ•  ๋•Œ๋Š” ์ด๋ฒคํŠธ ์ด๋ฆ„๋งŒ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋‹ค. ๋‹ค์‹œ๋งํ•ด ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ๋ฉ”์‹œ์ง€๋ฅผ ์ „์†กํ•˜๋„๋ก ์„œ๋น„์Šค ์›Œ์ปค์— ์š”์ฒญํ•  ์ˆ˜๋Š” ์žˆ์ง€๋งŒ ๋ฉ”์‹œ์ง€ ํ…์ŠคํŠธ๋ฅผ ์ „๋‹ฌํ•˜๋Š” ๊ฒƒ์€ ํ•จ์ˆ˜์— ์ธ์ž๋ฅผ ์ „๋‹ฌํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๊ฐ„๋‹จํ•˜์ง€ ์•Š๋‹ค.

์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์–‘ํ•œ ๋ฐฉ๋ฒ•์ด ์กด์žฌํ•œ๋‹ค.

IndexedDB์— ์•ก์…˜ ํ ๋งŒ๋“ค๊ธฐ

๋ฐฑ๊ทธ๋ผ์šด๋“œ ๋™๊ธฐํ™” ์ž‘์—…์ด ์‹œ์ž‘๋˜๊ธฐ ์ „์— ์‚ฌ์šฉ์ž๊ฐ€ ์ž‘์—…ํ•˜๊ณ  ์žˆ๋Š” ๋‚ด์šฉ์„ IndexedDB์— ์ €์žฅํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค. ๊ทธ ํ›„ ์„œ๋น„์Šค ์›Œ์ปค์˜ ๋™๊ธฐํ™” ์ด๋ฒคํŠธ ์ฝ”๋“œ๋Š” ๊ฐ์ฒด ์ €์žฅ์†Œ๋ฅผ ์ˆœํšŒํ•˜๋ฉฐ ์ €์žฅ๋œ ๋‚ด์šฉ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•„์š”ํ•œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.

๋ฉ”์‹œ์ง• ์•ฑ์œผ๋กœ ๋Œ์•„๊ฐ€ ์ด ๋ฐฉ๋ฒ•์„ ์ ์šฉํ•ด๋ณด๋ฉด, ๋ชจ๋“  ์‹ ๊ทœ ๋ฉ”์‹œ์ง€๋ฅผ message-queue ๊ฐ์ฒด ์ €์žฅ์†Œ์— ์ถ”๊ฐ€ํ•œํ›„ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ๋™๊ธฐํ™” ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ send-messages ์ด๋ฒคํŠธ๋ฅผ ๋“ฑ๋กํ•œ๋‹ค.

์ด ์ด๋ฒคํŠธ๋Š” message-queue์˜ ๋ชจ๋“  ๋ฉ”์‹œ์ง€๋ฅผ ์ˆœํšŒํ•˜์—ฌ ๊ฐ ๋ฉ”์‹œ์ง€๋ฅผ ๋„คํŠธ์›Œํฌ๋กœ ์ „์†กํ•˜๊ณ  message-queue์—์„œ ์‚ญ์ œํ•œ๋‹ค. ๋ชจ๋“  ๋ฉ”์‹œ์ง€๊ฐ€ ์ „์†ก๋˜๊ณ  ๊ฐ์ฒด ์ €์žฅ์†Œ๊ฐ€ ๋น„์›Œ์ง„ ํ›„์— sync ์ด๋ฒคํŠธ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ๋ฆฌ์กธ๋ธŒ๋œ๋‹ค. ๋ฉ”์‹œ์ง€๊ฐ€ ํ•˜๋‚˜๋ผ๋„ ์ „์†ก์— ์‹คํŒจํ•˜๋ฉด, ๋ฆฌ์ ๋œ ํ”„๋กœ๋ฏธ์Šค๊ฐ€ ์ด๋ฒคํŠธ๋กœ ๋ฐ˜ํ™˜๋˜๊ณ  SyncManager๋Š” ์ฐจํ›„์— ๋‹ค์‹œ ๋™๊ธฐํ™” ์ด๋ฒคํŠธ๋ฅผ ์‹œ์ž‘ํ•œ๋‹ค.

ํ•„์š”ํ•œ ํ๋งˆ๋‹ค(์˜ˆ: ๋ฐœ์‹  ๋ฉ”์‹œ์ง€์šฉ ํ, ํฌ์ŠคํŒ… ์ข‹์•„์š” ํ) ๋ณ„๊ฐœ์˜ ๊ฐ์ฒด ์ €์žฅ์†Œ๋ฅผ ์œ ์ง€ํ•˜์—ฌ, ๊ฐ๊ฐ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ณ„๋„์˜ ๋™๊ธฐํ™” ์ด๋ฒคํŠธ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.

์ด ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•ด ์ฝ”๋“œ๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๊ต์ฒดํ•  ์ˆ˜ ์žˆ๋‹ค.

const sendMessage = function(subject, message) {
  fetch('/new-message', {
    method: 'post',
    body: JSON.stringify({
      subject,
      message
    })
  })
}

์—ฌ๊ธฐ์— ์ด๋ ‡๊ฒŒ ์ถ”๊ฐ€ํ•ด๋ณด์ž.

const triggerMessageQueueUpdate = function() {
  navigator.serviceWorker.ready.then(function(registration) {
    registration.sync.register('message-queue-sync')
  })
}

const sendMessage = function(subject, message) {
  addToObjectStore("message-queue", {
    subj: subject,
    msg: message
  })
}

๋‹ค์Œ์— ์„œ๋น„์Šค์›Œ์ปค์— ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.

self.addEventListener("sync", (event) => {
  if (event.tag === 'message-queue-sync') {
    event.waitUntil(
      () => {
        return getAllMessages().then((messages) => {
          return Promise.all(
            messages.map((message) => {
              return fetch("/new-message", {
                method: 'post',
                body: JSON.stringify({
                  subj:subject,
                  msg: message
                })
              }).then(() => {
                return deleteMessageFromQueue(message)
              })
            })
          )
        })
      }
    )
  }
})

getAllMessages()๋ฅผ ์‚ฌ์šฉํ•ด IndexedDB์— ์Œ“์—ฌ ์žˆ๋Š” ๋ชจ๋“  ๋ฉ”์‹œ์ง€๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค. ์ดํ›„ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ๋‚ด๋ถ€์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๋ชจ๋“  ํ”„๋กœ๋ฏธ์Šค๊ฐ€ ๋ฆฌ์กธ๋ธŒ๋œ ๊ฒฝ์šฐ์—๋งŒ ๋ฆฌ์กธ๋ธŒ๋˜๋Š” ํ”„๋กœ๋ฏธ์Šค๋ฅผ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ๋กœ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

์ด ํ”„๋กœ๋ฏธ์Šค๋Š” Promise.all ํ•จ์ˆ˜์— ํ”„๋กœ๋ฏธ์Šค ๋ฐฐ์—ด์„ ๋„˜๊ฒจ ํ˜ธ์ถœํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๋งŒ๋“ค์–ด์ง„๋‹ค. ํ”„๋กœ๋ฏธ์Šค ๋ฐฐ์—ด์€ IndexedDB์—์„œ ๊ฐ€์ ธ์˜จ ๋ฉ”์‹œ์ง€ ๋ฐฐ์—ด์— ๋Œ€ํ•ด map()์„ ์‹คํ–‰ํ•ด, ๊ฐ ๋ฉ”์‹œ์ง€์— ๋Œ€ํ•ด ๊ฐ๊ฐ์˜ ํ”„๋กœ๋ฏธ์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ ์ƒ์„ฑ๋œ๋‹ค. ์ด๋“ค ๊ฐ๊ฐ์˜ ํ”„๋กœ๋ฏธ์Šค๋Š” ๋ฉ”์‹œ์ง€๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ๋ฐœ์†ก๋˜์–ด ํ์—์„œ ์‚ญ์ œ๋˜์—ˆ์„ ๋•Œ๋งŒ ๋ฆฌ์กธ๋ธŒ๋œ๋‹ค.

์ด ๋ฐฉ๋ฒ•์„ ์กฐ๊ธˆ ๋‹ค๋ฅด๊ฒŒ ์‹œ๋„ํ•ด ๋ณผ ์ˆ˜๋„ ์žˆ๋‹ค. ๋™๊ธฐํ™” ์ž‘์—…์— ํ•„์š”ํ•œ ๊ฐ์ฒด์™€ ์„ฑ๊ณต์ ์œผ๋กœ ๋™๊ธฐํ™”๊ฐ€๋œ ๊ฐ์ฒด๋ฅผ ํ•จ๊ป˜ ๋™์ผํ•œ ๊ฐ์ฒด์ €์žฅ์†Œ์— ์ €์žฅํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

์ด ๊ฒฝ์šฐ์—๋Š” ๊ฐ ๊ฐ์ฒด์˜ ์ƒํƒœ๋ฅผ ์ €์žฅํ•ด๋‘์—ˆ๋‹ค๊ฐ€, ๊ฐ์ฒด๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ๋™๊ธฐํ™”๋˜๋ฉด ์ด๋ฅผ ์—…๋ฐ์ดํŠธ ํ•  ์ˆ˜ ์žˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ์•ฑ์—์„œ ๋ฐœ์†ก๋œ ๋ฉ”์‹œ์ง€์™€ ๋ฏธ๋ฐœ์†ก๋œ ๋ฉ”์‹œ์ง€๋ฅผ ๊ฐ™์€ ์ €์žฅ์†Œ์— ์ €์žฅํ•ด๋‘”๋‹ค. ๋ฉ”์‹œ์ง€ ๊ฐ์ฒด์—๋Š” ๋ฉ”์‹œ์ง€ ์ฝ˜ํ…์ธ  ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ sent์™€ pending ๊ฐ™์€ ํ˜„์žฌ ์ƒํƒœ๋„ ํฌํ•จ๋œ๋‹ค. ๊ทธํ›„ ๋™๊ธฐํ™” ์ž‘์—…์€ pending ์ƒํƒœ์˜ ๋ชจ๋“  ๋ฉ”์‹œ์ง€๋ฅผ ์ˆœํšŒํ•˜๊ธฐ ์œ„ํ•ด ์ปค์„œ๋ฅผ ์˜คํ”ˆํ•˜๊ณ , ์ „์†กํ•˜๊ณ , ์ „์†ก ํ›„์— ์ƒํƒœ๋ฅผ sent๋กœ ๋ณ€๊ฒฝํ•œ๋‹ค.

IndexedDB์— ์š”์ฒญ ํ ๋งŒ๋“ค๊ธฐ

์ด๋ฏธ ์ž‘์„ฑ๋œ ํ”„๋กœ์ ํŠธ๋ฅผ ์ˆ˜์ •ํ•ด์•ผํ•˜๋Š” ๊ฒฝ์šฐ, ๊ฐ์ฒด๋ฅผ ๋กœ์ปฌ์— ์ €์žฅํ•˜๋„๋ก ์•ฑ์˜ ๊ตฌ์กฐ๋ฅผ ๋ฐ”๊พธ๊ณ  ๊ฐ์ฒด ์ƒํƒœ๋ฅผ ์ถ”์ ํ•˜๊ธฐ ์œ„ํ•œ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์€ ๋„ˆ๋ฌด ๊ณผํ•œ ์ผ์ด ๋  ์ˆ˜ ์žˆ๋‹ค. ๊ธฐ์กด ํ”„๋กœ์ ํŠธ์— ๋ฐฑ๊ทธ๋ผ์šด๋“œ ๋™๊ธฐํ™”๋ฅผ ์ ์šฉํ•  ๋•Œ ์ข€๋” ๊ฐ„๋‹จํ•œ ๋ฐฉ๋ฒ•์€ ๊ธฐ์กด Ajax ํ˜ธ์ถœ์„ ์š”์ฒญ ํ๋กœ ๋ฐ”๊พธ๋Š” ๊ฒƒ์ด๋‹ค.

์ด ๋ฐฉ์‹์„ ์ ์šฉํ•˜๋ฉด ๊ฐ ๋„คํŠธ์›Œํฌ ์š”์ฒญ์„, IndexedDB์— ์„ธ๋ถ€ ์š”์ฒญ์‚ฌํ•ญ์„ ์ €์žฅํ•˜๋Š” ๋ฉ”์†Œ๋“œ๋กœ ๊ต์ฒดํ•˜๊ณ , ๋™๊ธฐํ™” ์ด๋ฒคํŠธ๋ฅผ ๋“ฑ๋กํ•œ๋‹ค. ๋“ฑ๋ก๋œ ๋™๊ธฐํ™” ์ด๋ฒคํŠธ๋Š” ๊ฐ์ฒด ์ €์žฅ์†Œ์— ์ €์žฅ๋œ ๋ชจ๋“  ์š”์ฒญ์„ ์‚ดํ”ผ๊ณ  ํ•œ๋ฒˆ์— ํ•˜๋‚˜์”ฉ ๊ฐ ์š”์ฒญ์„ ๋ณด๋‚ธ๋‹ค.

์ด์ „ ๋ฐฉ๋ฒ•๊ณผ ๋‹ฌ๋ฆฌ, ๋™๊ธฐํ™” ์ด๋ฒคํŠธ์—์„œ ๊ฐ ๋„คํŠธ์›Œํฌ ์š”์ฒญ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ํ•„์š”ํ•œ ๋ชจ๋“  ์„ธ๋ถ€์‚ฌํ•ญ์„ IndexedDB์— ์ €์žฅํ•œ๋‹ค. ๋™๊ธฐํ™” ์ฝ”๋“œ๋Š” ๊ฐ๊ฐ์˜ ์ž‘์—…์ด ์‚ฌ์ดํŠธ์—์„œ ๋ฌด์Šจ ์˜๋ฏธ์ธ์ง€ ์ดํ•ดํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค. ๊ทธ์ € ์š”์ฒญ ๋ชฉ๋ก์„ ๋งน๋ชฉ์ ์œผ๋กœ ํƒ์ƒ‰ํ•˜๋ฉฐ, ํ•˜๋‚˜์”ฉ ์‹คํ–‰ํ•˜๊ธฐ๋งŒ ํ•˜๋ฉด๋œ๋‹ค.

const sendMessage = function(subject, message) {
  fetch('/new-message', {
    method: 'POST',
    body: JSON.stringify({
      subject,
      message
    })
  })
}

const likePost = function(postId) {
  fetch('/like-post?id=' + postId)
}

์ด๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ฝ”๋“œ๋ฅผ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ๋‹ค.

const triggerRequestQueueSync = () => {
  navigator.serviceWorker.ready.then((registration) => {
    registration.sync.register('request-queue');
  });
};

const sendMessage = (subject, message) => {
  addToObjectStore("request-queue", {
    url: "/new-message",
    method: "POST",
    body: JSON.stringify({
      subject,
      message
    })
  })

  triggerRequestQueueSync();
}

const likePost = (postId) => {
  addToObjectStore("request-queue", {
    url: "/like-post?id=" + postId,
    method: "GET"
  })

  triggerRequestQueueSync();
} 

๋„คํŠธ์›Œํฌ ์š”์ฒญ ์ฝ”๋“œ๋ฅผ request-queue๋ผ๋Š” ๊ฐ์ฒด ์ €์žฅ์†Œ์— ๊ฐœ๋ณ„ ์š”์ฒญ์„ ๋‚˜ํƒ€๋‚ด๋Š” ๊ฐ์ฒด๋ฅผ ์ €์žฅํ•˜๋Š” ์ฝ”๋“œ๋กœ ๊ต์ฒดํ•œ๋‹ค. ์ €์žฅ๋˜๋Š” ๊ฐ๊ฐ์˜ ๊ฐ์ฒด๋Š” ๋„คํŠธ์›Œํฌ ์š”์ฒญ์— ํ•„์š”ํ•œ ๋ชจ๋“  ์ •๋ณด๋ฅผ ๋‹ด๊ณ  ์žˆ๋‹ค. ๊ทธ ๋‹ค์Œ ์„œ๋น„์Šค ์›Œ์ปค์— sync ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ request-queue ์•ˆ์˜ ๋ชจ๋“  ์š”์ฒญ์„ ๊ฒ€ํ† ํ•˜๊ณ , ๊ฐ๊ฐ์— ๋Œ€ํ•œ ๋„คํŠธ์›Œํฌ ์š”์ฒญ์„ ๋งŒ๋“ค๊ณ  ์š”์ฒญ์ด ์„ฑ๊ณตํ•˜๋ฉด, ๊ฐ์ฒด ์ €์žฅ์†Œ์—์„œ ํ•ด๋‹น ์š”์ฒญ์„ ์‚ญ์ œํ•œ๋‹ค.

self.addEventListener('sync', (event) => {
  if (event.tag === 'request-queue') {
    event.waitUntil(
      () => {
        return getAllObjectsFrom("request-queue").then((requests) => {
          return Promise.all(
            requests.map((request) => {
              return fetch(request.url, {
                method: request.method,
                body: request.body
              }).then(() => {
                return deleteRequestFromQueue(message) // returns a promise
              })
            })
          )
        })
      }
    )
  }
})

์„ฑ๊ณตํ•œ ์š”์ฒญ์€ deleteRequestFromQueue() ๋ฉ”์†Œ๋“œ ํ˜ธ์ถœ์„ ํ†ตํ•ด IndexedDB ํ์—์„œ ์‚ญ์ œ๋œ๋‹ค. ์‹คํŒจํ•œ ์š”์ฒญ์€ ํ์— ๋‚จ๊ณ , ๋ฆฌ์ ๋œ ํ”„๋กœ๋ฏธ์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ๋„คํŠธ์›Œํฌ ์š”์ฒญ ์ค‘ ํ•˜๋‚˜๋ผ๋„ ๋ฆฌ์ ๋œ ํ”„๋กœ๋ฏธ์Šค๋ฅผ ๋ฐ˜ํ™˜ํ–ˆ๋‹ค๋ฉด, ์ž ์‹œํ›„ sync ์ด๋ฒคํŠธ๊ฐ€ ๋‹ค์‹œ ๋ฐœ์ƒํ•œ๋‹ค. ์•ž์„œ ์„ฑ๊ณต์ ์œผ๋กœ ํ˜ธ์ถœ๋œ ํ์—์„œ ์‚ญ์ œ๋œ ๋„คํŠธ์›Œํฌ ์š”์ฒญ์„ ์ œ์™ธํ•œ ๋‚˜๋จธ์ง€ ์š”์ฒญ๋“ค์„ ๋‹ค์‹œ ๊ฒ€ํ† ํ•˜์—ฌ ๋„คํŠธ์›Œํฌ ์š”์ฒญ์„ ์‹œ๋„ํ•œ๋‹ค.

๋™๊ธฐํ™” ์ด๋ฒคํŠธ ํƒœ๊ทธ๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ ์ „๋‹ฌํ•˜๊ธฐ

๋™๊ธฐํ™” ํ•จ์ˆ˜์— ๊ฐ„๋‹จํ•œ ๊ฐ’์„ ์ „๋‹ฌํ•ด์•ผํ•  ๋•Œ, ๋ชจ๋“  ์ž‘์—…์„ ์ผ์ผ์ด ์ถ”์ ํ•˜๊ธฐ ์œ„ํ•œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์€ ๋„ˆ๋ฌด ๊ณผํ•˜๊ฒŒ ๋Š๊ปด์งˆ ์ˆ˜ ์žˆ๋‹ค.

์‚ฌ์šฉ์ž๊ฐ€ ํŽ˜์ด์ง€์— ํ‘œ์‹œ๋œ ํŠน์ • ํฌ์ŠคํŠธ์— ์ข‹์•„์š”๋ฅผ ๋ˆ„๋ฅผ ์ˆ˜ ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ณด์ž. ์ด๋Š” ํฌ์ŠคํŒ…์˜ ID๋ฅผ ํŠน์ • URL๋กœ ์ „๋‹ฌํ•˜๋Š” ๊ฐ„๋‹จํ•œ ์ž‘์—…์ด๋‹ค. ๊ธฐ์กด ์ฝ”๋“œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

const likePost = function(postId) {
  fetch("/like-post?id=" + postId)
}

์ด๋ฅผ ๋ฐ”๊ฟ”๋ณด์ž.

const likePost = function(postId) {
  navigator.serviceWorker.ready.then((registration) => {
    registration.sync.register('like-post?id=' + postId)
  })
}

๊ทธ๋ฆฌ๊ณ  ID๋ฅผ ์ถ”์ถœํ•˜๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.

self.addEventListener('sync', function(event) {
  if (event.tag.startsWith("like-post-")) {
    event.waitUntil(function() {
      const postId = event.tag.slice(10);
      return fetch("/like-post?id=" + postId)
    })
  }
})

react-query์—์„œ์˜ ๊ณ ๋ฏผ

react-query์—๋Š” ๋‚˜์ค‘์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก queryClient ๋ฐ ํ•ด๋‹น ์บ์‹œ์˜ ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•˜๋Š” ๊ธฐ๋Šฅ์ด์žˆ๋‹ค.

react-query์™€ background sync๊ธฐ๋Šฅ์„ ๊ณต์กด ์‹œํ‚ค๊ณ  ์‹ถ์—ˆ๋‹ค.

react-query๋Š” ๋„คํŠธ์›Œํฌ ์—ฐ๊ฒฐ์ด ์—†๋Š” ๊ฒฝ์šฐ query์™€ mutation์ด ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•ด์•ผ ํ•˜๋Š”์ง€ ๊ตฌ๋ถ„ํ•˜๊ธฐ ์œ„ํ•ด ์„ธ๊ฐ€์ง€ ๋‹ค๋ฅธ ๋„คํŠธ์›Œํฌ ๋ชจ๋“œ๋ฅผ ์ œ๊ณตํ•œ๋‹ค. ์ด ๋ชจ๋“œ๋“ค์€ query์™€ mutation์— ๋Œ€ํ•ด ๊ฐœ๋ณ„์ ์œผ๋กœ ๋˜๋Š” ๊ธฐ๋ณธ๊ฐ’์„ ์ „์—ญ์ ์œผ๋กœ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

Since React Query is most often used for data fetching in combination with data fetching libraries, the default network mode is online.

Network Mode: online

์ด ๋ชจ๋“œ์—์„œ๋Š” ์—ฌ๋Ÿฌ๋ถ„์ด ์˜จ๋ผ์ธ ์ƒํƒœ๊ฐ€ ์•„๋‹ˆ๋ผ๋ฉด query์™€ mutation์ด ์‹คํ–‰๋˜์ง€ ์•Š๋Š”๋‹ค. ๋„คํŠธ์›Œํฌ ์—ฐ๊ฒฐ์ด ์—†์ด query๋ฅผ ์ˆ˜ํ–‰ํ•  ๊ฒฝ์šฐ ํ•ญ์ƒ ์ƒํƒœ(loading, error, success)๋ฅผ ์œ ์ง€ํ•œ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ fetchStatus๊ฐ€ ์ถ”๊ฐ€๋กœ ๋…ธ์ถœ๋œ๋‹ค.

fetchStatus๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

  • fetching: The queryFn is really executing - a request is in-flight.
  • paused: The query is not executing - it is paused until you have connection again
  • idle: The query is not fetching and not paused

isFetching ๋ฐ isPaused ํ”Œ๋ž˜๊ทธ๋Š” ์ด ์ƒํƒœ์—์„œ ํŒŒ์ƒ๋˜๋ฉฐ ํŽธ์˜๋ฅผ ์œ„ํ•ด ๋…ธ์ถœ๋œ๋‹ค.

๋งŒ์•ฝ query๊ฐ€ ์‹คํ–‰์ค‘์— offline ์ƒํƒœ๊ฐ€ ๋œ๋‹ค๋ฉด react-query๋Š” retry mechanism์„ ์ผ์‹œ ์ค‘์ง€ํ•œ๋‹ค. ์ผ์‹œ ์ค‘์ง€๋œ query๋Š” ๋„คํŠธ์›Œํฌ์— ๋‹ค์‹œ ์—ฐ๊ฒฐ๋˜๋ฉด ๊ณ„์† ์‹คํ–‰๋œ๋‹ค. ์ด๊ฒƒ์€ refetchOnReconnect์™€๋Š” ๋ฌด๊ด€ํ•˜๋‹ค. ์˜คํžˆ๋ ค ์ด๊ฒƒ์€ refetch๊ฐ€ ์•„๋‹ˆ๊ณ  continue์— ๊ฐ€๊น๋‹ค. ๋งŒ์•ฝ query๊ฐ€ ์ทจ์†Œ๋œ๋‹ค๋ฉด continue๋˜์ง€๋Š” ์•Š๋Š”๋‹ค.

Network Mode: always

In this mode, React Query will always fetch and ignore the online / offline state. This is likely the mode you want to choose if you use React Query in an environment where you don't need an active network connection for your Queries to work - e.g. if you just read from AsyncStorage, or if you just want to return Promise.resolve(5) from your queryFn.

  • Queries will never be paused because you have no network connection.
  • retries will also not pause - you Query will go to error state if it fails.
  • refetchOnReconnect defaults to false in this mode, because reconnecting to the network is not a good indicator anymore that stale queries should be refetched. You can still turn it on if you want.

Network Mode: offlineFirst

This mode is the middle ground between the first two options, where react-query will run the queryFn once, but then pause retries. This is very handy if you have a serviceWorker that intercepts a request for caching like in an offline-first PWA, or if you use HTTP caching via the Cache-Control header.

In those situations, the first fetch might succeed because it comes from an offline storage / cache. However, if there is a cache miss, the network request will go out and fail, in which case this mode behaves like an online query - pausing retries.

Devtools

The react-query-devtools will show queries in a paused state if they would be fetching, but there is no network connection. There is also a toggle button to Mock offline behavior. Please note that this button will not actually mess with your network connection, but it will set the OnlineManager in an offline state.

Signature

  • networkMode: online, always, offlineFirst
  • optional
  • defaults to online