Broker JavaScript hors ligne

Dans mon projet, j'avais besoin d'une fonctionnalitĂ© qui me permettrait de ne pas perdre les donnĂ©es saisies en cas de panne de connexion Internet et je suis arrivĂ© avec un "Broker" trĂšs simple qui me permettrait de ne pas perdre de donnĂ©es lorsque la connexion Ă©tait perdue, mais de les envoyer lorsque la connexion est rĂ©tablie. «Courtier» n'est peut-ĂȘtre pas un bon nom pour lui, mais ne jugez pas strictement. Je veux partager, peut-ĂȘtre que quelqu'un sera utile.

À propos du projet


Mon projet est conçu pour rendre compte des dĂ©penses et des revenus ou comme une simple version de comptabilitĂ© Ă  domicile. Il a Ă©tĂ© créé en tant qu'application Web progressive, de sorte qu'il est pratique de l'utiliser sur des appareils mobiles, ainsi que pour ouvrir les capacitĂ©s des notifications Push, accĂ©der Ă  la camĂ©ra pour lire les codes-barres, etc. Il existe une application mobile similaire appelĂ©e ZenMoney, mais je voulais quelque chose de moi-mĂȘme et Ă  ma maniĂšre.

L'émergence des idées


J'essaie de garder une trace claire des dĂ©penses et des revenus, mais comme on oublie souvent d'ajouter les postes nĂ©cessaires, en particulier ceux liĂ©s Ă  la trĂ©sorerie, je dois le faire presque immĂ©diatement lorsque la "transaction" s'est produite. Parfois, je saisissais des donnĂ©es dans les transports publics, comme le mĂ©tro, oĂč les pertes de connexion se produisent souvent, malgrĂ© le rĂ©seau Wi-Fi trĂšs rĂ©pandu. C'Ă©tait dommage que tout se fige et que rien ne se passe, puis les donnĂ©es ont simplement Ă©tĂ© perdues.

L'idĂ©e est venue de l'utilisation d'un courtier de file d'attente tel que RabbitMQ. Bien sĂ»r, j'ai une solution plus simple et moins fonctionnelle, mais il y a quelque chose de similaire Ă  ses principes. Je pensais que vous pouvez tout enregistrer, par exemple, dans Cache ou LocalStorage en tant qu'objet avec une file d'attente de demandes «non satisfaites», et lorsqu'une connexion apparaĂźt, vous pouvez les exĂ©cuter en toute sĂ©curitĂ©. Bien entendu, elles ne sont pas exĂ©cutĂ©es par ordre de prioritĂ©, ce qui, grĂące au traitement asynchrone des requĂȘtes en langage JS, est encore meilleur, Ă©tant donnĂ© que vous n'avez qu'un seul «abonné». J'ai rencontrĂ© quelques difficultĂ©s, peut-ĂȘtre mĂȘme la mise en Ɠuvre de tout cela semblera un peu tordue, mais c'est une solution qui fonctionne. Bien sĂ»r, il peut ĂȘtre amĂ©liorĂ©, mais pour l'instant je vais dĂ©crire l'option existante "brute" mais fonctionnelle.

Pour commencer


La premiĂšre chose Ă  laquelle j'ai pensĂ© Ă©tait de savoir oĂč stocker les donnĂ©es en l'absence de connexion?! Le service woker imposĂ© par PWA fonctionne bien avec le cache, mais est-il judicieux d'utiliser le cache?! Une question difficile, je ne vais pas y entrer. En gĂ©nĂ©ral, j'ai dĂ©cidĂ© que LocalStorage est mieux pour moi. Étant donnĂ© que LocalStorage stocke des valeurs de type clĂ©: valeur, l'objet a dĂ» ĂȘtre ajoutĂ© en tant que chaĂźne Json. Dans mon projet de dĂ©veloppement externe, j'ai ajoutĂ© un rĂ©pertoire appelĂ© QueueBroker au dossier de classe

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


Mon projet est réalisé dans la pile Laravel + VueJs, donc une certaine dépendance de la structure du fichier est requise. Je ne sais pas comment dans de tels cas il est correct d'appeler vos propres répertoires pour les classes, donc je l'ai fait.

Le fichier d'index a Ă©tĂ© créé pour simplement connecter des modules Ă  partir de la bibliothĂšque imbriquĂ©e. Ce n'est peut-ĂȘtre pas une solution trĂšs Ă©lĂ©gante, mais je voulais faire en sorte que si je change soudainement d'avis d'utiliser LocalStorage, j'Ă©crirai une autre classe pour Storage avec les mĂȘmes mĂ©thodes, la passerai au constructeur du courtier et utiliserai l'autre stockage sans rien changer.

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


Cette méthode vous permet de connecter uniquement les bibliothÚques dont j'ai besoin dans mes scripts, par exemple, si j'ai besoin des deux:

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

Pour faciliter la modification de la classe de stockage, j'ai créé une sorte de constructeur pour la classe Broker, dans lequel l'objet Storage pourrait ĂȘtre passĂ© en argument, l'essentiel est qu'il possĂšde les fonctions nĂ©cessaires. Je sais que sur ES6 je pouvais Ă©crire classe et constructeur, mais j'ai dĂ©cidĂ© de le faire Ă  l'ancienne - prototype. Je vais Ă©crire des commentaires directement par 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; 


Ensuite, vous avez besoin de l'objet de stockage lui-mĂȘme, qui enregistrera et rĂ©cupĂ©rera tout avec succĂšs du stockage

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; 


Lorsque tout ce qui prĂ©cĂšde sera prĂȘt, cela peut ĂȘtre utilisĂ© Ă  votre discrĂ©tion, je l'utilise comme ceci:

Utiliser lors de l'enregistrement
 //   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); }); }, 


Utiliser lors de la reconnexion
 //   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


C'est difficile pour moi de parler du code, j'ai donc essayé de fournir le code dans les exemples aussi détaillé que possible avec des commentaires détaillés. Si vous avez des idées pour améliorer cette solution ou améliorer cet article, je serai heureux de les voir dans les commentaires. J'ai pris des exemples de mon propre projet sur Vue, je l'explique afin de bien comprendre pourquoi mes méthodes sont ainsi appelées et pourquoi j'y accÚde par ce biais. Je ne le fais pas dans cet article spécifiquement sur Vue, donc je ne fournis pas d'autre code de composant, je le laisse pour la compréhension.

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


All Articles