So lassen Sie Ihre Webanwendungen offline arbeiten

Die Leistungsfähigkeit von JavaScript und der Browser-API

Die Welt wird immer vernetzter - die Zahl der Menschen mit Internetzugang ist auf 4,5 Milliarden gestiegen.

Bild

Diese Daten geben jedoch nicht die Anzahl der Personen wieder, die eine langsame oder unterbrochene Internetverbindung haben. Selbst in den USA können 4,9 Millionen Haushalte nicht mit einer Geschwindigkeit von über 3 Megabit pro Sekunde auf den kabelgebundenen Internetzugang zugreifen.

Der Rest der Welt - diejenigen mit zuverlässigem Internetzugang - neigt immer noch dazu, die Konnektivität zu verlieren. Einige Faktoren , die die Qualität Ihrer Netzwerkverbindung beeinflussen können, sind:

  • Schlechte Abdeckung durch den Anbieter.
  • Extreme Wetterbedingungen.
  • Stromausfälle.
  • Benutzer, die in tote Zonen fallen, z. B. Gebäude, die ihre Netzwerkverbindungen blockieren.
  • Zugfahrt und Tunnelfahrt.
  • Verbindungen, die von Dritten gesteuert werden und zeitlich begrenzt sind.
  • Kulturelle Praktiken, die zu bestimmten Zeiten oder Tagen einen eingeschränkten oder keinen Internetzugang erfordern.

Angesichts dessen ist es klar, dass wir bei der Entwicklung und Erstellung von Anwendungen autonome Erfahrungen berücksichtigen müssen.

EDISON Software - Webentwicklung
Dieser Artikel wurde mit Unterstützung von EDISON Software übersetzt, einem Unternehmen, das hervorragende Aufträge aus Südchina ausführt und auch Webanwendungen und Websites entwickelt .
Ich hatte kürzlich die Möglichkeit, einer vorhandenen Anwendung mithilfe von Servicemitarbeitern, Cache-Speicher und IndexedDB Autonomie hinzuzufügen. Die technische Arbeit, die erforderlich ist, damit die Anwendung offline funktioniert, wurde auf vier separate Aufgaben reduziert, auf die ich in diesem Beitrag eingehen werde.

Service-Arbeiter


Anwendungen, die für die Offline-Verwendung erstellt wurden, sollten nicht stark netzwerkabhängig sein. Konzeptionell ist dies nur möglich, wenn im Fehlerfall Sicherungsoptionen vorhanden sind.

Wenn die Webanwendung nicht geladen werden kann, müssen wir die Ressourcen für den Browser irgendwo (HTML / CSS / JavaScript) verwenden. Woher kommen diese Ressourcen, wenn nicht von einer Netzwerkanforderung? Wie wäre es mit einem Cache. Die meisten Menschen würden zustimmen, dass es besser ist, eine möglicherweise veraltete Benutzeroberfläche bereitzustellen als eine leere Seite.

Der Browser fragt ständig Daten ab. Der Daten-Caching-Dienst als Fallback erfordert weiterhin, dass wir Browseranforderungen abfangen und Caching-Regeln schreiben. Hier kommen Servicemitarbeiter ins Spiel - betrachten Sie sie als Vermittler.

Bild

Service Worker ist nur eine JavaScript-Datei, in der wir Ereignisse abonnieren und unsere eigenen Regeln für das Zwischenspeichern und Behandeln von Netzwerkfehlern schreiben können.
Fangen wir an.

Bitte beachten Sie: unsere Demo-Anwendung

In diesem Beitrag werden wir der Demo-Anwendung eigenständige Funktionen hinzufügen. Die Demo-Anwendung ist eine einfache Seite zum Aufnehmen / Ausleihen von Büchern in der Bibliothek. Der Fortschritt wird als eine Reihe von GIFs und die Verwendung von Offline-Chrome DevTools-Simulationen dargestellt.

