Synchronizace na pozadí se servisními pracovníky

Servisní pracovníci měli chvilku. V březnu 2018 začalo iOS Safari zahrnovat servisní pracovníky – takže všechny hlavní prohlížeče v tuto chvíli podporují možnosti offline. A to je důležitější než kdy jindy – 20 % dospělých ve Spojených státech nemá doma internet, takže tito jedinci se při přístupu k většině informací spoléhají pouze na mobilní telefon. To může zahrnovat něco tak jednoduchého, jako je kontrola bankovního zůstatku nebo něco tak zdlouhavého, jako je hledání práce nebo dokonce výzkum nemocí.

Offline podporované aplikace jsou nutností a zahrnutí servisního pracovníka je skvělý začátek. Samotní servisní pracovníci však někoho dostanou pouze na část cesty ke skutečně bezproblémovému online-to-offline zážitku. Ukládání prostředků do mezipaměti je skvělé, ale bez připojení k internetu stále nemůžete přistupovat k novým datům ani odesílat žádné požadavky.

Životní cyklus požadavku

Aktuálně může požadavek vypadat takto:

Uživatel stiskne tlačítko a někde se odešle požadavek na server. Pokud je internet, vše by mělo jít bez problémů. Pokud není internet... tak věci nejsou tak jednoduché. Požadavek nebude odeslán a uživatel si možná uvědomuje, že jeho požadavek nikdy neprošel, nebo možná o tom neví. Naštěstí existuje lepší způsob.

Zadejte:synchronizace na pozadí.

Synchronizace na pozadí

Životní cyklus se synchronizací na pozadí je mírně odlišný. Nejprve uživatel zadá požadavek, ale místo okamžitého pokusu o požadavek zasáhne servisní pracovník. Servisní pracovník zkontroluje, zda má uživatel přístup k internetu - pokud ano, skvělé. Žádost bude odeslána. Pokud ne, servisní pracovník počká, dokud uživatel učiní mít internet a v tomto okamžiku odeslat požadavek poté, co načte data z IndexedDB. Nejlepší ze všeho je, že synchronizace na pozadí bude pokračovat a odešle požadavek, i když uživatel opustil původní stránku.

Zatímco synchronizace na pozadí je plně podporována pouze v prohlížeči Chrome, Firefox a Edge aktuálně pracují na její implementaci. Naštěstí s použitím funkce detekce a onLine a offLine události, můžeme bezpečně používat synchronizaci na pozadí v jakékoli aplikaci a zároveň zahrnovat záložní.

(Pokud byste chtěli sledovat ukázku, kód najdete zde a samotné demo zde.)

Předpokládejme, že máme velmi jednoduchý formulář pro přihlášení k newsletteru. Chceme, aby se uživatel mohl přihlásit k odběru našeho newsletteru, ať už má nebo nemá přístup k internetu. Začněme implementací synchronizace na pozadí.

(Tento tutoriál předpokládá, že jste obeznámeni se servisními pracovníky. Pokud ne, zde je dobré začít. Pokud nejste obeznámeni s IndexedDB, doporučuji začít zde.)

Když poprvé nastavujete servisního pracovníka, budete ho muset zaregistrovat ze souboru JavaScript vaší aplikace. Může to vypadat takto:

if(navigator.serviceWorker) {
      navigator.serviceWorker.register('serviceworker.js');
}

Všimněte si, že používáme detekci funkcí i při registraci servisního pracovníka. Detekce funkcí nemá téměř žádnou nevýhodu a zabrání tomu, aby se ve starších prohlížečích, jako je Internet Explorer 11, objevovaly chyby, když servisní pracovník není dostupný. Celkově je dobrým zvykem držet krok, i když to není vždy nutné.

Když nastavíme synchronizaci na pozadí, naše funkce registru se změní a může vypadat nějak takto:

if(navigator.serviceWorker) {
        navigator.serviceWorker.register('./serviceworker.js')
        .then(function() {
            return navigator.serviceWorker.ready
        })
        .then(function(registration) {
            document.getElementById('submitForm').addEventListener('click', (event) => {
                registration.sync.register('example-sync')
                .catch(function(err) {
                    return err;
                })
            })
        })
        .catch( /.../ )
    }

Toto je mnohem více kódu, ale rozebereme ho po řádcích.

Nejprve registrujeme servisního pracovníka jako dříve, ale nyní využíváme toho, že register funkce vrací slib. Další kousek, který vidíte, je navigator.serviceWorker.ready . Toto je vlastnost servisního pracovníka pouze pro čtení, která v podstatě pouze informuje, zda je servisní pracovník připraven nebo ne. Tato vlastnost nám poskytuje způsob, jak zpozdit provádění následujících funkcí, dokud nebude servisní pracovník skutečně připraven.

