iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 20
1
Modern Web

你應該要知道的新一代Web技術---漸進式網頁(PWA)系列 第 20

[Day20] 了解PWA中的背景同步(Part2)

從昨天的流程圖可以發現,我們還必須將用戶要發佈的貼文資訊儲存到indexedDB中才行R。那我就接續昨天在feed.js中增加的code:

if('serviceWorker' in navigator && 'SyncManager' in window) {
    navigator.serviceWorker.ready.then(function(sw) {
      var post = {
        id: new Date().toISOString(),
        title: titleInput.value,
        location: locationInput.value
      };
      writeData('sync-posts', post).then(function() {
        return sw.sync.register('sync-new-post');
      }).then(function() {
        var snackbarContainer = document.querySelector('#confirmation-toast');
        var data = {message: '貼文已經儲存並開始背景同步!'};
        snackbarContainer.MaterialSnackbar,showSnackbar(data);
      }).catch(function(err) {
        console.log(err);
      });
    });
} else {
    sendData();
}

我先將用戶貼文資料打包成javascript object存到post變數裡,接下來還記得我之前在utility.js中已經完成了「寫入資料到indexedDB (writeData)」的funciton,這裡就可以直接使用這個函式啦。

在成功寫入indexedDB後,我才要執行註冊同步工作。因為之後service worker要從indexedDB中把POST的資料傳送出去,所以必須得確定資料已經存在indexedDB了。

最後的snackbarContainer是我直接使用material design提供的components,來增加user experience。當以上步驟都就緒後,會在視窗底部跳出一個通知,讓用戶知道「貼文已經儲存並開始背景同步!」。

接下來如果今天瀏覽器不支援Background Synchronous哩?這樣是不是需要一個Fallback的方法,能直接將用戶的貼文資料傳送到我們的server裡(目前因為還沒實作server,所以這裡指的是後台firebase資料庫),這裡我又實作另一個sendData() function:

function sendData() {
  fetch('https://trip-diary-f56de.firebaseio.com/posts.json', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Accept': 'application/json'
    },
    body: JSON.stringify({
      id: new Date().toISOString,
      title: titleInput.value,
      location: locationInput.value,
      image: 'https://firebasestorage.googleapis.com/v0/b/trip-diary-f56de.appspot.com/o/sf-boat.jpg?alt=media&token=fde987b2-5213-4d0e-b4ff-d3a548dc107e'
    })
  }).then(function(res) {
    console.log('Send data', res);
    updateUI();
  })
}

簡單說明一下我的邏輯,一樣使用fetch API發出POST Request並設定表頭,另外在Request的body中,因為我還沒實作拍照的功能,所以image欄位就先寫死。


最後重點來了~~/images/emoticon/emoticon23.gif 要在service worker中加入監聽sync event了。這樣當網路恢復連線時,service worker就知道要將儲存在indexedDB中待處理的貼文資料POST出去:

self.addEventListener('sync', function(event) {
    console.log('[Service Worker] Background syncing', event);
    if(event.tag === 'sync-new-posts。是的話就可以開始') {
        console.log('[Service Worker] Syncing new Posts');
        event.waitUntil(
            readAllData('sync-posts').then(function(data) {
                for(var dt of data) {
                    // feed.js中sendData()的code
                    fetch('https://trip-diary-f56de.firebaseio.com/posts.json', {
                        method: 'POST',
                        headers: {
                        'Content-Type': 'application/json',
                        'Accept': 'application/json'
                        },
                        body: JSON.stringify({
                        id: dt.id,
                        title: dt.title,
                        location: dt.location,
                        image: 'https://firebasestorage.googleapis.com/v0/b/trip-diary-f56de.appspot.com/o/sf-boat.jpg?alt=media&token=fde987b2-5213-4d0e-b4ff-d3a548dc107e'
                        })
                    }).then(function(res) {
                        console.log('Send data', res);
                        if(res.ok) {
                            deleteItemFromData('sync-posts', dt.id);
                        }
                    }).catch(function(err) {
                        console.log('Error while sending data', err); 
                    });
                }
            })
        );
    }
});

由於可能會有多個不同的同步工作要進行不同的處理,所以首先我們要先確定監聽到的「event tag」是否等於「我當初在註冊同步工作時設定的id」,這裡是sync-new-posts。是的話就跟之前一樣把我們接下去要執行的邏輯放在event.waitUntil()中,原因是為了避免執行同步工作時,其它的event listener又要執行其他的工作而發生衝突。

接著我在utility.js中也已經寫好讀取indexedDB的function了(readAllData),這裡當然直接使用囉。回傳回來的是一個陣列,所以我用一個for-of loop將每一筆的貼文「去執行之前在feed.js中寫好的sendData() code」,也就是傳送到後台firebase資料庫中。

在該筆貼文傳送完成後,如果response為200 OK,那就要來清除「sync-new-posts」這個object store中的這一則貼文,不過我好像還沒在utility.js中實作這個function,來看一下要怎麼寫吧:

function deleteItemFromData(objectStore, id) {
    dbPromise.then(function(db) {
        var tx = db.transaction(objectStore, 'readwrite');
        var store = tx.objectStore(objectStore);
        store.delete(id);
        return tx.complete;
    }).then(function() {
        console.log('Item deleted!');
    });
}

基本上步驟都跟其他操作indexedDB的函式差不多,只是要刪除某個object store中的單筆資料,必須要使用delete(id)這個function才行,當然這裡的id是我存在indexedDB中的每一則貼文的「時間」。

Day20 結束!! /images/emoticon/emoticon34.gif


上一篇
[Day19] 了解PWA中的背景同步(Part1)
下一篇
[Day21] 了解PWA中的背景同步(Part3)
系列文
你應該要知道的新一代Web技術---漸進式網頁(PWA)29
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言