Hier ist der Ausgangszustand:

Bild

Aufgabe 1 - Zwischenspeichern statischer Ressourcen


Statische Ressourcen sind Ressourcen, die sich nicht häufig ändern. HTML, CSS, JavaScript und Bilder können in diese Kategorie fallen. Der Browser versucht, statische Ressourcen mithilfe von Anforderungen zu laden, die vom Servicemitarbeiter abgefangen werden können.

Beginnen wir mit der Registrierung unseres Servicemitarbeiters.

if ('serviceWorker' in navigator) { window.addEventListener('load', function() { navigator.serviceWorker.register('/sw.js'); }); } 

Servicemitarbeiter sind Web-Worker unter der Haube und müssen daher aus einer separaten JavaScript-Datei importiert werden. Die Registrierung erfolgt nach dem Laden der Site mit der register .
Nachdem wir einen Servicemitarbeiter geladen haben, lassen Sie uns unsere statischen Ressourcen zwischenspeichern.

 var CACHE_NAME = 'my-offline-cache'; var urlsToCache = [ '/', '/static/css/main.c9699bb9.css', '/static/js/main.99348925.js' ]; self.addEventListener('install', function(event) { event.waitUntil( caches.open(CACHE_NAME) .then(function(cache) { return cache.addAll(urlsToCache); }) ); }); 

Da wir die URLs statischer Ressourcen steuern, können wir sie unmittelbar nach der Initialisierung des Servicemitarbeiters mithilfe von Cache Storage .

Bild