Dále máme odkaz na registraci servisního pracovníka. Na naše tlačítko pro odeslání vložíme posluchač události a v tomto okamžiku zaregistrujeme událost synchronizace a předáme řetězec. Tento řetězec bude později použit na straně servisního pracovníka.

Pojďme to opravdu rychle přepsat, abychom zahrnuli detekci funkcí, protože víme, že synchronizace na pozadí zatím nemá širokou podporu.

if(navigator.serviceWorker) {
        navigator.serviceWorker.register('./serviceworker.js')
        .then(function() {
            return navigator.serviceWorker.ready
        })
        .then(function(registration) {
            document.getElementById('submitForm').addEventListener('click', (event) => {
                if(registration.sync) {
                    registration.sync.register('example-sync')
                    .catch(function(err) {
                        return err;
                    })
                }
            })
        })
    }

Nyní se podívejme na stranu servisních pracovníků.

self.onsync = function(event) {
    if(event.tag == 'example-sync') {
        event.waitUntil(sendToServer());
    }
}

K onsync připojíme funkci , posluchač událostí pro synchronizaci na pozadí. Chceme sledovat řetězec, který jsme předali do funkce registru zpět v JavaScriptu aplikace. Tento řetězec sledujeme pomocí event.tag .

Také používáme event.waitUntil . Protože servisní pracovník neběží neustále – „probudí se“, aby provedl úkol a poté „přešel do režimu spánku“, chceme použít event.waitUntil aby byl servisní pracovník aktivní. Tato funkce přijímá parametr funkce. Funkce, kterou předáme, vrátí příslib a event.waitUntil udrží servisního pracovníka „v bdělém stavu“, dokud se tato funkce nevyřeší. Pokud bychom nepoužili event.waitUntil požadavek se možná nikdy nedostane na server, protože servisní pracovník by spustil onsync a poté se okamžitě vrátíte do režimu spánku.

Když se podíváte na výše uvedený kód, všimnete si, že nemusíme nic dělat, abychom zkontrolovali stav připojení uživatele k internetu, nebo pokud první pokus selže, požadavek znovu odeslat. Synchronizace na pozadí se o to postará za nás. Podívejme se, jak přistupujeme k datům v servisním pracovníkovi.

Protože je servisní pracovník izolován ve svém vlastním pracovníkovi, nebudeme mít přístup k žádným datům přímo z DOM. Budeme se spoléhat na IndexedDB, že získá data a poté je odešle na server.

IndexedDB využívá zpětná volání, zatímco servisní pracovník je založen na slibech, takže s tím budeme muset počítat v naší funkci. (Okolo IndexedDB existují obálky, které tento proces trochu zjednodušují. Doporučuji vyzkoušet IDB nebo peněžní sponu.)

Naše funkce může vypadat následovně:

return new Promise(function(resolve, reject) {
    var db = indexedDB.open('newsletterSignup');
    db.onsuccess = function(event) {
        this.result.transaction("newsletterObjStore").objectStore("newsletterObjStore").getAll().onsuccess = function(event) {
            resolve(event.target.result);
        }
    }
    db.onerror = function(err) {
        reject(err);
    }
});

Když to projdeme, vracíme slib a použijeme resolve a reject parametry, aby byla tato funkce více založená na slibech, aby bylo vše v souladu se servisním pracovníkem.

Otevřeme databázi a použijeme getAll metoda k vytažení všech dat ze zadaného úložiště objektů. Jakmile to bude úspěšné, vyřešíme funkci s daty. Pokud máme chybu, odmítneme. Díky tomu naše zpracování chyb funguje stejně jako všechny ostatní sliby a zajišťuje, že máme data předtím, než je odešleme na server.

Poté, co získáme data, provedeme pouze požadavek na načtení způsobem, jakým bychom to normálně dělali.

fetch('https://www.mocky.io/v2/5c0452da3300005100d01d1f', {
    method: 'POST',
    body: JSON.stringify(response),
    headers:{
        'Content-Type': 'application/json'
    }
})

To vše samozřejmě poběží pouze v případě, že má uživatel přístup k internetu. Pokud uživatel nemá přístup k internetu, servisní pracovník počká, dokud se spojení neobnoví. Pokud se po obnovení připojení požadavek načtení nezdaří, servisní pracovník se pokusí maximálně třikrát, než se přestane pokoušet požadavek definitivně odeslat.

Nyní, když jsme nastavili synchronizaci na pozadí, jsme připraveni nastavit naši záložní verzi pro prohlížeče, které nepodporují synchronizaci na pozadí.

Podpora starších prohlížečů

Bohužel, servisní pracovníci nejsou podporováni ve starších prohlížečích a funkce synchronizace na pozadí je nyní podporována pouze v Chrome. V tomto příspěvku se zaměříme na využití dalších offline funkcí, abychom napodobili synchronizaci na pozadí a nabídli podobný zážitek.

