Corretor JavaScript offline

No meu projeto, eu precisava de uma funcionalidade que me permitisse não perder os dados inseridos no caso de uma falha na conexão com a Internet e criei um "Broker" muito simples que me permitiria não perder dados quando a conexão foi perdida, mas enviá-los quando a conexão for restaurada novamente. Talvez "Broker" não seja um bom nome para ele, mas não julgue rigorosamente. Eu quero compartilhar, talvez alguém seja útil.

Sobre o projeto


Meu projeto foi projetado para contabilizar despesas e receitas ou como uma versão simples da contabilidade doméstica. Foi criado como um aplicativo da web progressivo, para que seja conveniente usá-lo em dispositivos móveis, bem como para abrir os recursos de notificações push, acesso à câmera para leitura de códigos de barras e similares. Existe um aplicativo móvel semelhante chamado ZenMoney, mas eu queria algo próprio e do meu jeito.

O surgimento de idéias


Tento manter um registro claro de despesas e receitas, mas como muitas vezes esquecemos de acrescentar as posições necessárias, principalmente em relação ao caixa, tenho que fazer isso quase imediatamente quando a "transação" ocorreu. Às vezes, eu inseria dados no transporte público, como o metrô, onde as perdas de conexão ocorrem com frequência, apesar da ampla rede Wi-Fi. Foi uma pena que tudo congele e nada aconteça, e então os dados foram simplesmente perdidos.

A ideia surgiu do uso de um broker de filas como o RabbitMQ. É claro que tenho uma solução mais simples e não tão funcional, mas há algo semelhante aos seus princípios. Pensei que você pudesse salvar tudo, por exemplo, no Cache ou LocalStorage como um objeto com uma fila de solicitações "insatisfeitas" e, quando uma conexão aparecer, você poderá executá-las com segurança. Obviamente, eles não são executados em ordem de prioridade, o que, graças ao processamento assíncrono de solicitações na linguagem JS, é ainda melhor, pois você tem apenas um "assinante". Eu tive algumas dificuldades, talvez até a implementação de tudo isso pareça um pouco torta, mas essa é uma solução funcional. Obviamente, isso pode ser melhorado, mas por enquanto vou descrever a opção "bruta", mas funcional, existente.

Introdução


A primeira coisa que pensei foi onde armazenar dados na ausência de uma conexão ?! O woker de serviço imposto a mim pelo PWA funciona bem com o cache, mas é aconselhável usá-lo ?! Uma pergunta difícil, não vou entrar nela. Em geral, decidi que o LocalStorage é melhor para mim. Como LocalStorage armazena valores do tipo key: value, o objeto teve que ser adicionado como uma sequência Json. No meu projeto para desenvolvimento externo, adicionei um diretório chamado QueueBroker à pasta da classe

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


Meu projeto é feito na pilha Laravel + VueJs, portanto, é necessária uma certa dependência da estrutura do arquivo. Não sei como, nesses casos, é correto chamar seus próprios diretórios para classes, então o fiz.

O arquivo de índice foi criado para simplesmente conectar módulos da Biblioteca aninhada. Pode não ser uma solução muito elegante, mas eu queria fazê-lo para que, se de repente eu mude de idéia sobre o uso do LocalStorage, escreva outra classe para Storage com os mesmos métodos, passe-a para o construtor do broker e use o outro armazenamento sem alterar nada.

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


Este método permite conectar apenas as bibliotecas que eu preciso nos meus scripts, por exemplo, se eu precisar de ambas:

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

Para facilitar a alteração da classe de armazenamento, criei um tipo de construtor para a classe Broker, na qual o objeto Storage pode ser passado como argumento, o principal é que ele possui as funções necessárias. Eu sei que no ES6 eu poderia escrever classe e construtor, mas decidi fazê-lo da maneira antiga - protótipo. Escreverei comentários diretamente por código:

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; 


Em seguida, você precisa do próprio objeto Storage, que salvará e recuperará com êxito tudo do armazenamento

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; 


Quando todas as opções acima estiverem prontas, isso pode ser usado a seu critério, eu uso assim:

Use em Salvar
 //   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); }); }, 


Use ao reconectar
 //   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


É difícil falar sobre o código, então tentei fornecer o código nos exemplos o mais detalhado possível com comentários detalhados. Se você tiver idéias para melhorar esta solução ou melhorar este artigo, ficarei feliz em vê-las nos comentários. Peguei exemplos de meu próprio projeto no Vue, explico isso para deixar claro por que meus métodos são assim chamados e por que eu os acesso através disso . Não o faço neste artigo especificamente no Vue; portanto, não forneço outro código de componente, deixo-o para compreensão.

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


All Articles