Nachdem unser Cache nun mit den zuletzt angeforderten statischen Ressourcen gefüllt ist, laden wir diese Ressourcen im Falle eines Anforderungsfehlers aus dem Cache.

 self.addEventListener('fetch', function(event) { event.respondWith( fetch(event.request).catch(function() { caches.match(event.request).then(function(response) { return response; } ); ); }); 

Das fetch wird jedes Mal ausgelöst, wenn der Browser eine Anforderung stellt. Unser neuer fetch verfügt jetzt über eine zusätzliche Logik für die Rückgabe zwischengespeicherter Antworten bei Netzwerkausfällen.

Demo Nummer 1


Bild

Unsere Demo-Anwendung kann jetzt statische Ressourcen offline bereitstellen! Aber wo sind unsere Daten?

Aufgabe 2 - Zwischenspeichern dynamischer Ressourcen


Single-Page-Anwendungen (SPA) fordern normalerweise nach dem ersten Laden der Seite schrittweise Daten an, und unsere Demo-Anwendung ist keine Ausnahme - die Liste der Bücher wird nicht sofort geladen. Diese Daten stammen normalerweise aus XHR-Anforderungen, die Antworten zurückgeben, die sich häufig ändern, um einen neuen Status für die Anwendung bereitzustellen - daher sind sie dynamisch.

Das Zwischenspeichern dynamischer Ressourcen ist dem Zwischenspeichern statischer Ressourcen sehr ähnlich. Der Hauptunterschied besteht darin, dass der Cache häufiger aktualisiert werden muss. Das Generieren einer vollständigen Liste aller möglichen dynamischen XHR-Anforderungen ist ebenfalls recht schwierig. Daher werden sie bei ihrem Eintreffen zwischengespeichert und haben keine vordefinierte Liste, wie wir es für statische Ressourcen getan haben.

Schauen Sie sich unseren fetch Handler an:

 self.addEventListener('fetch', function(event) { event.respondWith( fetch(event.request).catch(function() { caches.match(event.request).then(function(response) { return response; } ); ); }); 

Wir können diese Implementierung anpassen, indem wir Code hinzufügen, der erfolgreiche Anforderungen und Antworten zwischenspeichert. Dies stellt sicher, dass wir ständig neue Anforderungen zu unserem Cache hinzufügen und die zwischengespeicherten Daten ständig aktualisieren.

 self.addEventListener('fetch', function(event) { event.respondWith( fetch(event.request) .then(function(response) { caches.open(CACHE_NAME).then(function(cache) { cache.put(event.request, response); }); }) .catch(function() { caches.match(event.request).then(function(response) { return response; } ); ); }); 

Unser Cache Storage derzeit mehrere Einträge.

Bild

Demo Nummer 2


Bild

Unsere Demo sieht jetzt beim Booten gleich aus, unabhängig von unserem Netzwerkstatus!

Großartig. Versuchen wir nun, unsere Anwendung zu verwenden.

Bild

Leider gibt es überall Fehlermeldungen. Es scheint, dass alle unsere Interaktionen mit der Schnittstelle nicht funktionieren. Ich kann das Buch nicht auswählen oder übergeben! Was muss behoben werden?

Aufgabe 3 - Erstellen einer optimistischen Benutzeroberfläche


Derzeit besteht das Problem bei unserer Anwendung darin, dass unsere Datenerfassungslogik immer noch stark von Netzwerkantworten abhängt. Die Aktion zum Ein- und Auschecken sendet eine Anforderung an den Server und erwartet eine erfolgreiche Antwort. Dies ist gut für die Datenkonsistenz, aber schlecht für unsere Standalone-Erfahrung.

Damit diese Interaktionen offline funktionieren, müssen wir unsere Anwendung optimistischer gestalten . Optimistische Interaktionen erfordern keine Antwort vom Server und zeigen bereitwillig eine aktualisierte Ansicht der Daten an. Der übliche optimistische Vorgang in den meisten Webanwendungen ist das delete Geben Sie dem Benutzer sofort Feedback, wenn wir bereits über alle erforderlichen Informationen verfügen.

Das Trennen unserer Anwendung vom Netzwerk mithilfe eines optimistischen Ansatzes ist relativ einfach zu implementieren.

 case CHECK_OUT_SUCCESS: case CHECK_OUT_FAILURE: list = [...state.list]; list.push(action.payload); return { ...state, list, }; case CHECK_IN_SUCCESS: case CHECK_IN_FAILURE; list = [...state.list]; for (let i = 0; i < list.length; i++) { if (list[i].id === action.payload.id) { list.splice(i, 1, action.payload); } } return { ...state, list, }; 

Der Schlüssel besteht darin, Benutzeraktionen auf dieselbe Weise zu behandeln - unabhängig davon, ob die Netzwerkanforderung erfolgreich ist oder nicht. Das obige Code-Snippet stammt aus dem Redux-Reduzierer unserer Anwendung. SUCCESS und FAILURE abhängig von der Verfügbarkeit des Netzwerks gestartet. Unabhängig davon, wie die Netzwerkanforderung abgeschlossen ist, werden wir unsere Bücherliste aktualisieren.

Demo Nummer 3


Bild

Die Benutzerinteraktion erfolgt jetzt online (nicht wörtlich). Die Schaltflächen "Einchecken" und "Auschecken" aktualisieren die Benutzeroberfläche entsprechend, obwohl die roten Meldungen der Konsole anzeigen, dass Netzwerkanforderungen nicht ausgeführt werden.

Gut! Es gibt nur ein kleines Problem mit optimistischem Offline-Rendering ...

Verlieren wir nicht unser Wechselgeld?

Bild

Aufgabe 4 - Benutzeraktionen für die Synchronisierung in die Warteschlange stellen


Wir müssen die Aktionen verfolgen, die der Benutzer ausgeführt hat, als er offline war, damit wir sie mit unserem Server synchronisieren können, wenn der Benutzer zum Netzwerk zurückkehrt. Es gibt verschiedene Speichermechanismen im Browser, die als Warteschlange für Aktionen fungieren können, und wir werden IndexedDB verwenden. IndexedDB bietet einige Dinge, die Sie von LocalStorage nicht erhalten:

  • Asynchrone nicht blockierende Operationen
  • Deutlich höhere Lagergrenzen
  • Transaktionsmanagement

Schauen Sie sich unseren alten Reduziercode an:

 case CHECK_OUT_SUCCESS: case CHECK_OUT_FAILURE: list = [...state.list]; list.push(action.payload); return { ...state, list, }; case CHECK_IN_SUCCESS: case CHECK_IN_FAILURE; list = [...state.list]; for (let i = 0; i < list.length; i++) { if (list[i].id === action.payload.id) { list.splice(i, 1, action.payload); } } return { ...state, list, }; 

Lassen Sie es uns ändern, um die Check-In- und Check-Out-Ereignisse während des FAILURE -Ereignisses in IndexedDB zu speichern.

 case CHECK_OUT_FAILURE: list = [...state.list]; list.push(action.payload); addToDB(action); // QUEUE IT UP return { ...state, list, }; case CHECK_IN_FAILURE; list = [...state.list]; for (let i = 0; i < list.length; i++) { if (list[i].id === action.payload.id) { list.splice(i, 1, action.payload); addToDB(action); // QUEUE IT UP } } return { ...state, list, }; 

Hier ist die Implementierung des Erstellens von IndexedDB zusammen mit dem addToDB- addToDB .

 let db = indexedDB.open('actions', 1); db.onupgradeneeded = function(event) { let db = event.target.result; db.createObjectStore('requests', { autoIncrement: true }); }; const addToDB = action => { var db = indexedDB.open('actions', 1); db.onsuccess = function(event) { var db = event.target.result; var objStore = db .transaction(['requests'], 'readwrite') .objectStore('requests'); objStore.add(action); }; }; 

Nachdem alle unsere Offline-Benutzeraktionen im Speicher des Browsers gespeichert sind, können wir den Ereignis-Listener des online Browsers verwenden, um Daten zu synchronisieren, wenn die Verbindung wiederhergestellt wird.

 window.addEventListener('online', () => { const db = indexedDB.open('actions', 1); db.onsuccess = function(event) { let db = event.target.result; let objStore = db .transaction(['requests'], 'readwrite') .objectStore('requests'); objStore.getAll().onsuccess = function(event) { let requests = event.target.result; for (let request of requests) { send(request); // sync with the server } }; }; }); 

In dieser Phase können wir die Warteschlange von allen Anforderungen löschen, die wir erfolgreich an den Server gesendet haben.

Demo Nummer 4


Die endgültige Demo sieht etwas komplizierter aus. Rechts im dunklen Terminalfenster werden alle API-Aktivitäten protokolliert. Die Demo beinhaltet das Offline-Gehen, das Auswählen mehrerer Bücher und das Online-Zurückkehren.

Bild

Es ist klar, dass offline gestellte Anforderungen sofort in die Warteschlange gestellt und gesendet wurden, wenn der Benutzer online zurückkehrt.

Dieser "Spiel" -Ansatz ist ein bisschen naiv - zum Beispiel müssen wir wahrscheinlich nicht zwei Anfragen stellen, wenn wir dasselbe Buch annehmen und zurückgeben. Es funktioniert auch nicht, wenn mehrere Personen dieselbe Anwendung verwenden.

Das ist alles


Gehen Sie raus und machen Sie Ihre Webanwendungen offline! Dieser Beitrag zeigt einige der vielen Dinge, die Sie tun können, um Ihren Anwendungen eigenständige Funktionen hinzuzufügen, und ist definitiv nicht vollständig.
Weitere Informationen finden Sie unter Google Web Fundamentals . Schauen Sie sich diesen Vortrag an , um eine weitere Offline-Implementierung zu sehen.


Lesen Sie auch den Blog
EDISON Unternehmen:


20 Bibliotheken für
spektakuläre iOS-Anwendung

Source: https://habr.com/ru/post/de474374/


All Articles