Online a offline události

Začneme online a offline událostmi. Náš kód pro registraci servisní práce minule vypadal takto:

if(navigator.serviceWorker) {
    navigator.serviceWorker.register('./serviceworker.js')
    .then(function() {
        return navigator.serviceWorker.ready
    })
    .then(function(registration) {
        document.getElementById('submitForm').addEventListener('click', (event) => {
            event.preventDefault();
            saveData().then(function() {
                if(registration.sync) {
                    registration.sync.register('example-sync')
                    .catch(function(err) {
                        return err;
                    })
                }
            });
        })
    })
}

Udělejme rychlou rekapitulaci tohoto kódu. Poté, co zaregistrujeme servisního pracovníka, použijeme příslib vrácený z navigator.serviceWorker.ready aby bylo zajištěno, že servisní pracovník je skutečně připraven vyrazit. Jakmile bude servisní pracovník připraven jít, připojíme k tlačítku Odeslat posluchač události a data okamžitě uložíme do IndexedDB. Lucky for us IndexedDB je podporována efektivně ve všech prohlížečích, takže se na ni můžeme docela dobře spolehnout.

Po uložení dat používáme detekci funkcí, abychom se ujistili, že můžeme použít synchronizaci na pozadí. Pokračujme a přidejte náš záložní plán do jiného.

if(registration.sync) {
    registration.sync.register('example-sync')
    .catch(function(err) {
        return err;
    })
} else {
    if(navigator.onLine) {
        sendData();
    } else {
        alert("You are offline! When your internet returns, we'll finish up your request.");
    }
}

Další podpora

Používáme navigator.onLine pro kontrolu připojení uživatele k internetu. Pokud mají připojení, vrátí se to jako true. Pokud mají připojení k internetu, budeme pokračovat a data odešleme. V opačném případě zobrazíme upozornění, které uživateli sdělí, že jeho data nebyla odeslána.

Ke sledování internetového připojení přidáme pár událostí. Nejprve přidáme událost, abychom mohli sledovat, jak připojení probíhá offline.

window.addEventListener('offline', function() {
    alert('You have lost internet access!');
});

Pokud uživatel ztratí připojení k internetu, zobrazí se mu upozornění. Dále přidáme posluchač události, který bude sledovat, aby se uživatel mohl vrátit online.

window.addEventListener('online', function() {
    if(!navigator.serviceWorker && !window.SyncManager) {
        fetchData().then(function(response) {
            if(response.length > 0) {
                return sendData();
            }
        });
    }
});

Jakmile se internetové připojení uživatele vrátí, rychle zkontrolujeme, zda je dostupný servisní pracovník, a také dostupnost synchronizace. Chceme to zkontrolovat, protože pokud má prohlížeč k dispozici synchronizaci, nemusíme se spoléhat na naše nouzové řešení, protože by to vedlo ke dvěma načtením. Pokud však použijeme naši záložní verzi, nejprve vytáhneme data z IndexedDB takto:

var myDB = window.indexedDB.open('newsletterSignup');

myDB.onsuccess = function(event) {
    this.result.transaction("newsletterObjStore").objectStore("newsletterObjStore").getAll().onsuccess = function(event) {
        return event.target.result;
    };
};

myDB.onerror = function(err) {
    reject(err);
}

Dále ověříme, že odpověď z IndexedDB skutečně obsahuje data, a pokud ano, odešleme je na náš server.

Tato záložní možnost zcela nenahradí synchronizaci na pozadí z několika důvodů. Za prvé, kontrolujeme online a offline události, což se synchronizací na pozadí nemusíme dělat, protože synchronizace na pozadí to vše zvládá za nás. Synchronizace na pozadí se navíc bude nadále pokoušet odesílat požadavky, i když uživatel stránku opustil.

Naše řešení nebude schopno odeslat požadavek, i když uživatel odejde, ale můžeme preventivně zkontrolovat IndexedDB, jakmile se stránka načte, a okamžitě odeslat všechna data uložená v mezipaměti. Toto řešení také sleduje jakékoli změny síťového připojení a odesílá data uložená v mezipaměti, jakmile se připojení vrátí.

Další kroky podpory offline

Prohlížeče Edge a Firefox aktuálně pracují na implementaci synchronizace na pozadí, což je fantastické. Je to jedna z nejlepších funkcí pro poskytování empatičtějšího zážitku pro uživatele, kteří se pohybují mezi připojením k internetu a ztrátou připojení. Naštěstí s trochou pomoci online a offline událostí a IndexedDB můžeme uživatelům začít poskytovat lepší prostředí již dnes.

Pokud se chcete dozvědět více o offline technikách, podívejte se na můj blog:carmalou.com nebo mě sledujte na Twitteru.