Offline JavaScript Broker

In meinem Projekt benötigte ich Funktionen, mit denen ich die eingegebenen Daten bei einem Ausfall der Internetverbindung nicht verlieren konnte, und ich entwickelte einen sehr einfachen „Broker“, mit dem ich keine Daten verlieren konnte, wenn die Verbindung unterbrochen wurde, sondern sie senden konnte, wenn die Verbindung wieder hergestellt wurde. Vielleicht ist "Broker" kein guter Name für ihn, aber urteilen Sie nicht streng. Ich möchte teilen, vielleicht wird jemand nützlich sein.

Über das Projekt


Mein Projekt ist so konzipiert, dass Ausgaben und Einnahmen berücksichtigt werden, oder als einfache Version der Hausbuchhaltung. Es wurde als progressive Webanwendung erstellt, damit es bequem auf Mobilgeräten verwendet werden kann und die Funktionen von Push-Benachrichtigungen, der Zugriff auf die Kamera zum Lesen von Barcodes und dergleichen geöffnet werden können. Es gibt eine ähnliche mobile Anwendung namens ZenMoney, aber ich wollte etwas Eigenes und auf meine eigene Weise.

Die Entstehung von Ideen


Ich versuche, Ausgaben und Einnahmen klar zu erfassen, aber da oft vergessen wird, die erforderlichen Positionen hinzuzufügen, insbesondere die im Zusammenhang mit Bargeld, muss ich dies fast sofort tun, als die "Transaktion" stattfand. Manchmal habe ich Daten in öffentlichen Verkehrsmitteln wie der U-Bahn eingegeben, wo trotz des weit verbreiteten Wi-Fi-Netzwerks häufig Verbindungsverluste auftreten. Es war eine Schande, dass alles einfriert und nichts passiert, und dann gingen die Daten einfach verloren.

Die Idee kam von einem Warteschlangenbroker wie RabbitMQ. Natürlich habe ich eine einfachere und nicht so funktionale Lösung, aber es gibt etwas Ähnliches wie seine Prinzipien. Ich dachte, dass Sie alles, zum Beispiel in Cache oder LocalStorage, als Objekt mit einer Warteschlange von "unbefriedigten" Anforderungen speichern können, und wenn eine Verbindung angezeigt wird, können Sie diese sicher ausführen. Natürlich werden sie nicht in der Reihenfolge ihrer Priorität ausgeführt, was dank der asynchronen Verarbeitung von Anforderungen in der JS-Sprache sogar noch besser ist, da Sie nur einen „Abonnenten“ haben. Ich bin auf einige Schwierigkeiten gestoßen, vielleicht scheint sogar die Implementierung ein wenig schief zu sein, aber dies ist eine funktionierende Lösung. Natürlich kann es verbessert werden, aber im Moment werde ich die vorhandene "rohe", aber funktionierende Option beschreiben.

Erste Schritte


Das erste, worüber ich nachdachte, war, wo Daten ohne Verbindung gespeichert werden können ?! Der mir von PWA auferlegte Service-Woker funktioniert gut mit dem Cache, aber ist es ratsam, den Cache zu verwenden ?! Eine schwierige Frage, auf die ich nicht eingehen werde. Im Allgemeinen habe ich entschieden, dass LocalStorage für mich besser ist. Da LocalStorage Werte vom Typ key: value speichert, musste das Objekt als Json-Zeichenfolge hinzugefügt werden. In meinem Projekt für die externe Entwicklung habe ich dem Klassenordner ein Verzeichnis namens QueueBroker hinzugefügt

Dateistruktur
/**----**/
├── app.js
├── bootstrap.js
├── classes
│ └── QueueBroker
│ ├── index.js
│ └── Library
│ ├── Broker.js
│ └── Storage.js
├── components
/**----**/


Mein Projekt wird im Laravel + VueJs-Stack erstellt, daher ist eine gewisse Abhängigkeit von der Dateistruktur erforderlich. Ich weiß nicht, wie es in solchen Fällen richtig ist, eigene Verzeichnisse für Klassen aufzurufen, also habe ich es getan.

Die Indexdatei wurde erstellt, um einfach Module aus der verschachtelten Bibliothek einzufügen. Es ist vielleicht keine sehr elegante Lösung, aber ich wollte es so gestalten, dass ich, wenn ich plötzlich meine Meinung über die Verwendung von LocalStorage ändere, eine andere Klasse für Storage mit denselben Methoden schreibe, sie an den Broker-Konstruktor übergebe und den anderen Speicher verwende, ohne etwas zu ändern.

index.js
 const Broker = require('./Library/Broker'); const Storage = require('./Library/Storage'); module.exports.Broker = Broker; module.exports.Storage = Storage; 


Mit dieser Methode können Sie nur die Bibliotheken verbinden, die ich in meinen Skripten benötige, wenn ich beispielsweise beide benötige:

 import {Storage, Broker} from '../../classes/QueueBroker/index'; 

Um mir das Ändern der Speicherklasse zu erleichtern, habe ich eine Art Konstruktor für die Broker-Klasse erstellt, in den das Speicherobjekt als Argument übergeben werden kann. Hauptsache, es verfügt über die erforderlichen Funktionen. Ich weiß, dass ich auf ES6 Klasse und Konstruktor schreiben konnte, entschied mich aber dafür, es auf die altmodische Art und Weise zu tun - Prototyp. Ich schreibe Kommentare direkt per Code:

Broker.js
 const axios = require('axios'); //  axios /*     .    ,            front-end  */ function Broker(storage, prefix='storageKey') { this.storage = storage; this.prefix = prefix; /*     ,      .   storage   add     json */ if(this.storage.get('broker') === null) { this.broker = {}; this.storage.add('broker', this.broker) } else { //  , Storage    Json            this.broker = this.storage.getObject('broker'); } }; // ,           Broker.prototype.queueCount = function () { return Object.keys(this.broker).length; }; //  ""    Storage,    Broker.prototype.saveToStorage = function (method, url, data) { let key = this.prefix + '_' + (Object.keys(this.broker).length + 1); this.broker[key] = {method, url, data}; //            broker,        this.storage.add('broker', this.broker); }; // ,    ,    Broker.prototype.run = function () { for (let key in this.broker) { this.sendToServer(this.broker[key], key) } } /*    .        ,     method, url  data,        ,    ,    */ Broker.prototype.sendToServer = function (object, brokerKey) { axios({ method: object.method, url: object.url, data: object.data, }) .then(response => { if(response.data.status == 200) { //   ,    delete this.broker[brokerKey]; //  this.storage.add('broker', this.broker); } else { //   ;-) console.log(response.data) } }) .catch(error => { /*           ,       */ }); }; //   export module.exports = Broker; 


Als Nächstes benötigen Sie das Speicherobjekt selbst, mit dem alles erfolgreich gespeichert und aus dem Speicher abgerufen werden kann

Storage.js
 //  debug-   function Storage(debug) { if(debug === true) { this.debugMode = true; } this.storage = window.localStorage; }; // ,     Json      Storage.prototype.addObjectToStorage = function (key, object) { this.storage.setItem(key, JSON.stringify(object)); }; //    (,   ) Storage.prototype.addStringToStorage = function (key, value) { this.storage.setItem(key, value); }; //    Storage.prototype.get = function (key) { return this.storage.getItem(key); }; //    Json ,       Storage.prototype.getObject = function (key) { try { return JSON.parse(this.storage.getItem(key)); } catch (e) { this._debug(e); this._debug(key + ' = ' + this.storage.getItem(key)); return false; } }; /* ,     ,  ,        ,   Json      */ Storage.prototype.add = function (key, value) { try { if(typeof value === 'object') { this.addObjectToStorage(key, value); } else if (typeof value === 'string' || typeof value === 'number') { this.addStringToStorage(key, value); } else { //    this._debug('2 parameter does not belong to a known type') } return this.storage; } catch (e) { //    ,    ,    if (e === QUOTA_EXCEEDED_ERR) { this._debug('LocalStorage is exceeded the free space limit') } else { this._debug(e) } } }; //  Storage.prototype.clear = function () { try { this.storage.clear(); return true; } catch (e) { this._debug(e) return false; } }; //    Storage.prototype.delete = function(key) { try { this.storage.removeItem(key); return true; } catch (e) { this._debug(e) return false; } }; // ,      Storage.prototype._debug = function(error) { if(this.debugMode) { console.error(error); } return null; }; //   module.exports = Storage; 


Wenn alle oben genannten Punkte fertig sind, kann dies nach Ihrem Ermessen verwendet werden. Ich verwende es folgendermaßen:

Zum Speichern verwenden
 //   Vue (methods) /*----*/ //      Storage   sendBroker(method, url, data) { let storage = new Storage(true); let broker = new Broker(storage, 'fundsControl'); broker.saveToStorage(method, url, data); }, //     fundsSave() { let url = '/pa/funds'; let method = ''; if(this.fundsFormType === 'create') { method = 'post'; } else if(this.fundsFormType === 'update') { method = 'put'; } else if(this.fundsFormType === 'delete') { method = 'delete'; } this.$store.commit('setPreloader', true); axios({ method: method, url: url, data: this.fundsFormData, }) .then(response=> { if(response.data.status == 200) { this.fundsFormShow = false; this.getFunds(); this.$store.commit('setPreloader', false); } else { this.$store.commit('AlertError', '    '); } }) //        .catch(error => { this.$store.commit('setAlert', { type: 'warning', status: true, message: '   . ,        ,   ' } ); this.fundsFormShow = false; this.$store.commit('setPreloader', false); //   ""  this.sendBroker(method, url, this.fundsFormData); console.error(error); }); }, 


Zum erneuten Anschließen verwenden
 //   Vue /*--*/ methods: { /*----*/ /*   ,    ,   ,      ,      */ brokerSendRun() { let storage = new Storage(true); let broker = new Broker(storage, 'fundsControl'); //,   -   if(broker.queueCount() > 0) { // ,    broker.run(); //   ,   this.$store.commit('setAlert', {type: 'info', status: true, message: '      -  , ,      '}); } } } /*---*/ /*     , ,   ,          ,     ,        */ mounted() { this.brokerSendRun(); } /*---*/ 


PS


Es fällt mir schwer, über den Code zu sprechen, daher habe ich versucht, den Code in den Beispielen so detailliert wie möglich mit detaillierten Kommentaren zu versehen. Wenn Sie Ideen zur Verbesserung dieser Lösung oder dieses Artikels haben, freue ich mich, diese in den Kommentaren zu sehen. Ich habe Beispiele aus meinem eigenen Projekt auf Vue genommen, ich erkläre dies, um zu verdeutlichen, warum meine Methoden so genannt werden und warum ich dadurch auf sie zugreife. Ich mache es in diesem Artikel nicht speziell für Vue, daher gebe ich keinen anderen Komponentencode an. Ich überlasse es dem Verständnis.

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


All Articles