tcxsproject.com.br livros hacker gorpo orko... · erros. aprenderemos a automatizar esse processo....

525

Upload: others

Post on 28-Sep-2020

15 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria
Page 2: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

ImpressoePDF:978-85-94188-00-7

EPUB:978-85-94188-01-4

MOBI:978-85-94188-02-1

Você pode discutir sobre este livro no Fórum da Casa doCódigo:http://forum.casadocodigo.com.br/.

Casovocêdesejesubmeteralgumaerrataousugestão,acessehttp://erratas.casadocodigo.com.br.

ISBN

Page 3: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Dedico este livro à minha primeira filha, pois, sem ela, eleprovavelmente não teria existido. A motivação de deixarmaterializado nesta obra boa parte do conhecimento que tenhoveio da vontade de torná-lo facilmente acessível para ela, casoalgum dia deseje trilhar o caminho do pai. Boa parte dasexplicações que vocês encontrarão aqui foram construídasenquantovigiavaseusono,pensandoemumadidáticadepaiparafilha.

Porfim,otítulo"CangaceiroJavaScript"nãofoiescolhidoporacaso. Foi uma tentativa de homenagear parte da nossa cultura,seja na história fictícia escrita pormim que inicia cada parte dolivro,comotambémnascitaçõesdeoutrasobrasliterárias.

AGRADECIMENTOS

Page 4: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

OSertão,paraosforasteiros,pareceumaterradepoucavida,devegetação rasteira e compoucosanimais àvista,diferentedasgrandes florestas tropicais daZonadaMata, cheias de barulho evida.Masosertanejonãoseengana,sejaplantandonaterraduraouusandoasfolhasdojuazeiro,eletransformaoambientedifíciletira o seu sustento desse lugar que parece impossível de seaproveitar.

O homem e a mulher do Sertão são frutos desse ambienteinóspito, e precisam lidar com a dificuldade de onde estãodiariamente e se acostumaram a seguir em frente independentedosproblemasquesurgem.Masnãopensequesãopessoastristeseduras. Uma sanfona, um triângulo, uma rabeca e uma zabumbaparainspiraradança,juntodeumtachodecanjica,queijocoalhoe trouxinhasdepamonhaenroladasna folhadomilhoevocêvaiverfestapradotônenhumbotardefeito.

Nestelivro,vocêvaicorrerpeloSertãodoJavaScript,seguindoo cangaceiro FlávioAlmeida (@flaviohalmeida) para aprenderosdetalhesesegredosdestalinguagem,construindoumaaplicaçãodeverdade de ponta a ponta. Com o seu editor de código favoritocomo sua confiável peixeira, falando Vixe Maria sempre queencontrarumbugedançandoaqueleforróagarradinhoquandoocódigo vai pra produção, atravessar essa terra inóspita que édesenvolversoftwareseráumagrandeaventura.QuePadimCiçoeFreiDamiãoalumeiemoseucaminho!

PREFÁCIOPORMAURÍCIOLINHARES

Page 5: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Figura1:FlávioAlmeida

Flávio Almeida é desenvolvedor e instrutor na Caelum.Também é instrutor na Alura, contribuindo com mais de 20treinamentosparaestaplataformadeensinoonline.AutordolivroMean: Full stack JavaScript para aplicações web com MongoDB,Express,AngulareNode,possuimaisde15anosdeexperiêncianaáreadedesenvolvimento.BacharelemInformáticacomMBAemGestão de Negócios em TI, tem Psicologia como segundagraduação eprocura aplicaroque aprendeunodesenvolvimentodesoftwareenaeducação.

Atualmente, foca na plataforma Node.js e na linguagemJavaScript, tentando aproximar aindamais o front-end do back-end. Já palestrou e realizou workshops em grandes conferênciascomo QCON e MobileConf, e está sempre ávido por novoseventos.

SOBREOAUTOR

Page 6: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

"Mestre não é quem sempre ensina, mas quem de repenteaprende."-GrandeSertão:Veredas

Talveznenhumaoutra linguagemtenhaconseguido invadirocoletivo imagináriodosdesenvolvedorescomoJavaScript fez.Emsua história fabular em busca de identidade, foi a única queconseguiuseenraizarnosnavegadores,enosúltimosanospassouaempoderarservidoresdealtaperformanceatravésdaplataformaNode.js. Com tudo isso, tornou-se uma linguagem em que tododesenvolvedorprecisateralgumníveldeconhecimento.

Comacomplexidadecadavezmaiordosproblemas,soluçõesninjasnão sãomais suficientes; é preciso soluções cangaceiras.Énessecontextoqueestelivroseencaixa,ajudando-oatranscenderseuconhecimento,tornando-oumCangaceiroJavaScript.

Este livro destina-se àqueles que desejam aprimorar amanutenção e legibilidade de seus códigos aplicando osparadigmasdaOrientaçãoaObjetosedaProgramaçãoFuncional,inclusivepadrõesdeprojetos.Porfim,eleajudarátambémaquelesque desejam trabalhar com frameworks Single Page Application(SPA) conceituados do mercado, mas que carecem de umconhecimentomaisaprofundadodalinguagemJavaScript.

INTRODUÇÃO

Aquemsedestinaolivro

Page 7: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Procureiutilizarumalinguagemsimplesparatornarestelivroacessívelaumamploespectrodedesenvolvedores.Noentanto,énecessário que o leitor tenha algum conhecimento, mesmo quebásico, da linguagem JavaScript para que tenha um melhoraproveitamento.

Oprojetodestelivroseráumcadastrodenegociaçõesdebolsade valores. Ele será construído através do paradigma funcional eorientadoaobjetosaomesmotempo,utilizandoomelhordosdoismundos.

Figura1:Previewdaaplicação

Emumprimeiromomento,oescopodanossaaplicaçãopodeparecerbemreduzido,maséosuficientepara lançarmosmãodeumasériede recursosacumuladosna linguagematéa suaversãoECMAScript2017(ES8).

Paraqueoleitorpreparesuasexpectativassobreoqueestáporvir,segueumbreveresumodecadacapítulo:

Visãogeraldanossajornada

Page 8: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Capítulo 01: escreveremos um código rapidamente sem nospreocuparmoscomboaspráticasousuareutilização, sentindonapeleasconsequênciasdessaabordagem.Éapartirdelequetodaatrama do livro dará início, inclusive a motivação para umaestruturaaplicandoomodeloMVC.

Capítulo 02: materializar uma representação de algo domundo real em código é uma das tarefas do desenvolvedor.Veremoscomooparadigmaorientadoaobjetospodenosajudarnaconstruçãodeummodelo.

Capítulo03:doqueadiantaummodeloseousuárionãopodeinteragircomele?Criaremosumcontroller,aponteentreasaçõesdousuárioeomodelo.

Capítulo 04: lidar com data não é uma tarefa trivial emqualquer linguagem, mas se combinarmos recursos da próprialinguagemJavaScriptpodeseralgobemdivertido.

Capítulo05:nãoéapenasumanegociaçãoquepossuiregras,umalistadenegociaçãotambém.Criaremosmaisummodelocomsuasrespectivasregras.

Capítulo 06: do que adianta termos um modelo que émodificado pelo usuário se ele não consegue ver o resultado?Aprenderemosaligaromodelocomaview.

Capítulo07: criar ummodelo, modificá-lo com as ações dousuárioeapresentá-loéumatarefacorriqueira.Seráquepodemosisolaresseprocessoereutilizá-lo?Comcerteza!

Capítulo08: jogar a responsabilidade de atualizar a view nocolododesenvolvedortodavezqueomodelomudarestásujeitoa

Page 9: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

erros.Aprenderemosaautomatizaresseprocesso.

Capítulo 09: aplicaremos o padrão de projeto Proxy paraimplementarmos nossa própria solução de data binding, queconsiste na atualização da view toda vez que o modelo sofreralterações.

Capítulo 10: isolaremos a responsabilidade da criação deproxies em uma classe, aplicando o padrão de projeto Factory epermitindoqueelasejareutilizadapelanossaaplicação.

Capítulo11: aprenderemos a lidar com exceções, inclusive acriá-las.

Capítulo 12: trabalharemos com o velho conhecidoXMLHttpRequestparanosintegrarmoscomaAPIfornecidacomoprojeto.

Capítulo 13: combateremos o callback hell, resultado daprogramação assíncrona com callbacks através do padrão deprojetoPromise.IsolaremosacomplexidadedesetrabalharcomoXMLHttpRequestemclassesdeserviços.

Capítulo 14: negociações cadastradas são perdidas toda vezque a página é recarregada ou quando o navegador é fechado.UtilizaremosoIndexedDB,umbancopresenteemtodonavegadorparapersistirnossasnegociações.

Capítulo 15: não basta termos uma conexão com oIndexedDB.Aplicaremosumconjuntodeboaspráticasparalidarcomaconexão, facilitandoassimamanutençãoe legibilidadedocódigo.

Capítulo 16: aplicaremos o padrão de projeto DAO para

Page 10: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

esconderdodesenvolvedorosdetalhesdeacessoaobancoetornaraindamaislegívelocódigoescrito.

Capítulo 17: o tendão de Aquiles da linguagem JavaScriptsemprefoioescopoglobaleasdependênciasentrescripts,jogandoa responsabilidade da ordem de carregamento no colo dodesenvolvedor.Aprenderemosausarosistemademódulospadrãodo próprio JavaScript para atacar esses problemas. Durante esseprocesso,aprenderemosalidarcomBabel,otranscompiladormaisfamosodomundoopensource.

Capítulo18:veremoscomoousodepromisescomasync/waittorna nosso código assíncrono mais fácil de ler e de manter.Aplicaremosmaispadrõesdeprojetos.

Capítulo19: aplicaremosopadrãodeprojetoDecoratorcomauxílio do Babel, inclusive aprenderemos a realizar requisiçõesassíncronasatravésdaAPIFetch,simplificandoaindamaisnossocódigo.Nosaprofundaremosnametaprogramaçãocomreflect-metadata.

Capítulo 20: utilizaremos Webpack para agrupar nossosmódulos e aplicar boaspráticas em tempodedesenvolvimento edeprodução.InclusiveaprenderemosafazerodeploydaaplicaçãonoGitHubPages.

Ao final do livro, o leitor terá um arsenal de recursos dalinguagem e padrões de projetos que vão ajudá-lo a resolverproblemasdodiaadia.Acriaçãodeumminiframeworkéapenasparadeixaraaprendizagemmaisdivertida.

Por fim, o leitor poderá acompanhar o autor pelo twitter@flaviohalmeidaepeloblog(http://cangaceirojavascript.com.br).

Page 11: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Agoraquejátemosumavisãogeraldecadacapítulo,veremosodownloaddoprojetoeainfraestruturamínimanecessária.

A infraestrutura necessária para o projeto construído até ocapítulo 10 é apenas oGoogle Chrome. É importante que essenavegador seja usado, pois só atacaremos questões decompatibilidadeapartirdocapítulo11.Aofinaldolivro,sinta-seàvontadeparautilizarqualquernavegador.

Do capítulo 12 em diante, precisaremos do Node.js(https://nodejs.org/en/)instalado.Duranteacriaçãodesteprojeto,foi utilizada a versão 8.1.3.Mas não há problema baixar versõesmais novas, contanto que sejam versões pares, as chamadasversõesLTS(longtermsupport).

Háduasformasdebaixaroprojetobasedestelivro.Aprimeiraé clonar o repositório dohttps://github.com/flaviohenriquealmeida/jscangaceiro.

Você também pode baixar o seu zip no endereçohttps://github.com/flaviohenriquealmeida/jscangaceiro/archive/master.zip.Escolhaaquelaqueformaiscômodaparavocê.

Noentanto,casotenhamoptadopelaversãozip, depois dedescompactá-la, renomeie a pasta jscangaceiro-master parajscangaceiro.

Oprojetopossuiaseguinteestrutura:

Infraestruturanecessária

Downloaddoprojeto

Page 12: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

├──client│├──app│├──css│├──index.html└──server

Existemduaspastas,clienteserver.Aprimeiracontémtodoocódigoquerodaránonavegador,inclusiveédentrodapastaclient/appqueficarátodoocódigoquecriaremosatéofimdolivro.Asdemaispastasdentrodeclient possuemos arquivoscssdoBootstrap.

O Bootstrap nada mais é do que uma biblioteca CSS quepermiteaplicarumvisualprofissionalemnossaaplicaçãocompouco esforço, apenas com o uso de classes.Não é necessárioqueoleitortenhaconhecimentopréviodele.

Ainda na pasta client, temos o arquivo index.html, aúnicapáginaqueutilizaremosaologodoprojeto,nossopontodepartida:

<!--client/index.html-->

<!DOCTYPEhtml><html><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width"><title>Negociações</title><linkrel="stylesheet"href="css/bootstrap.css"><linkrel="stylesheet"href="css/bootstrap-theme.css"></head><bodyclass="container">

<h1class="text-center">Negociações</h1>

Page 13: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

<formclass="form">

<divclass="form-group"><labelfor="data">Data</label><inputtype="date"id="data"class="form-control"requiredautofocus/></div>

<divclass="form-group"><labelfor="quantidade">Quantidade</label><inputtype="number"min="1"step="1"id="quantidade"class="form-control"value="1"required/></div>

<divclass="form-group"><labelfor="valor">Valor</label><inputid="valor"type="number"class="form-control"min="0.01"step="0.01"value="0.0"required/></div>

<buttonclass="btnbtn-primary"type="submit">Incluir</button></form>

<divclass="text-center"><buttonid="botao-importa"class="btnbtn-primarytext-center"type="button">ImportarNegociações</button><buttonid="botao-apaga"class="btnbtn-primarytext-center"type="button">Apagar</button></div><br><br>

<tableclass="tabletable-hovertable-bordered">

<thead><tr><th>DATA</th><th>QUANTIDADE</th><th>VALOR</th>

Page 14: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

<th>VOLUME</th></tr></thead>

<tbody></tbody>

<tfoot></tfoot></table>

</body></html>

Porfim,apastaserversóseráusadaapartirdocapítulo12.Ela disponibiliza um servidor web com as APIs que serãoconsumidaspelanossaaplicação.InclusiveesteservidoréfeitoemNode.js, um dos motivos que tornam essa plataforma umadependência de infraestrutura.As instruções de como levantar oservidorserãoapresentadasassimqueoseuusofornecessário.

VISUALSTUDIOCODE(OPCIONAL)

Durante a criação do projeto, foi utilizado o Visual StudioCode(https://code.visualstudio.com/download),umeditordetexto gratuito e multiplataforma disponível para Windows,LinuxeMAC.Sinta-se livrepara escolher aquele editorquevocêpreferir.

Com o projeto baixado e os requisitos de infraestruturaatendidos, podemos começar nossa jornada através desse sertão

Dandoinícioànossajornada

Page 15: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

queéoJavaScript.Masassimcomoemqualquertramacominício,meioefim,nopróximocapítuloteremosumprólogoquemotivaosdemaiscapítulos.

Page 16: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

1

2

1112

14

141520232530343740434648

Sumário

Parte1-Ocaminhodocangaceiro

1Prólogo:eraumaveznosertão

1.1Oproblemadonossocódigo1.2OpadrãoMVC(Model-View-Controller)

2Negociarcomocangaceiro,temcoragem?

2.1Opapeldeummodelo2.2AclasseNegociação2.3Construtordeclasse2.4Métodosdeclasse2.5Encapsulamento2.6Asintaxeget2.7Objetosimutáveis2.8Ainstânciaéimutávelmesmo?2.9Programaçãodefensiva2.10MenosverbosidadenoconstructorcomObject.assign2.11Atalhoparapropriedadesdeobjetosliterais2.12Assurpresasdedeclaraçõescomvar

SumárioCasadoCódigo

Page 17: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

5153

58

585962677073767785

89

9397

101103

107

107110116

121

121122129

2.13Declaraçãodevariáveiscomlet2.14TemporalDeadZone

3Nocangaço,éaçãoparatodolado

3.1Opapeldeumcontrolador3.2AclasseNegociacaoController3.3Associandométodosdocontrolleràsaçõesdousuário3.4EvitandopercorrerdesnecessariamenteoDOM3.5CriandoumainstânciadeNegociação3.6CriandoumobjetoDateapartirdaentradadousuário3.7Umdesafiocomdatas3.8Resolvendoumproblemacomoparadigmafuncional3.9Arrowfunctions:deixandoocódigoaindamenosverboso

4Doispesos,duasmedidas?

4.1Isolandoaresponsabilidadedeconversãodedatas4.2Métodosestáticos4.3Templateliteral4.4Aboapráticadofail-fast

5Obandodeveseguirumaregra

5.1Criandoumnovomodelo5.2OtendãodeAquilesdoJavaScript5.3Blindandoonossomodelo

6Amodanocangaço

6.1OpapeldaView6.2NossasoluçãodeView6.3Construindoumtemplatedinâmico

CasadoCódigoSumário

Page 18: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

135137

140

143146151156157159

162

163

167173177

181

182183187193196197200202206

6.4Totalizandoovolumedenegociações6.5Totalizandocomreduce

7Oplano

7.1Parâmetrodefault7.2CriandoaclasseMensagemView7.3Herançaereutilizaçãodecódigo7.4Classesabstratas?7.5Parasabermais:super7.6Adquirindoumnovohábitocomconst

Parte2-ForçaVolante

8Umcangaceirosabedelegartarefas

8.1EseatualizarmosaViewquandoomodeloforalterado?8.2Driblandoothisdinâmico8.3Arrowfunctioneseuescopoléxico

9Enganaramocangaceiro,será?

9.1OpadrãodeprojetoProxy9.2AprendendoatrabalharcomProxy9.3Construindoarmadilhasdeleitura9.4Construindoarmadilhasdeescrita9.5ReflectAPI9.6Umproblemanãoesperado9.7Construindoarmadilhasparamétodos9.8UmapitadadoES2016(ES7)9.9AplicandoasoluçãoemNegociacaoController

SumárioCasadoCódigo

Page 19: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

210

210216218224

226

226227229233235

239

239241248255

263

264265267

270275280284

10Cúmplicenaemboscada

10.1OpadrãodeprojetoFactory10.2Nossoproxyaindanãoestá100%!10.3AssociandomodeloeViewatravésdaclasseBind10.4ParâmetrosREST

11Datadosinfernos!

11.1Oproblemacomoinputdate11.2Ajustandonossoconverter11.3Lidandocomexceções11.4Criandonossaprópriaexceção:primeiratentativa11.5Criandonossaprópriaexceção:segundatentativa

12Pilhandooqueinteressa!

12.1Servidoreinfraestrutura12.2RequisiçõesAjaxcomoobjetoXMLHttpRequest12.3Realizandooparsedaresposta12.4Separandoresponsabilidades

13Lutandoatéofim

13.1CallbackHELL13.2OpadrãodeprojetoPromise13.3CriandoPromises13.4CriandoumserviçoparaisolaracomplexidadedoXMLHttpRequest13.5ResolvendoPromisessequencialmente13.6ResolvendoPromisesparalelamente13.7Ordenandooperíodo

CasadoCódigoSumário

Page 20: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

289293

297

298

298304306309311316

322

322326329332

338

345

346347349352353355

13.8Impedindoimportaçõesduplicadas13.9Asfunçõesfilter()esome()

Parte3-Arevelação

14Aalgibeiraestáfurada!

14.1IndexedDB,obancodedadosdonavegador14.2Aconexãocomobanco14.3Nossaprimeirastore14.4Atualizaçãodobanco14.5Transaçõesepersistência14.6Cursores

15Colocandoacasaemordem

15.1AclasseConnectionFactory15.2CriandoStores15.3Garantindoumaconexãoapenasporaplicação15.4OpadrãodeprojetoModulePattern15.5MonkeyPatch:grandespoderestrazemgrandesresponsabilidades

16Entrandonalinha

16.1OpadrãodeprojetoDAO16.2CriandonossoDAOdenegociações16.3Implementandoalógicadeinclusão16.4Implementandoalógicadalistagem16.5CriandoumaDAOFactory16.6Combinandopadrõesdeprojeto

SumárioCasadoCódigo

Page 21: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

358360362

366

366367370376377379380382

385

387389393397398401402

407

408410415417

16.7Exibindotodasasnegociações16.8Removendotodasasnegociações16.9Factoryfunction

17Dividirparaconquistar

17.1MódulosdoES2015(ES6)17.2Instalandooloader17.3Transformandoscriptsemmódulos17.4Opapeldeumtranscompilador17.5Babel,instalaçãoebuild-step17.6Sourcemap17.7Compilandoarquivosemtemporeal17.8Barrel,simplificandoaimportaçãodemódulos

18Indoalém

18.1ES2017(ES8)eoaçúcarsintáticoasync/await18.2Parasabermais:generators18.3Refatorandooprojetocomasync/wait18.4GarantindoacompatibilidadecomES201518.5Lidandomelhorcomexceções18.6Debouncepattern:controlandoaansiedade18.7ImplementandooDebouncepattern

19Chegandoaolimite

19.1OpadrãodeprojetoDecorator19.2SuportandoDecoratoratravésdoBabel19.3UmproblemanãoesperadocomnossoDecorator19.4ElaborandoumDOMInjector

CasadoCódigoSumário

Page 22: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

418424428432435437440

445

446446448450453456457461469

472473475479480483486487492

19.5Decoratordeclasse19.6SimplificandorequisiçõesAjaxcomaAPIFetch19.7ConfigurandoumarequisiçãocomAPIFetch19.8Validaçãocomparâmetrodefault19.9Reflect-metadata:avançandonametaprogramação19.10AdicionandometadadoscomDecoratordemétodo19.11ExtraindometadadoscomumDecoratordeclasse

20Enfrentamentofinal

20.1Webpack,agrupadordemódulos20.2PreparandooterrenoparaoWebpack20.3Otemívelwebpack.config.js20.4Babel-loader,aponteentreoWebpackeoBabel20.5Preparandoobuilddeprodução20.6Mudandooambientecomcross-env20.7WebpackDevServereconfiguração20.8TratandoarquivosCSScomomódulos20.9ResolvendooFOUC(FlashofUnstyledContent)20.10Resolvemosumproblemaecriamosoutro,mastemsolução!20.11Importandoscripts20.12Lidandocomdependênciasglobais20.13OtimizandoobuildcomScopeHoisting20.14Separandonossocódigodasbibliotecas20.15Gerandoapáginaprincipalautomaticamente20.16Simplificandoaindamaisaimportaçãodemódulos20.17CodesplittingeLazyloading20.18System.importvsimport

SumárioCasadoCódigo

Page 23: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

493494496500

20.19Quaissãoosarquivosdedistribuição?20.20DeploydoprojetonoGitHubPages20.21AlterandooendereçodaAPInobuilddeprodução20.22Consideraçõesfinais

CasadoCódigoSumário

Page 24: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Avidatalhouemsuacarnetudooqueelesabia.Masduranteumanoiteacéuaberto,enquantofixavaseuolharnafogueiraqueoaquecia, foi tomado de súbito por uma visão. Nela, Lampiãoaparecia apontando para a direção oposta àquela em que seencontrava.Pormaisfugazqueavisãotenhasido,eleteveacertezade que ainda faltava um caminho a ser trilhado, o caminho docangaceiro. E então, ele juntou suas coisas e tomou seu rumo.Mesmo sabendo que havia cangaceiros melhores e mais bempreparadosdoqueele,nãoesmoreceueaindaapertouopassocomacertezadequeestavanocaminhocerto.Namanhãseguinte,vendoosolnascereolhandoohorizonte,entendeuquetudosódependiadesuavontade.

Parte1-Ocaminhodocangaceiro

Page 25: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

CAPÍTULO1

"Quemdesconfiaficasábio."-GrandeSertão:Veredas

Um cangaceiro não pode existir sem uma trama que ocontextualize. Com o nosso projeto, não será diferente. Estecapítuloéoprólogoquepreparaoterrenodoqueestáporvir.Atramagiraemtornodeumaaplicaçãoquepermiteocadastrodenegociaçõesdebolsadevalores.Apesardeodomíniodaaplicaçãoserreduzido,ésuficienteparaaprimorarmosnossoconhecimentodalinguagemJavaScriptatéofinaldolivro.

Para a nossa comunidade, o projeto que baixamos naintrodução possui a página client/index.html , com umformulárioprontoejáestilizadocomBootstrap,comtrêscamposdeentradaparacapturaremrespectivamenteadata,aquantidadeeovalordeumanegociação.Vejamosumdoscampos:

<!--client/index.html--><!--códigoanterioromitido-->

<formclass="form">

PRÓLOGO:ERAUMAVEZNOSERTÃO

2 1PRÓLOGO:ERAUMAVEZNOSERTÃO

Page 26: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

<divclass="form-group"><labelfor="data">Data</label><inputtype="date"id="data"class="form-control"requiredautofocus/></div><!--códigoposterioromitido-->

Como cada campo possui umid, fica fácil trazermos umareferência desses elementos para que possamosmanipulá-los emnosso código JavaScript. Vamos escrever todo o código destecapítulo no novo arquivo client/app/index.js . Sempestanejar, vamos importá-lo logo de uma vez emclient/index.html,antesdofechamentodatag</body>:

<!--client/index.html--><!--códigoanterioromitido-->

</table>

<scriptsrc="app/index.js"></script></body></html>

Dentrodoarquivoclient/js/index.js,vamoscriaroarray campos que armazenará uma referência para cada um doselementosdeentradadoformulário.Abuscaseráfeitaatravésde document.querySelector() , uma API do DOM que nospermitebuscarelementosatravésdeseletoresCSS:

//client/app/index.js

varcampos=[document.querySelector('#data'),document.querySelector('#valor'),document.querySelector('#quantidade')];

console.log(campos);//verificandooconteúdodoarray

1PRÓLOGO:ERAUMAVEZNOSERTÃO 3

Page 27: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Vamos recarregar a página no navegador e ver a saída noConsoledonavegador:

Figura1.1:Formulário

Antes demais nada, vamos buscar o elemento<tbody> da<table>,poisénelequevamos inserira<tr> que criaremoscomosdadosdasnegociações:

//client/app/index.js

varcampos=[document.querySelector('#data'),document.querySelector('#valor'),document.querySelector('#quantidade')];

//precisamosdetbody,poiselereceberáatrquevamosconstrui

vartbody=document.querySelector('tabletbody');

Agoraquetemosnossoarraycampospreenchido,precisamoslidar com a ação do usuário. Olhando na estrutura declient/index.html,encontramososeguintebotão:

<buttonclass="btnbtn-primary"type="submit">Incluir</button>

Eleédotiposubmit;issosignificaque,aoserclicado,faráoformuláriodispararoeventosubmit responsável pelo enviodeseusdados.Noentanto,nãoqueremosenviarosdadosparalugaralgum,queremosapenas lerosvalores inseridospelousuárioemcada campo e construir uma nova <tr>. Aliás, como estamos

4 1PRÓLOGO:ERAUMAVEZNOSERTÃO

Page 28: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

usandooatributorequiredemcadainput,sóconseguiremosdisparar o evento submit caso todos sejam preenchidos. Onavegador exibirá mensagens padrões de erro para cada camponãopreenchido.

Agora, através de document.querySelector() , vamosbuscar o formulário através da sua classe .form. De posse doelemento em nosso código, vamos associar a função ao eventosubmitatravésdedocument.addEventListener.Éestafunçãoqueseráchamadaquandoousuáriosubmeteroformulário:

//client/app/index.js

varcampos=[document.querySelector('#data'),document.querySelector('#valor'),document.querySelector('#quantidade')];

console.log(campos);//verificandooconteúdodoarray

vartbody=document.querySelector('tabletbody');

document.querySelector('.form').addEventListener('submit',function(event){

alert('oi');});

Por enquanto, vamos exibir um simples pop-up através dafunçãoalertacadasubmissãodoformulário:

1PRÓLOGO:ERAUMAVEZNOSERTÃO 5

Page 29: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Figura1.2:Formulário

Com a certeza de que nossa função está sendo chamada nasubmissão do formulário, já podemos dar início ao código queconstróidinamicamenteumanova<tr>, aquelaqueconteráosdados das negociações do formulário. Através dedocument.createElement,criamosumnovoelemento:

//client/app/index.js

//códigoanterioromitido

vartbody=document.querySelector('tabletbody');

document.querySelector('.form').addEventListener('submit',function(event){

//substituindooalertpelonovocódigovartr=document.createElement('tr');});

TodoarraypossuiafunçãoforEach quenospermite iterarem cada um dos seus elementos. Para cada elemento iterado,vamoscriarsuarespectivatdquefarápartenofinaldatrqueacabamosdecriar:

//client/app/index.js

6 1PRÓLOGO:ERAUMAVEZNOSERTÃO

Page 30: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

//códigoanterioromitido

vartbody=document.querySelector('tabletbody');

document.querySelector('.form').addEventListener('submit',function(event){

vartr=document.createElement('tr');

campos.forEach(function(campo){

//criaumatdseminformaçõesvartd=document.createElement('td');

//atribuiovalordocampoàtdtd.textContent=campo.value;

//adicionaatdnatrtr.appendChild(td);

});});

Ocódigoanteriorcriaumacolunacomovalordecadacampodonossoarraycampos,noentanto,aindafaltaumacolunaparaguardar volume. Toda negociação possui um volume, que nadamaisédoqueovalordanegociaçãomultiplicadopelaquantidadenegociadanodia.Nestecaso,aadiçãodestacolunaprecisaráficarforadonossoforEach:

//client/app/index.js

//códigoanterioromitido

vartbody=document.querySelector('tabletbody');

document.querySelector('.form').addEventListener('submit',function(event){

vartr=document.createElement('tr');

campos.forEach(function(campo){

1PRÓLOGO:ERAUMAVEZNOSERTÃO 7

Page 31: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

vartd=document.createElement('td');td.textContent=campo.value;tr.appendChild(td);

});

//novatdquearmazenaráovolumedanegociaçãovartdVolume=document.createElement('td');

//asposições1e2doarrayarmazenamoscamposdequantidadeevalor,respectivamentetdVolume.textContent=campos[1].value*campos[2].value;

//adicionandoatdquefaltavaàtrtr.appendChild(tdVolume);});

Agoraquejátemosatrcompleta,podemosadicioná-laemtbody.Nossocódigoatéestepontodeveestarassim:

//client/app/index.js

varcampos=[document.querySelector('#data'),document.querySelector('#valor'),document.querySelector('#quantidade')

];

console.log(campos);//verificandooconteúdodoarray

vartbody=document.querySelector('tabletbody');

document.querySelector('.form').addEventListener('submit',function(event){

vartr=document.createElement('tr');

campos.forEach(function(campo){

vartd=document.createElement('td');td.textContent=campo.value;tr.appendChild(td);

});

8 1PRÓLOGO:ERAUMAVEZNOSERTÃO

Page 32: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

vartdVolume=document.createElement('td');tdVolume.textContent=campos[1].value*campos[2].value;tr.appendChild(tdVolume);

//adicionandoatrtbody.appendChild(tr);});

Vamos realizar um teste cadastrando uma nova negociação.Paranossa surpresa,nadaéexibido.Nossa<tr> é até inserida,mascomoasubmissãodoformuláriorecarregounossapágina,elavoltaparaoestadoinicialsem<tr>.

Lembre-se de que não queremos submeter o formulário,queremosapenaspegarosseusdadosnoeventosubmit. Sendoassim, basta cancelarmos o comportamento padrão do eventosubmit através da chamada de event.preventDefault() .

Nosso código continuará a ser executado,mas o formulário nãoserásubmetido:

//client/app/index.js

varcampos=[document.querySelector('#data'),document.querySelector('#valor'),document.querySelector('#quantidade')

];

console.log(campos);//verificandooconteúdodoarray

vartbody=document.querySelector('tabletbody');

document.querySelector('.form').addEventListener('submit',function(event){

//cancelandoasubmissãodoformulárioevent.preventDefault();

//códigoposterioromitido});

1PRÓLOGO:ERAUMAVEZNOSERTÃO 9

Page 33: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Figura1.3:Páginanãorecarregada

Excelente,nossanegociaçãoaparecenalista,noentanto,éumaboapráticalimparoscamposdoformulário,preparando-oparaopróximocadastro.Alémdisso,faremoscomqueocampodadatarecebaofocoatravésdachamadadafunçãofocus, permitindoque o usuário já comece o novo cadastro, sem ter de clicar noprimeirocampo.Nossocódigofinalficaassim:

//client/app/index.js

varcampos=[document.querySelector('#data'),document.querySelector('#valor'),document.querySelector('#quantidade')

];

console.log(campos);//verificandooconteúdodoarray

vartbody=document.querySelector('tabletbody');

document.querySelector('.form').addEventListener('submit',function(event){

event.preventDefault();

vartr=document.createElement('tr');

10 1PRÓLOGO:ERAUMAVEZNOSERTÃO

Page 34: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

campos.forEach(function(campo){

vartd=document.createElement('td');td.textContent=campo.value;tr.appendChild(td);

});

vartdVolume=document.createElement('td');tdVolume.textContent=campos[1].value*campos[2].value;tr.appendChild(tdVolume);

tbody.appendChild(tr);

//limpaocampodadatacampos[0].value='';//limpaocampodaquantidadecampos[1].value=1;//limpaocampodovalorcampos[2].value=0;//focanocampodadatacampos[0].focus();});

Apesar de funcionar, nosso código deixa a desejar eentenderemosomotivoaseguir.

Nenhuma aplicação nasce do nada, mas sim a partir deespecificações criadas das necessidades do cliente. No caso danossa aplicação, ainda é necessário totalizarmos o volume norodapéda tabela,alémdegarantirmosqueadatasejaexibidanoformato dia/mês/ano . Também, precisamos garantir quenegociações,depoisdecriadas,nãosejammodificadas.Porfim,alista de negociações não pode ser alterada, ela só poderá recebernovositense,sequisermosumanovalista,precisaremoscriá-ladozero.

1.1OPROBLEMADONOSSOCÓDIGO

1.1OPROBLEMADONOSSOCÓDIGO 11

Page 35: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Damaneiracomonossaaplicação se encontra,não seránadafácilimplementarcomsegurançaasregrasquevimosnoparágrafoanterior.Nosso códigomistura emumúnico lugaro códigoquerepresenta uma negociação com sua apresentação. Para tornaraindamais desanimador, não temos uma estrutura que pode serreaproveitadaentreprojetos.

Será que um ninja se sairia bem dessa? Ou quem sabe umcangaceiropossabotaracasaemordem?

Existem diversos padrões que podem ser aplicados paraorganizar o código da nossa aplicação, e talvez o mais acessíveldeles seja o padrão MVC (Model-View-Controller). Este padrãocriaumaseparaçãomarcanteentreosdadosdaaplicação(model)esua apresentação (view). Ações do usuário são interceptadas porum controller responsável em atualizar o modelo e refletir seuestadomaisatualatravésdesuarespectivaview.

AlémdopadrãoMVC,trabalharemoscomdoisparadigmasdaprogramação que compõem a cartucheira de todo cangaceiro, oparadigmaorientadoaobjetoseoparadigmafuncional.

Caso o leitor nunca tenha aplicado o paradigma orientado aobjetos, não deve se preocupar, pois seus conceitos-chaves serãoensinados ao longo do livro. No entanto, é necessário ter umconhecimentofundamentaldalinguagemJavaScript.

A ideia é transitarmos entre os dois paradigmas, como umespíritoerrantedocangaçoqueviveentredoismundos,utilizando

1.2 O PADRÃO MVC (MODEL-VIEW-CONTROLLER)

12 1.2OPADRÃOMVC(MODEL-VIEW-CONTROLLER)

Page 36: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

o que cada um deles oferece de melhor para o problema a serresolvido.Nofinal,issotudoresultaráemumminiframework.

A construçãode umminiframeworknão é a finalidade destelivro,masconsequênciadeaprendizadodaartedeumcangaceirona programação. A ideia é que o leitor possa extrair de cadapassagem desta obra recursos que são interessantes de seremutilizadosemsuasaplicações.

Nosso trabalho de verdade começa no próximo capítulo noqualdaremosinícioacriaçãodomodelodenegociações.

O código completo deste capítulo você encontra emhttps://github.com/flaviohenriquealmeida/cangaceiro-javascript/tree/master/01

1.2OPADRÃOMVC(MODEL-VIEW-CONTROLLER) 13

Page 37: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

CAPÍTULO2

"Vivendoseaprende;masoqueseaprende,mais,ésóafazeroutrasmaioresperguntas."-GrandeSertão:Veredas

No capítulo anterior, vimos que nosso código não realizavaumaseparaçãoclaraentremodeloesuaapresentação,tornando-odifícilde compreenderemanter.Também foipropostoousodomodelo MVC (Model-View-Controller) para melhorar suaestrutura.

Da tríade do modelo MVC, começaremos construindo ummodelodenegociação.Masaliás,oqueseriaummodelo?

Um modelo é uma abstração de algo do mundo real. Umexemplodemodeloé aquele criadoporumanalistademercado,

NEGOCIARCOMOCANGACEIRO,TEMCORAGEM?

2.1OPAPELDEUMMODELO

14 2NEGOCIARCOMOCANGACEIRO,TEMCORAGEM?

Page 38: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

permitindo-osimularoperaçõeseatépreverseucomportamento.No contexto de nossa aplicação, criaremos um modelo denegociação.

Nossomodelo precisamaterializar asmesmas regras de umanegociação; emoutraspalavras,deve seguirumaespecificaçãodenegociação.Oparadigmaorientado a objetos trata especificaçõesatravésdousodeclasses.

A partir do ES2015 (ES6), ficou aindamais fácilmaterializaremnossocódigoacriaçãodeclassecoma introduçãoda sintaxeclass.

Antesdecontinuarcomnossoprojeto,vamosremoveroscriptclient/app/index.js de client/index.html , pois não

usaremos mais este código. Com o arquivo removido, podemosdarinícioaconstruçãodanossaclasseNegociacao.

VamoscriaroscriptquedeclararánossaclasseNegociacaonapastaclient/app/domain/negociacao/Negociacao.js.Estesimples ato de criar um novo arquivo já evidencia algumasconvençõesqueutilizaremosatéofinaldolivro.

Apastaclient/domain é aquela que armazenará todos osscriptsquedizemrespeitoaodomíniodaaplicação,nocaso,tudoaquilo sobre negociações. A subpastaclient/app/domain/negociacao não foi criada por engano, é

dentrodelaqueo scriptdanossa classe residirá, inclusive classesde serviços que possuem forte relação com esse domínio. Dessaforma, basta olharmos uma única pasta para saber sobre

2.2ACLASSENEGOCIAÇÃO

2.2ACLASSENEGOCIAÇÃO 15

Page 39: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

determinadodomíniodaaplicação.

Por fim, o nome do script segue o padrão PascalCase,caracterizado por arquivos que começam com letra maiúscula,assim como cada palavra que compõe o nome do arquivo. Essaconvençãotornaevidenteparaquemestudaaestruturadoprojetoqueestearquivopossuiadeclaraçãodeumaclasse,poistodaclasseporconvençãosegueomesmopadrãodeescrita.

Assim, alteraremos nosso arquivo client/app/domain/negociacao/Negociacao.js queacabamosdecriar:

//client/app/domain/negociacao/Negociacao.js

classNegociacao{

}

Uma classe no mundo da Orientação a Objetos define aestruturaquetodoobjetocriadoapartirdeladeveseguir.Sabemosqueumanegociaçãoprecisa terumadata,umaquantidade eumvalor. É através da função constructor() que definimos aspropriedadesdeumaclasse:

//client/app/domain/negociacao/Negociacao.js

classNegociacao{

constructor(){

this.data=newDate();//dataatualthis.quantidade=1;this.valor=0.0;

}}

Através de this.nomeDaPropriedade, especificamos que a

16 2.2ACLASSENEGOCIAÇÃO

Page 40: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

negociaçãoterá:data,quantidadeevalor,cadapropriedadecomseuvalorpadrão.

Emseguida,noarquivoclient/index.html, vamos incluirumanovatag<script>antesdofechamentodatag</body>.Nosso objetivo será criar duas instâncias de negociação, ou seja,doisobjetoscriadosapartirdaclasseNegociacao.Usaremosasvariáveisn1en2.

<!--client/index.html--><!--códigoanterioromitido--><scriptsrc="app/domain/negociacao/Negociacao.js"></script><script>

varn1=newNegociacao();console.log(n1);

varn2=newNegociacao();console.log(n2);

</script><!--códigoposterioromitido-->

Vamosrecarregaronavegadoreabriroconsole.VejamquejáaparecemduasNegociacoes:

Figura2.1:Duasnegociaçõesimpressas

Cadainstânciacriadapossuiasmesmaspropriedadesevalores,pois foram criadas a partir da mesma classe que atribui valorespadrõesparacadaumadelas.

2.2ACLASSENEGOCIAÇÃO 17

Page 41: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

O uso do operador new não foi por acaso. Vejamos o queacontececasoelesejaomitido:

<!--client/index.html--><!--códigoanterioromitido--><scriptsrc="app/domain/negociacao/Negociacao.js"></script><script>

varn1=newNegociacao();console.log(n1);

varn2=Negociacao();//<-operadornewremovidoconsole.log(n2);

</script><! códigoposterioromitido

Nonavegador,umamensagemdeerroéexibidanoconsole:

Figura2.2:Mensagemdeerro

Somosinformadosdequeaclasseconstructor()nãopodeser invocada sem operador new. O operador new é bastanteimportante por ser o responsável pela inicialização da variávelimplícitathis de cada instância criada, ou seja, de cadaobjetocriado. Dessa forma, caso o programador esqueça de usar ooperadornewparacriarainstânciadeumaclasse,oquenãofazsentido, ele será avisado com uma bela mensagem de erro noconsole.

Como cada instância criada possui seu próprio contexto emthis,podemosalterarovalordaspropriedadesdecadaobjeto,

18 2.2ACLASSENEGOCIAÇÃO

Page 42: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

semqueissointerfiranooutro:

<!--client/index.html--><!--códigoanterioromitido--><scriptsrc="app/domain/negociacao/Negociacao.js"></script><script>

varn1=newNegociacao();n1.quantidade=10;console.log(n1.quantidade);

varn2=Negociacao();n2.quantidade=20;console.log(n2.quantidade);console.log(n1.quantidade);//continua10

</script><!--códigoposterioromitido-->

Agora, se executarmos o código, no console do navegador,veremosquecadaNegociacaoteráumvalordiferente.

Figura2.3:Negociaçõesimpressas2

Realizamosoprimeirotestecomduasinstânciasquepossuemseusprópriosvaloresdepropriedade,masquecompartilhamumamesmaestruturacomdata,quantidadeevalor.

Antes de continuarmos, vamos apagar uma das negociações,pois uma já é suficiente para os novos testes que realizaremos aseguir:

<!--client/index.html--><!--códigoanterioromitido-->

2.2ACLASSENEGOCIAÇÃO 19

Page 43: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

<scriptsrc="app/domain/negociacao/Negociacao.js"></script><script>

varn1=newNegociacao();n1.quantidade=10;console.log(n1.quantidade);

</script><!--códigoposterioromitido-->

Veremosa seguiroutra formadecriarmos instânciasdeumaclasse.

Todanegociaçãoprecisa ter umadata, quantidade e valor ounãoseráumanegociação.VejamosumexemploqueconstróiumainstânciadeNegociacaocomtodososseusdadosatribuídos:

<!--client/index.html--><!--códigoanterioromitido--><scriptsrc="app/domain/negociacao/Negociacao.js"></script><script>

varn1=newNegociacao();n1.data=newDate();n1.quantidade=10;n1.valor=200.50;console.log(n1);

</script><!--códigoposterioromitido-->

Nosso código é funcional,mas se todanegociaçãoprecisa terumadata,quantidadeevalor,que taldefinirmosessesvaloresnamesma instrução que cria uma nova instância? Além de nossocódigo ficar menos verboso, deixamos clara essa necessidade naespecificaçãodaclasse.

Você deve lembrar que toda classe possui um

2.3CONSTRUTORDECLASSE

20 2.3CONSTRUTORDECLASSE

Page 44: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

constructor(),umafunçãoespecialqueseráchamadatodavezqueooperadornew forusadoparacriaruma instânciadeumaclasse.Sendoumafunção,elapodereceberparâmetros.

Queremospoderfazerisso:

<!--client/index.html--><!--códigoanterioromitido--><scriptsrc="app/domain/negociacao/Negociacao.js"></script><script>

//bastaumainstruçãoapenasemvezde4!varn1=newNegociacao(newDate(),5,700);console.log(n1);

</script><!--códigoposterioromitido-->

NaclasseNegociacao, adicionaremososparâmetrosde seuconstructor():

//client/app/domain/negociacao/Negociacao.js

classNegociacao{

constructor(data,quantidade,valor){

//cadaparâmetrorecebidoseráatribuídoàspropriedadesdaclasse

this.data=data;this.quantidade=quantidade;this.valor=valor;

}}

Veja que a passagem dos parâmetros para a instância deNegociacaosegueamesmaordemdeseusparâmetrosdefinidosem seuconstructor(). Cada parâmetro recebido é repassadoparacadapropriedadedaclasse.

Faremos um teste no console para ver se tudo funcionou

2.3CONSTRUTORDECLASSE 21

Page 45: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

corretamente:

Figura2.4:Testenoconsole

Os valores coincidem com os que especificamos no HTML.Porém, ainda não calculamos o volume - a quantidademultiplicadapelovalor.Faremosistoagora:

<!--client/index.html--><!--códigoanterioromitido--><scriptsrc="app/domain/negociacao/Negociacao.js"></script><script>

varn1=newNegociacao(newDate(),5,700);varvolume=n1.quantidade*n1.valor;console.log(volume);

</script><!--códigoposterioromitido-->

Figura2.5:Volume

22 2.3CONSTRUTORDECLASSE

Page 46: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Ovalor3500éoresultadodaoperaçãodemultiplicação.Masestamos fazendo uma programação procedural, e sempre que oprogramadorprecisardovolume,elemesmoteráderealizarestecálculo.

Alémdeseralgotediosoterderepetirocálculodovolume,aschances de cada programador cometer algum erro ao calculá-losãograndes,mesmocomumcódigosimplescomoesse.Aliás,esseéumproblemaqueoparadigmaorientadoaobjetosvemresolver.

NoparadigmadaOrientaçãoaObjetos,háumaforteconexãoentre dado e comportamento, diferente da programaçãoprocedural, na qual temos de um lado o dado e, do outro,procedimentosqueoperamsobreestesdados.Nossaclassepossuiapenas dados, chegou a hora de dotá-la do seu primeirocomportamento,oobtemVolume.

Vamoscriarométodo:

//client/app/domain/negociacao/Negociacao.js

classNegociacao{

constructor(data,quantidade,valor){

this.data=data;this.quantidade=quantidade;this.valor=valor;

}

obtemVolume(){

returnthis.quantidade*this.valor;}

2.4MÉTODOSDECLASSE

2.4MÉTODOSDECLASSE 23

Page 47: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

}

Funções que definem um comportamento de uma classe sãochamadas de métodos para alinhar o vocabulário com oparadigmadaOrientaçãoaObjetos.Noentanto,nãoéraroalgunscangaceiroscontinuaremautilizarotermofunção.

Agora, basta perguntarmos para a própria instância da classeNegociacaoparaqueobtenhaseuvolumeparanós.Temosum

únicolocalquecentralizaocálculodovolume.Então,alteraremosnossotesteparausarmosométodoqueacabamosdecriar:

<!--client/index.html--><!--códigoanterioromitido--><scriptsrc="app/domain/negociacao/Negociacao.js"></script><script>

varn1=newNegociacao(newDate(),5,700);varvolume=n1.obtemVolume();console.log(volume);

</script><!--códigoposterioromitido-->

Agora, ao executarmos o código, o valor 3500 apareceránovamentenoconsole:

Figura2.6:Volume2

24 2.4MÉTODOSDECLASSE

Page 48: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Ovalorfoicalculadocorretamente.Oobjetosabelidarcomaspropriedades trabalhadas.Logo, a regrade comoobterovolumeestánopróprioobjeto.Voltamosaterumaconexãoentredadoecomportamento.

NósjáconseguimostrabalharcomaclasseNegociacao,masprecisamos implementar outra regra de negócio além do cálculodovolume:apóscriadaanegociação,estanãopoderáseralterada.Noentanto,nossocódigonãoestápreparadoparalidarcomessaregra,comopodemosverificarcomumnovoteste:

<!--client/index.html--><!--códigoanterioromitido--><scriptsrc="app/domain/negociacao/Negociacao.js"></script><script>

varn1=newNegociacao(newDate(),5,700);n1.quantidade=10;console.log(n1.quantidade);

</script><!--códigoposterioromitido-->

No código anterior, estamos atribuindo um novo valor àpropriedade quantidade da nossa instância, para em seguidaimprimirmos seuvalor.Qualvalor será impressonoconsole,5ou10?

Figura2.7:Regraalterada

2.5ENCAPSULAMENTO

2.5ENCAPSULAMENTO 25

Page 49: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Eleimprimiuovalorrecém-adicionado10eistopodecausarproblemas.Nãoéàtoaaregradeumanegociaçãonaqualelanãopodeseralteradadepoisdecriada.

Imaginequeacabamosdefazerumanegociaçãoecombinamosum determinado valor, mas depois alguém decide alterá-la parabenefício próprio.Nosso objetivo é que as propriedades de umanegociaçãosejamsomentedeleitura.

Noentanto,alinguagemJavaScript-atéaatualdata-nãonospermite usar modificadores de acesso, um recurso presente emoutraslinguagensorientadasaobjeto.Nãopodemosfazercomqueuma propriedade seja apenas leitura (ou gravação). O quepodemos fazer é convencionarmosque todapropriedadedeumaclasseprefixadacomumunderline(_)nãodeveseracessadaforadosmétodosdaprópriaclasse:

//client/app/domain/negociacao/Negociacao.js

classNegociacao{

constructor(data,quantidade,valor){

this._data=data;this._quantidade=quantidade;this._valor=valor;

}

obtemVolume(){

returnthis._quantidade=this._valor;}

}

Esta seráumaconvençãoque informaráaoprogramadorqueas propriedades que contenham _ (underline) só poderão seracessadas pelos próprios métodos da classe. Isto significa que,

26 2.5ENCAPSULAMENTO

Page 50: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

mesmo podendo imprimir a propriedade _quantidade comoutrovalor,nãodeveríamosmaispoderacessá-la.O_ funcionacomoumavisodizendo:"programador,vocênãopodealterarestapropriedade!".

Então, se usamos a convenção de utilizar o prefixo, comofaremosparaimprimirovalordaquantidadedeumanegociação,porexemplo?Seráqueprecisaremosabandonaraconvençãoqueacabamos de aplicar? Não será preciso, pois podemos criarmétodosacessadores.

Métodosacessadoresgeralmentecomeçamcomoprefixogetem seus nomes. No caso da nossa classe Negociacao ,adicionaremos o método getQuantidade() , que retornará o_quantidade . Usaremos também o getData() e o

getValor()queterãofinalidadessemelhantes:

//client/app/domain/negociacao/Negociacao.js

classNegociacao{

constructor(data,quantidade,valor){

this._data=data;this._quantidade=quantidade;this._valor=valor;

}

obtemVolume(){

returnthis._quantidade=this._valor;}

getData(){

returnthis._data;}

2.5ENCAPSULAMENTO 27

Page 51: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

getQuantidade(){

returnthis._quantidade;}

getValor(){

returnthis._valor;}

}

Como são métodos da própria classe, seguindo nossaconvenção, eles podem acessar às propriedades prefixadas com(_).Equalqueroutrocódigoqueprecisarleraspropriedadesdeum objeto do tipo Negociacao poderá fazê-lo através dosmétodosacessadoresquecriamos.

Vamosatualizarnossocódigoemindex.html,pararefletiroestadoatualdonossocódigo:

<!--client/index.html--><!--códigoanterioromitido--><scriptsrc="app/domain/negociacao/Negociacao.js"></script><script>

varn1=newNegociacao(newDate(),5,700);console.log(n1.getQuantidade());console.log(n1.getData());console.log(n1.getValor());

</script><!--códigoposterioromitido-->

Observequeestamosacessandoosdemaiscampos:

28 2.5ENCAPSULAMENTO

Page 52: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Figura2.8:Valoresnoconsole

Os valores serão impressos corretamente no console. Emseguida,modificaremosoobterVolume()paragetVolumeemNegocicacao js

//client/app/domain/negociacao/Negociacao.js

classNegociacao{

constructor(data,quantidade,valor){

this._data=data;this._quantidade=quantidade;this._valor=valor;

}

//trocamosonomedeobtemVolumeparagetVolumegetVolume(){

returnthis._quantidade=this._valor;}

//códigoposterioromitido}

Vamostestartodososmétodosacessadoresemindex.html:

<!--códigoanterioromitido--><scriptsrc="app/domain/negociacao/Negociacao.js"></script><script>

2.5ENCAPSULAMENTO 29

Page 53: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

varn1=newNegociacao(newDate(),5,700);console.log(n1.getQuantidade());console.log(n1.getData());console.log(n1.getValor());console.log(n1.getVolume());

</script><!--códigoposterioromitido-->

Nonavegador,veremosqueosvaloresquantidade,data,valorevolumeserãoimpressoscorretamente.

Figura2.9:Quantidadeevolume

Podemos enxugar nossos métodos acessadores através dasintaxeget que a linguagemnos oferece.Vamos alterar nossocódigo:

//client/app/domain/negociacao/Negociacao.js

classNegociacao{

constructor(data,quantidade,valor){

this._data=data;this._quantidade=quantidade;this._valor=valor;

}

2.6ASINTAXEGET

30 2.6ASINTAXEGET

Page 54: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

getvolume(){

returnthis._quantidade*this._valor;}

getdata(){

returnthis._data;}

getquantidade(){

returnthis._quantidade;}

getvalor(){

returnthis._valor;}

}

Vejamqueasintaxegetvemantesdonomedométodo,quenão possui mais o prefixo get que usamos antes. Por fim,acessamosessesmétodoscomose fossempropriedadesemnossocódigo.

Vamos alterar index.html para vermos em prática aalteração:

<!--códigoanterioromitido--><scriptsrc="app/domain/negociacao/Negociacao.js"></script><script>

varn1=newNegociacao(newDate(),5,700);

console.log(n1.quantidade);console.log(n1.data);console.log(n1.valor);console.log(n1.volume);

</script><!--códigoposterioromitido-->

2.6ASINTAXEGET 31

Page 55: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Estamoscriandoumapropriedadegetterdeacessoà leitura.Emesmo sendo um método, precisamos acessá-lo como umapropriedade. Contudo, por debaixo dos panos, as propriedadesqueacessamoscontinuamsendométodos.

Figura2.10:console

Outracaracterísticademétodoscriadoscomasintaxeget éque, se tentarmos atribuir umnovo valor à propriedade, ele seráignorado.Vejamos:

<!--códigoanterioromitido--><scriptsrc="app/domain/negociacao/Negociacao.js"></script><script>

varn1=newNegociacao(newDate(),5,700);n1.quantidade=1000;//instruçãoéignorada,semmensagemdeerro

console.log(n1.quantidade);console.log(n1.data);console.log(n1.valor);console.log(n1.volume);

</script><!--códigoposterioromitido-->

Ovalor1000 não será repassadoparao_quantidade.Ovalorcontinuarásendo5:

32 2.6ASINTAXEGET

Page 56: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Figura2.11:Valordequantidadeinalterados

Istoacontecequandoapropriedadeéumgetter (ouseja,édeleitura), já que não podemos atribuir a ela um valor.Mas aindapodemosmodificá-lacomn1._quantidade:

<!--códigoanterioromitido--><scriptsrc="app/domain/negociacao/Negociacao.js"></script><script>

varn1=newNegociacao(newDate(),5,700);

//instruçãoéignorada,semmensagemdeerron1.quantidade=1000;

//nadanosimpededefazermosisso,apenasnossaconvençãon1._quantidade=5000;

console.log(n1.quantidade);console.log(n1.data);console.log(n1.valor);console.log(n1.volume);

</script><!--códigoanterioromitido-->

2.6ASINTAXEGET 33

Page 57: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Figura2.12:Quantidadealterada

Como vimos, isso não impede o programador desavisado deainda acessar as propriedades prefixadas com underline. Noentanto, ainda temos um artifício da linguagem que pode evitarqueissoaconteça.

Nós usaremos um artifício existente há algum tempo nalinguagem JavaScript que permite "congelar" um objeto.Propriedades de objetos congeladas não podem receber novasatribuições.ConseguimosessecomportamentoatravésdométodoObject.freeze().

Vamoscongelarainstâncian1emnossoteste:

<!--códigoanterioromitido--><scriptsrc="app/domain/negociacao/Negociacao.js"></script><script>

varn1=newNegociacao(newDate(),5,700);

Object.freeze(n1);n1._quantidade=100000000000;//nãoconseguemodificarenenhumerroacontece

2.7OBJETOSIMUTÁVEIS

34 2.7OBJETOSIMUTÁVEIS

Page 58: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

console.log(n1._quantidade);</script><!--códigoposterioromitido-->

Figura2.13:Objetocongelado

No console, veremos o valor 5 ; isto significa que nãoconseguimosmais alterar e o objeto foi congelado.Vale lembrarque quantidade é uma propriedade que chamamos emNegociacao.js, quepor "debaixodospanos" rodará comoum

métodoeretornaráthis._quantidade.

Mesmo colocando n1._quantidade = 100000000000; noindex.html,essevalorparecetersidoignorado.Issoébom,poisagora o desenvolvedor já não conseguirá alterar esta quantidade.Vamos fazer um pequeno teste que nos mostrará o que estácongeladonoconsole:

<!--códigoanterioromitido--><scriptsrc="app/domain/negociacao/Negociacao.js"></script><script>

varn1=newNegociacao(newDate(),5,700);

console.log(Object.isFrozen(n1));Object.freeze(n1);console.log(Object.isFrozen(n1));n1._quantidade=100000000000;

</script>

2.7OBJETOSIMUTÁVEIS 35

Page 59: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Noconsole,veremososeguinteresultado:

Figura2.14:Consolecomfalseetrue

Oobjetoantesnãoeracongeladoe,depois, foi.Por isso,nãoconseguimosalterar_quantidade, o atributoquedefinimos serprivadoporconvenção.Porém,essasoluçãoéprocedural,porquevocêterásempredelembrardecongelarainstância.

Entretanto, queremos que, ao usarmos onewNegociacao,umainstânciacongeladajásejadevolvida.Nóspodemosfazerissocongelando a instância no constructor() . Em

app/domain/negociacao/Negociacao.js , adicionaremosObject.freeze()apósoúltimothis:

//client/app/domain/negociacao/Negociacao.js

classNegociacao{

constructor(data,quantidade,valor){

this._data=data;this._quantidade=quantidade;this._valor=valor;Object.freeze(this);

}

36 2.7OBJETOSIMUTÁVEIS

Page 60: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

//códigoposterioromitido

Vocês lembram que o this é uma variável implícita? Equando algum método é chamado, temos acesso à instânciatrabalhada. O this do Object.freeze() será o n1 noindex.html.

<!--códigoanterioromitido--><scriptsrc="app/domain/negociacao/Negociacao.js"></script><script>

varn1=newNegociacao(newDate(),5,700);

n1._quantidade=100000000000;

console.log(n1.quantidade);console.log(n1.data);console.log(n1.valor);console.log(n1.volume);

</script><!--códigoanterioromitido-->

Então,oresultadodenewNegociacao(/*parâmetros*/)jádevolveráumainstânciacongelada.Aparentemente,resolvemoso nossoproblema.

Mas seráquenossanegociação é realmente imutável?VamosverificarmaisadiantesedefatoaNegociacaonãoseráalterada.

Pelaregradenegócio,umanegociaçãodeveserimutávelenãopode ser alterada depois de criada. Mas faremos um novo testetentando alterar a data da negociação. Nós fizemos isto com aquantidade. Agora, criaremos a variável outroDia , que seráreferenteàdatadiferentedaatual:

2.8AINSTÂNCIAÉIMUTÁVELMESMO?

2.8AINSTÂNCIAÉIMUTÁVELMESMO? 37

Page 61: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

<!--códigoanterioromitido--><scriptsrc="app/domain/negociacao/Negociacao.js"></script><script>

varn1=newNegociacao(newDate(),5,700);

console.log(n1.data);

varoutroDia=newDate();

//setDatepermitealterarodiadeumadataoutroDia.setDate(11);

n1.data=outroDia;

console.log(n1.data);</script><!--códigoposterioromitido-->

Paraverificarseocódigofuncionacorretamente,adicionamoso console.log(). Se Negociacao for realmente imutável, adatanãopoderáseralterada.

Figura2.15:Negociaçãoimutável

Vemosqueelacontinuouimutável.

Agora, faremosumtestediferente.Nãovamosmais trabalharcom a variável outroDia , e substituiremos porn1.data.setDate(11).Veja que estamos chamandoométodosetDatedapropriedadedatadopróprioobjeto:

38 2.8AINSTÂNCIAÉIMUTÁVELMESMO?

Page 62: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

<!--códigoanterioromitido--><scriptsrc="app/domain/negociacao/Negociacao.js"></script><script>

varn1=newNegociacao(newDate(),5,700);

console.log(n1.data);

n1.data.setDate(11);

console.log(n1.data);</script><!--códigoposterioromitido-->

Seráqueadatafoialterada?

Figura2.16:dataalterada

Vejaqueadatafoialterada,poisadataexibidanoconsolefoiodia11.Nossanegociaçãoémutável!Nósnãopodemosdeixarissoacontecer.

Mas se nós utilizamos o Object.freeze() , por que issoaconteceu?OObject.freeze somente evita que seja realizadauma nova atribuição à propriedade do objeto congelado. Nãopodemos fazer, por exemplo, n1.data = new Date() , poisestamos atribuindo um novo valor à propriedade. Porém, éperfeitamentepossível fazermosn1.data.setData(11), pois aspropriedadesdoobjetodefinidoemn1.datanãosãocongeladas.

2.8AINSTÂNCIAÉIMUTÁVELMESMO? 39

Page 63: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Quandooprogramadorchegaaumpontoemquealinguagemnãoofereceumasoluçãonativaparaoproblema,elepodeapelarpara sua criatividade; característica fundamental de todocangaceiroparalidarcomasadversidadesqueencontra.

UmamaneiradetornarmosadatadeumanegociaçãoimutáveléretornarmosumnovoobjetoDatetodavezqueogetterdadatafor acessado. Dessa maneira, qualquer modificação através dasfunçõesquetodainstânciadeDatepossuisómodificaráacópia,enãooobjetooriginal.

Atualmente,ogetdoarquivoNegociacaoestáassim:

//app/domain/negociacao/Negociacao.js//códigoanterioromitido

getdata(){

returnthis._data;}//códigoposterioromitido

Vamosmodificaroretornodogetdata():

//app/domain/negociacao/Negociacao.js//códigoanterioromitido

getdata(){

returnnewDate(this._data.getTime());}//códigoposterioromitido

OgetTime()deumadataretornaráumnúmerolongcomumarepresentaçãodadata.Vejamosnoconsole:

2.9PROGRAMAÇÃODEFENSIVA

40 2.9PROGRAMAÇÃODEFENSIVA

Page 64: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Figura2.17:Retornodadata

Noconstructor()deDate,estenúmeroseráaceitoparaaconstruçãode umanovadata. Então, se tentarmos alterar a dataatravés de n1.data.setDate(11), não estaremos mudando adataquen1guarda,masumacópia. Istoéoquechamamosdeprogramaçãodefensiva.

Vamostestaronossocódigo,apósasalteraçõesfeitasnogetdata()daclasseNegociacao.Depoisderecarregarmosapáginanonavegador,veremosaseguintedatanoconsole:

Figura2.18:Datasnoconsole

Devemos ter o mesmo cuidado com o constructor()também, porque se passamos uma data no constructor() deNegociacao, qualquer mudança na referência da data fora da

classe modificará também sua data. Vejamos o problemaacontecendo:

<!--códigoanterioromitido-->

2.9PROGRAMAÇÃODEFENSIVA 41

Page 65: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

<scriptsrc="app/domain/negociacao/Negociacao.js"></script><script>

varhoje=newDate();

varn1=newNegociacao(hoje,5,700);

console.log(n1.data);

//alteraadataquefoiarmazenadaemNegociacaotambémhoje.setDate(11);

console.log(n1.data);</script>

Observe que, em vez de passarmos o new Date() para oconstructor()deNegociacao,passamosavariávelhojequereferenciaumaDate.Quando aNegocicao receber o objetohoje, ele guardará uma referência para o mesmo objeto. Isto

significaque,sealterarmosavariávelhoje,modificaremosadataque estánaNegociacao. Se executarmos o código, a data seráalteradaparaodia11.

Figura2.19:Dataalterada2

Por isso, também usaremos o getTime() noconstructor():

//app/domain/negociacao/Negociacao.js

classNegociacao{

42 2.9PROGRAMAÇÃODEFENSIVA

Page 66: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

constructor(data,quantidade,valor){

//criandoumanovadata,umanovareferênciathis._data=newDate(data.getTime());

this._quantidade=quantidade;this._valor=valor;Object.freeze(this);

}//códigoposterioromitido

Aofazermosisto,jánãoconseguiremosalterarareferêncianoindex.html,porqueoqueestamosguardandonoNegociacaonãoémaisumareferênciaparahoje. Então,nósusaremosumnovoobjeto.Quandorecarregarmosapáginanonavegador,adataqueapareceránoconsoleserá10.

Nomomentodacriaçãododesigndasclasses,sejacuidadosocom a mutabilidade, tomando as medidas necessárias paragarantiraimutabilidadequandoforpreciso.

Com isso, finalizamos a blindagem da nossa classe paragarantir a sua imutabilidade. Existem outras soluções maisavançadasnoJSparatentarmosemularoprivacy-aprivacidade-do seu código; entretanto, perdemos em legibilidade eperformance.Então,asoluçãousadaéamaisviável.

Podemos simplificar ainda mais nosso código com umpequenotruquequeveremosaseguir.

VamoslançarnossoolharmaisumaveznoconstructordaclasseNegociacao:

2.10 MENOS VERBOSIDADE NOCONSTRUCTORCOMOBJECT.ASSIGN

2.10MENOSVERBOSIDADENOCONSTRUCTORCOMOBJECT.ASSIGN 43

Page 67: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

//app/domain/negociacao/Negociacao.js

classNegociacao{

constructor(data,quantidade,valor){

this._data=newDate(data.getTime());this._quantidade=quantidade;this._valor=valor;Object.freeze(this);

}//códigoposterioromitido

Cadaparâmetrorecebidonoconstructoréatribuídoaumapropriedade de instância da classe através de instruções queseguemaestruturathis._nomeDaPropriedade=parametro.Setivéssemosumaquantidadedeparâmetrosmuitogrande,ocódigodonossoconstructorcresceriabastante.

Podemos simplificar bastante esse processo de atribuição dosparâmetros recebidos pelo constructor nas propriedades dainstânciada classe comométodoObject.assign(). Primeiro,vamoscompreendercomoelefunciona.

OmétodoObject.assign() éusadoparacopiarosvaloresde todas as propriedades próprias enumeráveis de um ou maisobjetosdeorigemparaumobjetodestino.Ele retornaráoobjetodestino.Vejamosumexemplo:

//EXEMPLOAPENAS,FAÇANOCONSOLEDOCHROME

varorigem1={nome:'Cangaceiro'};varorigem2={idade:20};

varcopia=Object.assign({},origem1,origem2);

console.log(copia);//exibe{nome:'Cangaceiro',idade:20};

O primeiro parâmetro de Object.assign() é o objeto

44 2.10MENOSVERBOSIDADENOCONSTRUCTORCOMOBJECT.ASSIGN

Page 68: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

destino que receberá a cópia das propriedades dos objetos deorigem,umobjetosemqualquerpropriedadecomasintaxe{}.Podemospassarquantosobjetosdeorigemdesejarmosapartirdosegundo parâmetro, em nosso caso passamos origem1 eorigem2. O retorno do método é um objeto que contém as

propriedadesdeorigem1eorigem2.

Semudarmosnossoexemploepassarmosa referênciadeumobjeto já existente como parâmetro de destino no métodoObject.assign() , esse objeto será modificado ganhando as

propriedades de um ou mais objetos de origem passados comoparâmetro:

//EXEMPLOAPENAS,FAÇANOCONSOLEDOCHROME

varorigem={idade:20};vardestino={nome:'Cangaceiro'};

Object.assign(destino,origem);console.log(destino);//exibe{nome:'Cangaceiro',idade:20};

OcomportamentoqueacabamosdevernospermiterealizaraseguintealteraçãonoconstructordeNegociacao:

//app/domain/negociacao/Negociacao.js

classNegociacao{

constructor(data,quantidade,valor){

Object.assign(this,{_data:newDate(data.getTime()),_quantidade:quantidade,_valor:valor});

Object.freeze(this);}

//códigoposterioromitido

Estamoscopiandoparathis, que representa a instânciada

2.10MENOSVERBOSIDADENOCONSTRUCTORCOMOBJECT.ASSIGN 45

Page 69: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

classeNegociacao, as propriedadesdoobjetodeorigemcriadocomaspropriedades_data,_quantidadee_valorecomosvalores recebidos pelo constructor da classe. Temos apenasumainstruçãodeatribuiçãonolugardetrêsinstruções.

Em um primeiro momento, não parece ser muita vantagemtrocar três instruções por uma apenas com Object.assign(),pois a legibilidade não é lá uma das melhores. Mas secombinarmos este recurso comopróximoqueveremos a seguir,teremosumasoluçãobeminteressante.

O método Object.assign() recebeu como origem umobjetocriadonaformaliteral,istoé,atravésde{}.Vejamosumexemploisoladoquecriaumobjetoatravésdaformaliteral:

//EXEMPLOAPENAS,FAÇANOCONSOLEDOCHROME

varperfil='Cangaceiro';

varobjeto={perfil:perfil};

console.log(objeto);//exibe{perfil:"Cangaceiro"}

No exemplo anterior, a propriedade perfil recebe comovalor uma variável de mesmo nome. Quando o nome dapropriedade tem o mesmo nome da variável que será atribuídacomoseuvalor,podemosdeclararnossoobjetodestaforma:

//EXEMPLOAPENAS,FAÇANOCONSOLEDOCHROME

varperfil='Cangaceiro';

2.11 ATALHO PARA PROPRIEDADES DEOBJETOSLITERAIS

46 2.11ATALHOPARAPROPRIEDADESDEOBJETOSLITERAIS

Page 70: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

varobjeto={perfil};//passamosapenasperfil

console.log(objeto);//exibe{perfil:"Cangaceiro"}

Essa novidade introduzida no ES2016 (ES6) nos permitesimplificar o uso de Object.assign() que vimos antes.AlterandooconstructordeNegociacao:

//app/domain/negociacao/Negociacao.js

classNegociacao{

//MUDANÇANONOMEDOSPARÂMETROS,AGORACOMUNDERLINEconstructor(_data,_quantidade,_valor){

//USANDOOATALHOPARADECLARAÇÃODEPROPRIEDADES

Object.assign(this,{_data:newDate(_data.getTime()),_quantidade,_valor});

Object.freeze(this);}

//códigoposterioromitido

Reparem que o nome dos parâmetros recebidos noconstructormudaram dedata,quantidade,valor para_data, _quantidade, _valor . Isso não foi por acaso, a

intenção é que eles tenhamomesmonomedas propriedades doobjetopassadocomoorigemparaObject.assign(). Possuindoo mesmo nome, podemos usar o atalho de declaração depropriedadesquevimosnaseçãoanterior.

Para melhorarmos a legibilidade, vamos convencionar quetoda propriedade recebida pelo constructor que precisa seralteradaantesdaatribuiçãoseráatribuídaindividualmente,semoauxíliodeObject.assign().

Alterandonossocódigo:

2.11ATALHOPARAPROPRIEDADESDEOBJETOSLITERAIS 47

Page 71: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

//app/domain/negociacao/Negociacao.js

classNegociacao{

constructor(_data,_quantidade,_valor){

Object.assign(this,{_quantidade,_valor});this._data=newDate(_data.getTime());Object.freeze(this);

}//códigoposterioromitido

Simplificamos bastante a passagem dos parâmetros doconstructorparaaspropriedadesdaclasse.

Antes de continuarmos com o próximo capítulo, precisamosadquirirumnovohábitoaodeclararmosnossasvariáveis.

Temosnossomodelodenegociaçãopronto.Contudo,antesdecontinuarmos, vamos voltar nossa atenção para as variáveisdeclaradascomvar.

Declaraçõescomvarnãocriamescopodeblocoassimcomoemoutraslinguagens.Vejamosumexemplo:

<!--CÓDIGODEEXEMPLO,NÃOENTRAEMNOSSAAPLICAÇÃO--><script>

for(vari=1;i<=100;i++){console.log(i);

}alert(i);//qualseráovalor?</script>

2.12AS SURPRESASDEDECLARAÇÕESCOMVAR

Ausênciadeescopodebloco

48 2.12ASSURPRESASDEDECLARAÇÕESCOMVAR

Page 72: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Ocódigoanteriorimprimirácorretamenteosnúmerosde1a100noconsole.Porém,a instruçãoalert(i) exibirá o valor101:

Figura2.20:Alert

Avariávelicontinuasendoacessívelforadoblocodofor.A únicamaneira de termos um escopo para variáveis declaradascomvaréaodeclará-lasdentrodoblocodeumafunção:

functionexibeNome(){

varnome='FlávioAlmeida';alert(nome);

}exibeNome();//exibeFlávioAlmeidaalert(nome);//undefined

No exemplo que acabamos de ver, a variável nome não éacessívelforadoblocodafunção.

Comvar, podemosdeclarar variáveis comomesmonomesem que isso incomode o interpretador do JavaScript. Porexemplo:

functionminhaFuncao(){

Declaraçõesrepetidas

2.12ASSURPRESASDEDECLARAÇÕESCOMVAR 49

Page 73: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

varnome="Flávio";varnome="Almeida";//nãoprecisadevar!

}

Este comportamento não ajuda em nada o desenvolvedor aperceber, nas dezenas de linhas de código, que está declarandonovamenteumavariávelquejáhaviasidodeclarada.

Vejamosmaisumexemplo:

functionminhaFuncao(){

alert(nome);varnome='Flávio';

}

minhaFuncao();

Ocódigoanteriorexibeundefined,nafunçãoalert(),emvez de um erro. Isso acontece porque variáveis declaradas comvarsãoiçadas(hoisting)paraoiníciodoblocodafunçãonaqualforamdeclaradas.

Por debaixo dos panos, o JavaScript executa nosso código daseguintemaneira:

//COMOOINTERPRETADORLÊNOSSOCÓDIGOfunctionminhaFuncao(){

/*Içouadeclaraçãodavariávelparaotopodocontexto,no

caso,iníciodafunção*/varnome;alert(nome);nome='Flávio';

}

Acessoantesdesuadeclaração

50 2.12ASSURPRESASDEDECLARAÇÕESCOMVAR

Page 74: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

minhaFuncao();

Esse comportamento permite que códigos como estefuncionem:

functionminhaFuncao(){

if(!nome){varnome='FlávioAlmeida'

}alert(nome);

}

minhaFuncao();

Por debaixo dos panos, nosso código será interpretado destaforma:

//COMOOINTERPRETADORLÊNOSSOCÓDIGO

functionminhaFuncao(){varnome;if(!nome){

nome='FlávioAlmeida'}alert(nome);

}

minhaFuncao();

Dessa maneira, a variável nome testada pelo if já foideclarada,porémcomvalorundefined.AboanotíciaéqueháoutramaneiradedeclararmosvariáveisemJavaScript,assuntodapróximaseção.

A partir do ES2015 (ES6), foi introduzida uma nova sintaxepara declaração de variáveis, a sintaxe let . Vejamos as

2.13DECLARAÇÃODEVARIÁVEISCOMLET

2.13DECLARAÇÃODEVARIÁVEISCOMLET 51

Page 75: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

característicasdestenovotipodedeclaração.

Vamos rever o exemplo da seção anterior, mas desta vezusandoletparadeclararavariáveldoblocodofor:

<!--CÓDIGODEEXEMPLO,NÃOENTRAEMNOSSAAPLICAÇÃO--><script>

for(leti=1;1<=100;1++){console.log(i);

}//undefined!

alert(i);</script>

A instrução alert(i) exibirá undefined, pois variáveisdeclaradascomlet,dentrodeumbloco,sósãoacessíveisdentrodessebloco.

Vejamosmaisumteste:

<!--CÓDIGODEEXEMPLO,NÃOENTRAEMNOSSAAPLICAÇÃO--><script>

for(leti=1;i<=100;i++){//sóexistenoblocofor!letnome="Flávio";console.log(i);

}//undefined!alert(i);//undefined!alert(nome);

</script>

Escopodebloco

Nãoépossívelhaverdeclarações repetidasdentrodeummesmobloco

52 2.13DECLARAÇÃODEVARIÁVEISCOMLET

Page 76: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Outropontointeressantedeletéquenãopodemosdeclararduas variáveis com omesmo nome dentro de ummesmo bloco{}.Vejamos:

functionminhaFuncao(){

letnome='Flávio';letnome='Almeida';

}minhaFuncao();

Afunçãoanterior,aoserchamada,resultaránoerro:

UncaughtSyntaxError:Identifier'nome'hasalreadybeendeclared

Contudo,ocódigoseguinteétotalmenteválido:

letnome='Flávio';

functionminhaFuncao(){

letnome='Almeida';alert(nome);

}minhaFuncao();//exibeAlmeidaalert(nome);//exibeFlávio

Há mais um detalhe que precisamos entender a respeito donovotipodedeclaraçãoqueacabamosdeaprender.

Vamos revisar comportamentos de variáveis declaradas comvar.Afunçãodeclaraavariávelnomepormeiodevar:

functionminhaFuncao(){

alert(nome);varnome='Almeida';

}

2.14TEMPORALDEADZONE

2.14TEMPORALDEADZONE 53

Page 77: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

minhaFuncao();

Sabemos que, através de hoisting, ela será interpretada destaforma:

//COMOOINTERPRETADORLÊNOSSOCÓDIGO

functionminhaFuncao(){

varnome;alert(nome);nome='Almeida';

}minhaFuncao();

Da maneira que é interpretada pelo JavaScript, temos adeclaraçãovarnome;, quenãopossuiqualquer atribuição.Porissooalert(nome)exibeundefinedemvezdeumerroalegandoque a variável não existe. Contudo, quando usamos let ,precisamos ficar atentos para oTemporalDead Zone. O nomepodeparecerumtantoassustadorinicialmente.

Variáveisdeclaradascomlet tambémsão içadas (hoisting).Contudo, seu acesso só é permitido após receberem umaatribuição; caso contrário, teremos um erro.Vejamos o exemploanterior,destavezusandolet:

functionminhaFuncao(){

alert(nome);letnome='Flávio';

}

minhaFuncao();

Quando executamos nosso código, receberemos amensagemdeerro:

UncaughtReferenceError:nomeisnotdefined

54 2.14TEMPORALDEADZONE

Page 78: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Como foi dito, variáveis declaradas com let também sãoiçadas,entãonossocódigoestaráassimquandointerpretado:

//COMOOINTERPRETADORLÊNOSSOCÓDIGO

functionminhaFuncao(){

letnome;alert(nome);nome='Flávio';

}

minhaFuncao();

Éentreadeclaraçãodavariável e suaatribuiçãoque temosoTemporalDeadZone.Qualqueracessofeitoàvariávelnesseespaçode tempo resultará em ReferenceError. Contudo, é um errobenéfico, pois acessar uma variável antes de sua declaração éraramenteintencionalemumaaplicação.

Nadanosimpedededeliberadamentefazermosisso:

functionminhaFuncao(){letnome=undefined;alert(nome);nome='Flávio';

}

minhaFuncao();

O resultado dealert seráundefined. Faz todo sentido,porqueacessamosavariávelnome após receberuma atribuição,istoé,foradoTemporalDeadZone.

Agoraquejásabemosdosdesdobramentosdousodeletemnossa aplicação, vamos alterar client/index.html parautilizarmosessanovadeclaração:

<!--códigoanterioromitido-->

2.14TEMPORALDEADZONE 55

Page 79: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

<scriptsrc="app/domain/negociacao/Negociacao.js"></script><script>

lethoje=newDate();

letn1=newNegociacao(hoje,5,700);

console.log(n1.data);

hoje.setDate(11);

console.log(n1.data);</script>

Se executarmos o código, tudo continuará funcionandonormalmente.

Figura2.21:Dataconsole

Nestecapítulo,criamosummodelodenegociaçãoaplicandooparadigma da Orientação a Objetos. Contudo, implementamosapenas o M do modelo MVC. Precisamos implementar asinterações do usuário com o modelo, papel realizado pelocontrollerdomodeloMVC,assuntodopróximocapítulo.

O código completo deste capítulo você encontra emhttps://github.com/flaviohenriquealmeida/cangaceiro-javascript/tree/master/02

56 2.14TEMPORALDEADZONE

Page 80: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

2.14TEMPORALDEADZONE 57

Page 81: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

CAPÍTULO3

"Orealnãoestánoinícionemnofim,elesemostrapragenteénomeiodatravessia..."-GrandeSertão:Veredas

No capítulo anterior, criamos nosso modelo de negociaçãoatravésda classeNegociacao e, através do console, realizamosdiversos testes com instâncias dessa classe. Para que nossaaplicação seja útil para o usuário, essas instâncias devemconsiderar informações prestadas pelo usuário através doformulário definido client/index.html . Resolveremos essaquestãocomauxíliodeumControllerdomodeloMVC.

NomodeloMVC,oControllerfazapontedeligaçãoentreasaçõesdousuárionaView comoModel.NaView, temosnosso client/index.html (ainda nos aprofundaremos), e noModeltemosinstânciasdeNegociacao.Dessaforma,mantendoaseparaçãoentreModeleView,alteraçõesnaView(comopor

NOCANGAÇO,ÉAÇÃOPARATODOLADO

3.1OPAPELDEUMCONTROLADOR

58 3NOCANGAÇO,ÉAÇÃOPARATODOLADO

Page 82: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

exemplo,trocarde<table>para<ul>)nãoafetamoModel.Issoéinteressante,poispodemosterváriasrepresentaçõesvisuaisdeummesmomodelosemqueestasforcemqualqueralteraçãonomodelo.

Chegouomomentodecriarmosnossoprimeirocontrolador.

Vamos criar o arquivoclient/app/controllers/NegociacaoController.js,noqual

declararemosaclassedonossocontroller:

//client/app/controllers/NegociacaoController.js

classNegociacaoController{

}

Maisumavez,paranãocorrermosoriscodeesquecermosdeimportar o script que acabamos de criar, vamos importá-lo emclient/index.html , como o último script. Aliás, vamos

removera tag<script> queusamospara realizarnossos testescomaclasseNegociacao:

<!--client/index.html--><!--códigoanterioromitido-->

<scriptsrc="app/domian/negociacao/Negociacao.js"></script>

<!--REMOVEUATAGCOMNOSSOSTESTES-->

<scriptsrc="app/controllers/NegociacaoController.js"></script>

<!--códigoposterioromitido-->

Vamosagoracriarumnovométodochamadoadiciona.Ele

3.2ACLASSENEGOCIACAOCONTROLLER

3.2ACLASSENEGOCIACAOCONTROLLER 59

Page 83: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

será chamado toda vez que o usuário submeter o formulárioclicandonobotão"Incluir".

Por enquanto, vamos apenas cancelar o evento padrão desubmissãodoformulário(nãoqueremosqueapáginarecarregue)eexibirumalerta:

//client/app/controllers/NegociacaoController.js

classNegociacaoController{

adiciona(event){

//cancelandoasubmissãodoformulárioevent.preventDefault();

alert('Chameiaçãonocontroller');}}

Como faremos a ligação do método adiciona com asubmissão do formulário? Uma prática comum entreprogramadores JavaScript é realizar essa associação através dopróprioJavaScript,buscandooelementodoDOMeassociando-ocomumafunçãooumétodoatravésdainterfacedeeventos.

Vamoscriaroarquivoclient/app/app.js,noqualfaremosaassociaçãocitadanoparágrafoanterior.Aliás,jávamosimportá-lo emclient/index.html antesde escrever qualquer linhadecódigo:

<!--client/index.html--><!--códigoanterioromitido-->

<scriptsrc="app/domain/negociacao/Negociacao.js"></script><scriptsrc="app/controllers/NegociacaoController.js"></script><scriptsrc="app/app.js"></script>

<!--códigoposterioromitido-->

60 3.2ACLASSENEGOCIACAOCONTROLLER

Page 84: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Não foi engano o nome do arquivo começar com letraminúscula, uma vez que ele não declara uma classe. Entendamapp.js como o ponto de entrada da nossa aplicação, isto é,

aquelequefazainicializaçãodetodososobjetosnecessáriosparanossaaplicação.Nocasodanossaaplicação,sóprecisamosdeumcontrollerporenquanto.

Em client/app/app.js , criaremos uma instância deNegociacaoController , para em seguida buscarmos o

formulário da página. Por fim, vamos associar o métodoadicionadainstânciacriadaaoeventosubmitdoformulário:

//client/app/app.js

//criouainstânciadocontrollerletcontroller=newNegociacaoController();

//associaoeventodesubmissãodoformulárioàchamadadométodo "adiciona"

document.querySelector('.form').addEventListener('submit',function(event){

controller.adiciona(event);});

Coma alteraçãoque fizemos,basta recarregarmos apágina epreenchermos o formulário. Com os dados da negociação todospreenchidos, ao clicarmos no botão "Incluir" o alerta queprogramamosemnossocontrollerseráexibido.

Apesarde funcional,estecódigopodeserreduzido.AfunçãoaddEventListeneresperareceberdoisparâmetros.Oprimeiroéo nomedoeventoqueestamostratandoeosegundoafunçãoquedesejamos chamar. Mas, se o método controller.adiciona

3.2ACLASSENEGOCIACAOCONTROLLER 61

Page 85: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

também é uma função e funções podem ser passadas comoparâmetroparaoutrasfunções,podemossimplificardestaforma:

//client/app/app.js

letcontroller=newNegociacaoController();

//passamosdiretamentecontroller.adicionadocument

.querySelector('.form').addEventListener('submit',controller.adiciona);

Tudo continua funcionando como antes. Então, a ação docontroller foi chamada.Mas o nosso objetivo não é exibir a

mensagem,nósqueremoscriarumanegociação.

Ao clicarmos no botão Incluir do formulário, jáconseguimos executar as ações do controller . Agora,precisamos capturar os dados preenchidos. Mas já fizemos issoantes,noprólogo, atravésdedocument.querySelector(), querecebe como parâmetro um seletor CSS que permite identificarqualelementodesejamosbuscar:

//client/app/controllers/NegociacaoController.js

classNegociacaoController{

adiciona(event){

event.preventDefault();

//buscandooselementosletinputData=document.querySelector('#data');letinputQuantidade=document.querySelector('#quantidad

e');

3.3 ASSOCIANDO MÉTODOS DOCONTROLLERÀSAÇÕESDOUSUÁRIO

62 3.3ASSOCIANDOMÉTODOSDOCONTROLLERÀSAÇÕESDOUSUÁRIO

Page 86: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

letinputValor=document.querySelector('#valor');

console.log(inputData.value);console.log(inputQuantidade.value);console.log(inputValor.value);

}}

Odocument.querySelector()éoresponsávelpelabuscanoDOMdoselementoscomid#data,#quantidadee#valor-observe que conseguimos utilizar os seletores CSS. Aliás, umcangaceiroJavaScripttemumbomconhecimentodessesseletores.

Vamosexecutarocódigoparaversetodososdadosaparecemnoconsoledonavegador.Preencheremosoformuláriocomadata11/12/2016,aquantidade1eovalor100.

Figura3.1:Dadosdoconsole

Os dados aparecem no console, no entanto, os valores daspropriedades _quantidade e _valor são strings. Podemosverificarissoatravésdainstruçãotypeof:

//client/app/controllers/NegociacaoController.js

3.3ASSOCIANDOMÉTODOSDOCONTROLLERÀSAÇÕESDOUSUÁRIO 63

Page 87: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

classNegociacaoController{

adiciona(event){

event.preventDefault();

//buscandooselementosletinputData=document.querySelector('#data');letinputQuantidade=document.querySelector('#quantidad

e');letinputValor=document.querySelector('#valor');

console.log(inputData.value);console.log(inputQuantidade.value);

//exibestringconsole.log(typeof(inputQuantidade.value));

console.log(inputValor.value);

//exibestringconsole.log(typeof(inputValor.value));

}}

Isso acontece porque o retorno da propriedade value doselementos dosDOMdo tipoinput retorna seus valores comostring. Precisamos convertê-los para números com as funçõesparseInt()eparseFloat(),respectivamente:

//client/app/controllers/NegociacaoController.js

classNegociacaoController{

adiciona(event){

event.preventDefault();

//buscandooselementosletinputData=document.querySelector('#data');letinputQuantidade=document.querySelector('#quantidad

e');

64 3.3ASSOCIANDOMÉTODOSDOCONTROLLERÀSAÇÕESDOUSUÁRIO

Page 88: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

letinputValor=document.querySelector('#valor');

console.log(inputData.value);console.log(parseInt(inputQuantidade.value));console.log(parseFloat(inputValor.value));

}}

Nosso formulário é pequeno, mas se tivesse mais de dezcampos, teríamos de repetir a instruçãodocument.querySelector dez vezes. Podemos enxugar essa

sintaxe.

Vamoscriaravariável$ (umahomenagemao jQuery!)quereceberácomovalordocument.querySelector,algototalmentepossível em JavaScript, pois funções podem ser atribuídas avariáveis. Com isso, criamos um alias , um atalho para ainstrução:

//client/app/controllers/NegociacaoController.js

classNegociacaoController{

adiciona(event){

event.preventDefault();

//aideiaéque$sejaoquerySelectorlet$=document.querySelector;

letinputData=$('#data');letinputQuantidade=$('#quantidade');letinputValor=$('#valor');

console.log(inputData.value);console.log(parseInt(inputQuantidade.value));console.log(parseFloat(inputValor.value));

}}

3.3ASSOCIANDOMÉTODOSDOCONTROLLERÀSAÇÕESDOUSUÁRIO 65

Page 89: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Destaforma,ficoumenostrabalhosoescreverocódigo.Masseo executarmos como está, veremos uma mensagem de erro nonavegador:

UncaughtTypeError:IllegalinvocationatHTMLFormElement.adiciona(NegociacaoController.js:9)

A atribuição do document.querySelector na variável $parecenãoterfuncionado.Porquenãofuncionou?

Odocument.querySelector() é uma funçãoquepertenceao objeto document - chamaremos tal função de método.Internamente, o querySelector tem uma chamada para othis, que é o contexto no qual ométodo é chamado, no casodocument.

Noentanto,quandoatribuímosodocument.querySelectorà variável $ , ele passa a ser executado fora do contexto dedocument, e isto não funciona. O que devemos fazer, então?

PrecisamostrataroquerySelectorcomoumafunçãoseparada,masqueaindamantenhaodocumentcomoseucontexto.

A boa notícia é que conseguimos isso facilmente através debind(),umafunção,pormaisestranhoquepareça,presenteemqualquerfunçãoemJavaScript:Assim,alteraremosnossocódigo:

//client/app/controllers/NegociacaoController.js

classNegociacaoController{

adiciona(event){

event.preventDefault();

//realizandoobind,$mantémdocumentcomoseucontextothis

let$=document.querySelector.bind(document);

66 3.3ASSOCIANDOMÉTODOSDOCONTROLLERÀSAÇÕESDOUSUÁRIO

Page 90: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

letinputData=$('#data');letinputQuantidade=$('#quantidade');letinputValor=$('#valor');

console.log(inputData.value);console.log(parseInt(inputQuantidade.value));console.log(parseFloat(inputValor.value));

}}

Agora,estamosinformandoqueoquerySelectorvaiparaavariável$,mas aindamanterádocument como seu contexto.Destavez,ocódigofuncionarácorretamentenonavegador.

Temos "amoringa e a cartucheira" emmãos para podermoscriarumainstânciadenegociaçãocomosdadosdoformulário.Noentanto, o código anterior, apesar de ninja, não é um código decangaceiro. Podemos melhorá-lo ainda mais e faremos isso aseguir.

O código funciona, mas se adicionarmos dez negociações eclicarmosdezvezesemIncluir,oquerySelectorbuscaráoselementos identificadospor#data,#quantidade e #valordezvezestambém.Contudo,devemosevitaraomáximopercorrero DOMporquestõesdeperformance.

Nossocódigoestáassim:

//client/app/controllers/NegociacaoController.js

classNegociacaoController{

3.4 EVITANDO PERCORRERDESNECESSARIAMENTEODOM

3.4EVITANDOPERCORRERDESNECESSARIAMENTEODOM 67

Page 91: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

adiciona(event){

event.preventDefault();

let$=document.querySelector.bind(document);

letinputData=$('#data');letinputQuantidade=$('#quantidade');letinputValor=$('#valor');

console.log(inputData.value);console.log(parseInt(inputQuantidade.value));console.log(parseFloat(inputValor.value));

}}

Pormaisqueoimpactodeperformancebuscandoapenastrêselementos pelo seu ID não seja tão drástico em nossa aplicação,vamospensarcomocangaceiroseaplicarboaspráticas.

Paramelhoraraperformance,adicionaremosoconstructoremoveremosos elementosde entradaparadentrodele.Mas emvez de criarmos variáveis, criaremos propriedades em this .Sendoassim,abuscapeloselementosdoDOMsóserãorealizadasumaúnicaveznoconstructor(),enãomaisacadachamadadométodoadiciona().

Comoessaspropriedadessófazemsentidoaoseremacessadaspela própria classeNegociacaoControler, usaremos o prefixo_(aconvençãoparapropriedadesprivadas):

//client/app/controllers/NegociacaoController.js

classNegociacaoController{

constructor(){

let$=document.querySelector.bind(document);this._inputData=$('#data');

68 3.4EVITANDOPERCORRERDESNECESSARIAMENTEODOM

Page 92: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

this._inputQuantidade=$('#quantidade');this._inputValor=$('#valor');

}

adiciona(event){

event.preventDefault();

//precisamosacessaraspropriedadesatravésdethisconsole.log(this._inputData.value);console.log(parseInt(this._inputQuantidade.value));console.log(parseFloat(this._inputValor.value));

}}

Agora,mesmo que incluamos 300 negociações, os elementosdo DOM serão buscados uma única vez, quando a classeNegociacaoController for instanciada. No entanto, as coisas

nãosaemcomoesperado.

Depois de clicarmos no botão Incluir e verificarmos oconsoledoChrome,vemosaseguintemensagemdeerro:

UncaughtTypeError:Cannotreadproperty'value'ofundefinedatHTMLFormElement.adiciona(NegociacaoController.js:15)

Temos o mesmo problema quando associamos odocument.querySelectoràvariável$,queperdeuareferênciade this para o document . Vamos olhar o arquivoclient/app/app.js:

//client/app/app.js

letcontroller=newNegociacaoController();document

.querySelector('.form').addEventListener('submit',controller.adiciona);

Temos o mesmo problema aqui. Quando passamoscontroller.adicionaparaométodoaddEventListener,seu

3.4EVITANDOPERCORRERDESNECESSARIAMENTEODOM 69

Page 93: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

this deixou de ser o controller e passou a ser o próprioelemento doDOM, no caso, o formulário. Isso acontece porquetodafunção/métodopossuiumthisdinâmicoqueassumecomovalorocontextonoqualfoichamado.Masjáaprendemosalidarcomisso.

Vamoslançarmãomaisumavezdafunçãobind:

//client/app/app.js

letcontroller=newNegociacaoController();

//bindaqui!document

.querySelector('.form').addEventListener('submit',controller.adiciona.bind(controller));

Se executarmos o código, veremos que eles serão exibidoscorretamentenoconsole.

Figura3.2:Dadosnoconsole

Agora que o usuário pode chamar ométodo adiciona da

3.5 CRIANDO UMA INSTÂNCIA DENEGOCIAÇÃO

70 3.5CRIANDOUMAINSTÂNCIADENEGOCIAÇÃO

Page 94: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

nossa classe NegociacaoController podemos construir umanegociaçãocombasenosdadosdoformulário:

//client/app/controllers/NegociacaoController.js

classNegociacaoController{

//códigoanterioromitido

adiciona(event){

event.preventDefault();

letnegociacao=newNegociacao(this._inputData.value,parseInt(this inputQuantidade value),parseFloat(this._inputValor.value)

);

console.log(negociacao);}

}

Passamososparâmetrosdata,quantidadeevalorparaoconstructordeNegociacao.Comosdadosdoformulário,nósjátemosumanegociação.Masseráquetemosmesmo?

OlhandooconsoledoChrome,vemosaseguintemensagemdeerro:

Negociacao.js:5UncaughtTypeError:Cannotreadproperty'getTime'ofundefined

atnewNegociacao(Negociacao.js:5)atNegociacaoController.adiciona(NegociacaoController.js:15)

Recebemos uma mensagem de erro, mas não foi emNegociacaoController.OerroocorreunaclasseNegociacao.Noconsole,vemosaindaemquallinhafoioproblema,alémdeelenos avisar quedata.getTime não é uma função. Como isso épossível?

3.5CRIANDOUMAINSTÂNCIADENEGOCIAÇÃO 71

Page 95: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Vamosdarumaolhadinhanadefiniçãodanossaclasse:

//client/app/domain/negociacao/Negociacao.js

classNegociacao{

constructor(_data,_quantidade,_valor){

Object.assign(this,{_quantidade,_valor});this._data=newDate(_data.getTime());Object.freeze(this);

}

//códigoposterioromitido

Quer dizer então que a data que estamos passando para oconstructornãoéumaDate?Quetipodedadoelaéentão?

Para descobrirmos o tipo da data capturada no formulário,podemosusarafunçãotypeof():

//client/app/controllers/NegociacaoController.js

adiciona(event){

event.preventDefault();

//verificandootipoconsole.log(typeof(this._inputData.value));

letnegociacao=newNegociacao(this._inputData.value,parseInt(this._inputQuantidade.value),parseFloat(this._inputValor.value)

);

console.log(negociacao);}

Atravésdoconsole,vemosqueavalordadatadoelementoédotipostring:

72 3.5CRIANDOUMAINSTÂNCIADENEGOCIAÇÃO

Page 96: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Figura3.3:String

Encarar conversões de datas nas mais diversas linguagensrequer determinação. É por isso que precisamos da nossacartucheiraedonossochapéudecangaceiroaseguir.

Existem váriasmaneiras de construirmos uma data passandoparâmetrosparaoconstructordeDate.Umadasformasquejávimoséque,sejátemosumadata,podemospassaroresultadodogetTime() dela para o constructor de outra. Será quefuncionasepassarmosastringdiretamente?Vamostentar:

//client/app/controllers/NegociacaoController.js

//códigoanterioromitido

adiciona(event){

event.preventDefault();

//oconstructorestárecebendoumastring.Seráquefunciona

letdata=newDate(this._inputData.value)console.log(data);

}

//códigoposterioromitido

3.6 CRIANDO UM OBJETO DATE A PARTIRDAENTRADADOUSUÁRIO

3.6CRIANDOUMOBJETODATEAPARTIRDAENTRADADOUSUÁRIO 73

Page 97: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Realizando um teste com a última alteração, não temossucesso:

Figura3.4:DataerradanoConsole

Adataqueapareceunoconsolenãofoiamesma.Emvezdisso,vemosqueodiaé11.Então,nãofuncionou.Temosdeencontraroutrojeitodetrabalharcomessadata.

Outramaneirade trabalharmoscomDate é passarmosumarray para seu construtor com os valores do ano, mês e dia,respectivamente.Sedigitarmosnoconsole:

newDate(['2016','11','12']);

Oretornoseráadatacorreta.

Estaéumaformaderesolvermos.Precisamosentãoencontraruma maneira de transformar a string retornada porthis._inputData.valueemumarraydestrings.AboanotíciaéquestringssãoobjetosemJavaScripte,comotodoobjeto,sabemexecutartarefasparanós.

É através do método split de uma string que podemostransformá-la em um array. No entanto, para que o métodoidentifique cada elemento, precisamos informar um separador*.Comoa string retornadaporthis._inputData.value segueopadrãoano-mês-dia,utilizaremosohífencomoseparador.

74 3.6CRIANDOUMOBJETODATEAPARTIRDAENTRADADOUSUÁRIO

Page 98: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

QuetalumtesteantesnoconsoledoChrome?

dataString='2016-12-11';lista=dataString.split('-')console.log(lista);//exibe["2016","12","11"]

Ele nos retornou a data correta, perfeito. Então, no métodoadiciona da nossa classe NegociacaoController , faremos

assim:

//client/app/controllers/NegociacaoController.js//códigoanterioromitido

adiciona(event){

event.preventDefault();

//agoraoDaterecebeumarrayletdata=newDate(this._inputData.value.split('-'));console.log(data);}//códigoposterioromitido

Veja que, em uma única instrução, capturamos dethis._inputData.value , realizamos o split e passamos o

resultado final para Date . Resolvemos o problema da data!Vejamosoresultadonoconsole!

SatNov12201600:00:00GMT-0200(-02)

Masnossa soluçãonãoparaporaqui,háoutras formasde seresolver o mesmo problema. Por exemplo, quando um Daterecebeumarray,internamenteelereagrupatodososelementosdoArrayemumaúnicastringusandoumavírgulacomoseparador.

Oreagrupamentointernoéfeitoatravésdafunçãojoin,querecebe como parâmetro o separador utilizado. Podemos simularestecomportamentonoconsole:

3.6CRIANDOUMOBJETODATEAPARTIRDAENTRADADOUSUÁRIO 75

Page 99: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

dataString='2016-12-11';lista=dataString.split('-');//separouosnúmeroslista=lista.join(',')//juntouosnúmerosusandovírgulacomoseparadorconsole.log(lista);

Noentanto,podemosemnossocódigosimplesmentetrocaroshifensporvírgulasepassarastringresultanteparaonovoDate.Para isso,podemospedirajudaà funçãoreplace, presente emtodas as strings. Passaremos como parâmetro uma expressãoregular,pedindoqueseja trocadoohífende todasasocorrênciasdastring(ouseja,global)porvírgula:replace(/-/g,',').

//client/app/controllers/NegociacaoController.js//códigoanterioromitido

adiciona(event){

event.preventDefault();

//outraformaderesolverletdata=newDate(this._inputData.value.replace(/-/g,','))

;console.log(data);

}

Eoresultadonoconsolefica:

SatNov12201600:00:00GMT-0200(-02)

A data ficou correta. Vimos que existem várias formas deresolver a questão da data. Mas que tal escolhermos uma outramaneira para testar nossas habilidades de cangaceiro? É o queveremosaseguir.

HámaisumaformaaceitapeloDatenaconstruçãodeuma

3.7UMDESAFIOCOMDATAS

76 3.7UMDESAFIOCOMDATAS

Page 100: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

novadata.NoconsoledoChrome,escreveremosaseguintelinha:

data=newDate(2016,11,12)console.log(data);

No entanto, se imprimirmos esta data, veremos o seguinteretorno:

MonDec12201600:00:00GMT-0200(-02)

AdataimpressaéMonDec122016,ouseja,12/12/2016.Porqueeleimprimiudezembro,sepassamosomês11?Porquenestaformaomêsdeveserpassadode0(janeiro)a11 (dezembro).Então, se queremos que a data seja em novembro, precisamosdecrementar o valor do mês. Vamos fazer um novo teste noconsole,digitando:

data=newDate(2016,10,12)console.log(data);

Agora,adatajáaparececorreta.

SatNov12201600:00:00GMT-0200(-02)

Todas as soluções que vimos anteriormentepara se construirumadata foramsoluçõesninjas.Noentanto, cangaceirosgostamdedesafios,eporissoescolheremosestaúltimaformaquevimos.

Então,voltandoaoproblema,comodeveserfeitaaconversãoda string no formato 2016-11-12 , capturada dethis._inputData.valueparaumDate, usando a formaque

acabamosdeaprender?Éissoqueveremosaseguir.

3.8 RESOLVENDO UM PROBLEMA COM OPARADIGMAFUNCIONAL

3.8RESOLVENDOUMPROBLEMACOMOPARADIGMAFUNCIONAL 77

Page 101: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

NossoobjetivoéqueoDate receba em seuconstructorumano,mêsedia.Vamosalterarnossocódigolançandomãodafunçãosplit():

//client/app/controllers/NegociacaoController.js//códigoanterioromitido

adiciona(event){

event.preventDefault();

letdata=newDate(this._inputData.value.split('-'));console.log(data);

}

//códigoposterioromitido

Atravésdosplit,astring2016-11-12setornaráumarrayde três elementos. Sabemos que se passarmos da forma como jáestá, conseguiremos o resultado desejado. Porém, não queremosqueoDaterecebaumarray,queremosqueelerecebacadaitemdoarraycomocadaparâmetrodoseuconstructor.Queremosque ele receba a primeira posição do array como a primeiraposição do constructor , e que o processo se repita com osegundoeterceiroelementodoarray.

ApartirdoES2015(ES6),podemosutilizarospreadoperator,que permite tratar cada elemento do array como um elementoindividualmente.Leiaatentamenteaalteraçãonocódigo:

//client/app/controllers/NegociacaoController.js//códigoanterioromitido

adiciona(event){

event.preventDefault();

//usamososfamosostrêspontinhos!letdata=newDate(...this._inputData.value.split('-'));

78 3.8RESOLVENDOUMPROBLEMACOMOPARADIGMAFUNCIONAL

Page 102: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

console.log(data);}

//códigoposterioromitido

Adicionamos o spread operator ... (reticências) antes doparâmetro recebidopeloconstructor deDate.Comele, nolugardeoconstructorreceberoarraycomoúnicoparâmetro,ele receberá cada elemento do array individualmente. Oselementosserãopassadosnamesmaordemparaoconstructordaclasse.

Nossocódigoestásintaticamentecorretoeostrêsparâmetrospara o constructor de Date serão passados. Porém, o mêsficará incorretosenãooajustarmos, subtraindo1de seuvalor.VejamosseuresultadonoconsoledoChrome:

MonDec12201600:00:00GMT-0200(-02)

Ospreadoperatoroperatorfuncionou,masaindaprecisamosencontrar uma forma de, antes de reposicionarmos cadaparâmetrodoarrayparaoconstructordeDate,decrementarmos1 do valor do mês. Para isto, trabalharemos com a funçãomap(),bemconhecidanomundoJavaScriptequenospermitirárealizaroajustequenecessitamos.

Primeiro,chamaremosafunçãomap()semrealizarqualquertransformação,apenasparaentendermosemqual lugardonossocódigoeledeveentrar:

//client/app/controllers/NegociacaoController.js//códigoanterioromitido

adiciona(event){

event.preventDefault();

3.8RESOLVENDOUMPROBLEMACOMOPARADIGMAFUNCIONAL 79

Page 103: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

letdata=newDate(...this._inputData.value.split('-').map(function(item){

returnitem;})

);console.log(data);

}

//códigoposterioromitido

A lógica passada para map() atuará em cada elemento doarray,retornandoumnovoarraynofinal.Masdojeitoqueestá,oarray resultante será idêntico ao array que estamos querendotransformar.Precisamosagoraaplicaralógicaquefaráoajustedomês, subtraindo 1 de seu valor. Mas como conseguiremosidentificar seestamosnoprimeiro, segundoou terceiroelementoda lista? Essa informação é importante, porque queremos alterarapenasosegundo.

Aboanotíciaéqueafunçãomap()podedisponibilizarparanósaposiçãodoelementoiteradonosegundoparâmetropassadoparaela:

//client/app/controllers/NegociacaoController.js//códigoanterioromitido

adiciona(event){

event.preventDefault();

letdata=newDate(...this._inputData.value.split('-').map(function(item,indice){

//lembre-sedequearrayscomeçamcomíndice0,//logo,1éosegundoelemento!

80 3.8RESOLVENDOUMPROBLEMACOMOPARADIGMAFUNCIONAL

Page 104: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

if(indice==1){//oresultadoaquiseráumnúmero,conversãoimp

lícitareturnitem-1;

}returnitem;

}));console.log(data);

}

//códigoposterioromitido

Quandoindice for1, significa que estamos iterando nosegundoelemento,aquelequecontémovalordomêsequeprecisaserajustado.Nacondiçãoif, realizamos facilmenteesseajuste.No entanto, sabemos que os elementos do array que estamositerando são do tipo string , sendo assim, qual a razão daoperaçãodesubtraçãofuncionar?

Quando o interpretador do JavaScript detecta operações demultiplicação, divisão e subtração envolvendo uma string e umnúmero,pordebaixodospanostentaráconverterimplicitamenteastringemumnúmero.Casoastringnãorepresenteumnúmero,oresultadoseráNaN(NotaNumber).

Por fim, o resultado bem-sucedido da operação resultará emum novo número. Operações de soma não são levadas emconsideraçãona conversão implícita, porque o interpretador nãoterácomosabersedeveconcatenarouconverteronúmero.

Veremos se o código vai funcionar. No formulário,preencheremosocampodadatacom12/11/2016.Destavez,adataqueapareceránoconsoleestarácorreta.

3.8RESOLVENDOUMPROBLEMACOMOPARADIGMAFUNCIONAL 81

Page 105: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Figura3.5:Datascorrespondentes

Nossocódigofuncionaperfeitamenteeconseguimosresolveroproblema.Porém,cangaceiros falampouco, sãohomensdeação.Será que podemos escrever menos código e conseguir o mesmoresultado?Sim,atravésdomódulodoíndice.

Namatemática,módulo é o resto da divisão. No console doChrome,vamosrealizarumteste:

2%2//omóduloé0,poisesteéorestodadivisãode2por2

Vejamosoutroexemplo:

3%2//omóduloserá1,poiséorestodadivisãode3por2

Osíndicesdonossoarraysão0,1e2, respectivamente.Sendo assim, se formos calcular omódulo de cadaumdeles pordois,temososmódulos:

Recordaréviver

82 3.8RESOLVENDOUMPROBLEMACOMOPARADIGMAFUNCIONAL

Page 106: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

0%2//móduloé01%2//móduloé12%2//móduloé0

Vejaqueéjustamenteíndicedomêsquepossuioresultado1,justamenteovalorqueprecisamossubtrair.Porfim,nossocódigoficaráassim:

//códigoanterioromitido

adiciona(event){

event.preventDefault();

letdata=newDate(...this._inputData.value.split('-').map(function(item,indice){returnitem-indice%2;

}));

console.log(data);}

//códigoposterioromitido

Seestamosnaprimeiraposiçãodoarray,ovalordeindiceé0.Porisso,oresultadodeindice%2seráiguala0também.Se subtrairmos este valor de item, nadamudará.Mas quandoestivermosna segundaposiçãodo array, oindice será igual a1.Agora,quandocalcularmos1módulode2,oresultadoserá1.Equandoestivermosnaterceiraposiçãodoarray,2módulode2,tambémseráiguala0.Nãodiminuiremosnadadovalordoitem.Dessaformaconseguimosevitaracriaçãodeumif:

3.8RESOLVENDOUMPROBLEMACOMOPARADIGMAFUNCIONAL 83

Page 107: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Figura3.6:Valoresdosmódulos

Atéaqui,onossocódigoficouassim:

//client/app/controllers/NegociacaoController.js

classNegociacaoController{

constructor(){

let$=document.querySelector.bind(document);this._inputData=$('#data');this._inputQuantidade=$('#quantidade');this._inputValor=$('#valor');

}

adiciona(event){

event.preventDefault();

letdata=newDate(...this._inputData.value.split('-').map(function(item,indice){returnitem-indice%2;

}));

console.log(data);

84 3.8RESOLVENDOUMPROBLEMACOMOPARADIGMAFUNCIONAL

Page 108: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

}}

Seeleforexecutado,veremosqueadataquesurgiránoconsoleserá12denovembrode2016.

Podemosaindadeixaronossocódigomenosverboso,usandouma forma diferente de se declarar funções, usada a partir daversãoES2015(ES6).Falamosdasarrowfunctions.

O termo arrow traduzido para o português significa flecha.Com estas "funções flecha", podemos eliminar a palavrafunctiondocódigo.Massesimplesmenteapagarmosapalavraetentarmosexecutarocódigo,teremosumerrodesintaxe,queseráapontado no navegador. Para emitirmos o termo, teremos deadicionar=>(olhaaflechaaqui!).Elasinalizaráquetemosumaarrowfunctionquereceberádoisparâmetros.

//client/app/controllers/NegociacaoController.js//códigoanterioromitido

adiciona(event){

event.preventDefault();

letdata=newDate(...this._inputData.value.split('-').map((item,indice)=>{

returnitem-indice%2;})

);

console.log(data);

3.9 ARROW FUNCTIONS: DEIXANDO OCÓDIGOAINDAMENOSVERBOSO

3.9ARROWFUNCTIONS:DEIXANDOOCÓDIGOAINDAMENOSVERBOSO 85

Page 109: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

}

//códigoposterioromitido

Quandousamosasintaxedaflecha,jásabemosquesetratadeumaarrowfunction.Seatualizarapáginanonavegador,veremosquetudofunciona.

Agora, uma pergunta: no bloco da arrow functions, quantasinstruçõesnós temos?Apenasuma:returnitem-indice%2.Alinhaestádentrodafunçãomap():

//analisandoumtrechodocódigo

.map((item,indice)=>{returnitem-indice%2;

});

Quandonossaarrowfunctionpossuiduasoumaisinstruções,somosobrigadosautilizarobloco{}, inclusiveaadicionarumreturn. No entanto, como nosso código possui apenas uma

instrução,épermitidoremoverobloco{},inclusiveainstruçãoreturn. Isso pois ela é adicionada implicitamente pela arrow

function.Nossocódigosimplificadoficaassim:

//client/app/controllers/NegociacaoController.js//códigoanterioromitido

adiciona(event){

event.preventDefault();

letdata=newDate(...this._inputData.value.split('-').map((item,indice)=>item-indice%2)

);

console.log(data);}

86 3.9ARROWFUNCTIONS:DEIXANDOOCÓDIGOAINDAMENOSVERBOSO

Page 110: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

//códigoposterioromitido

Vejamcomoocódigoficoumaissimples!

Se rodarmos o código no navegador, o formulário estáfuncionandonormalmente,eadataaparecerácomodesejamosnoconsole. Nós conseguimos reduzir a "verbosidade" do códigousando uma arrow function. Agora, só nos resta criar umainstânciadeNegociacaocomosdadosdoformulário:

//client/app/controllers/NegociacaoController.js//códigoanterioromitido

adiciona(event){

event.preventDefault();

letdata=newDate(...this._inputData.value.split('-').map((item,indice)=>item-indice%2)

);

letnegociacao=newNegociacao(data,parseInt(this._inputQuantidade.value),parseFloat(this._inputValor.value)

);

console.log(negociacao);}//códigoposterioromitido

Nossa saga em lidar com datas ainda não terminou. Somoscapazes de criar um novo objeto Date a partir dos dados doformulário,mascomofaremossequisermosexibirumDateparao usuárionoformatodia/mês/ano?Preparadosparaopróximocapítulo?

3.9ARROWFUNCTIONS:DEIXANDOOCÓDIGOAINDAMENOSVERBOSO 87

Page 111: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

O código completo deste capítulo você encontra emhttps://github.com/flaviohenriquealmeida/cangaceiro-javascript/tree/master/03

88 3.9ARROWFUNCTIONS:DEIXANDOOCÓDIGOAINDAMENOSVERBOSO

Page 112: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

CAPÍTULO4

"Conto ao senhor é o que eu sei e o senhor não sabe; masprincipalquerocontaréoqueeunãoseisesei,equepodeserqueosenhorsaiba."-GrandeSertão:Veredas

Jásomoscapazesde instanciarumanegociaçãocombasenosdados do formulário. Em breve, precisaremos exibir seus dadosdentro da <table> de client/index.html. Mas será que aexibiçãodadataseráamigável?

Vamos alterar o método adiciona() deNegociacaoController para que seja impressa a data da

negociaçãocriadaemvezdoobjetointeiro:

//client/src/app/controllers/NegociacaoController.js//códigoanterioromitido

adiciona(event){

//códigoanterioromitido

//acessandoogetterdadataexibindo-anoconsole

DOISPESOS,DUASMEDIDAS?

4DOISPESOS,DUASMEDIDAS? 89

Page 113: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

console.log(negociacao.data);}

//códigoposterioromitido

Agora,paraadata12/11/2016 informada,suaapresentaçãonoconsoleficanesteformato:

SatNov12201600:00:00GMT-0200(BRST)

Apesar de ser uma informação válida, ficaria muito maissimples e direto exibirmos para o usuário a data no formatodia/mês/ano . É um problema que precisamos resolver, pois

muito embreve incluiremosnegociações emnossa tabela, eumadas colunas se refere à propriedade data de uma negociação.Faremosumexperimento.

Aindanométodoadiciona(),vamosimprimiradatadestamaneira:

//client/src/app/controllers/NegociacaoController.js//códigoanterioromitido

adiciona(event){

//códigoanterioromitido

letdiaMesAno=negociacao.data.getDate()+'/'+negociacao.data.getMonth()+'/'+negociacao.data.getFullYear();

console.log(diaMesAno);}

//códigoposterioromitido

Com o getDate() retornaremos o dia e depois, com ogetMonth(), retornaremos omês e, com getFullYear(), o

ano.Todosforamconcatenadoscomadata.

90 4DOISPESOS,DUASMEDIDAS?

Page 114: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Masadataqueapareceránoconsoleaindanãoseráacorreta:

Figura4.1:Dataerradanoconsole

Noconsole,vemosomês10.Issoaconteceporqueeleveiodeumarrayquevaide0a11.Então,seadatagravadafornomês11,eleseráimpressonomês10.

Umatentativaparasolucionarmosoproblemaésomando+1aoretornodegetMonth():

//client/src/app/controllers/NegociacaoController.js//códigoanterioromitido

adiciona(event){

//códigoanterioromitido

letdiaMesAno=negociacao.data.getDate()+'/'+negociacao.data.getMonth()+1+'/'+negociacao.data.getFullYear();

4DOISPESOS,DUASMEDIDAS? 91

Page 115: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

console.log(diaMesAno);}

//códigoposterioromitido

Figura4.2:Dataesquisita

Contudo,o resultadoaindanão éo esperado e aindaexibeomêscomo"101",umastring!Aliás,esseéumproblemaclássicodeconcatenaçãocomquetodoprogramadorJavaScriptsedeparaquandoestácomeçandoseuprimeirocontatocomalinguagem.

Para resolvermos o problema da precedência dasconcatenações, basta adicionarmos parênteses na operação quequeremos que seja processada primeiro, antes de qualquerconcatenação:

//client/src/app/controllers/NegociacaoController.js//códigoanterioromitido

adiciona(event){

//códigoanterioromitido

letdiaMesAno=negociacao.data.getDate()+'/'+(negociacao.data.getMonth()+1)+'/'+negociacao.data.getFullYear();

console.log(diaMesAno);}

//códigoposterioromitido

92 4DOISPESOS,DUASMEDIDAS?

Page 116: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Básico de JavaScript. Um teste demonstra o texto correto deexibição da data. Podemos tornar ainda melhor nosso código,principalmentequandooassuntoélidarcomdatas.

Empoucas linhas, garantimos a conversão de uma string emobjetoDate, inclusive o caminho inverso, umDate em umastringno formatodia/mês/ano.No entanto, em todo lugardanossaaplicaçãoqueprecisarmosrealizaressasoperações, teremosderepetirumcódigoquejáescrevemos.

Vamos criar o novo arquivoclient/app/ui/converters/DateConverter.js . Como

sempre, já vamos importar o arquivo em client/index.htmlparanãocorrermosoriscodeesquecermosdeimportá-lo:

<!--client/index.html--><!--códigoanterioromitido-->

<scriptsrc="app/domain/negociacao/Negociacao.js"></script><scriptsrc="app/controllers/NegociacaoController.js"></script><scriptsrc="app/app.js"></script><scriptsrc="app/ui/converters/DateConverter.js"></script>

<!--códigoposterioromitido-->

No arquivo que acabamos de criar, vamos declarar a classeDateConvertercomoesqueletodedoismétodosquerealizarãoaconversão:

//client/app/ui/converters/DateConverter.js

classDateConverter{

4.1 ISOLANDO A RESPONSABILIDADE DECONVERSÃODEDATAS

4.1ISOLANDOARESPONSABILIDADEDECONVERSÃODEDATAS 93

Page 117: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

paraTexto(data){

}

paraData(texto){

}}

Teremos pouco esforço para implementá-lo, pois podemoscopiaralógicadeconversãoespalhadapelométodoadicionadeNegociacaoController,sófaltandoadequarocódigoaonome

dosparâmetrosrecebidospelosmétodos:

//client/app/ui/converters/DateConverter.js

classDateConverter{

paraTexto(data){

returndata.getDate()+'/'+(data.getMonth()+1)+'/'+data.getFullYear();

}

paraData(texto){

returnnewDate(...texto.split('-').map((item,indice)=>item-indice%2));

}}

Agora, precisamos alterar a classe NegociacaoController.No seu método adiciona() , usaremos a nova classe queacabamosdecriar:

//client/app/controllers/NegociacaoController.js//códigoanterioromitido

adiciona(event){

94 4.1ISOLANDOARESPONSABILIDADEDECONVERSÃODEDATAS

Page 118: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

event.preventDefault();

letconverter=newDateConverter();

letdata=converter.paraData(this._inputData.value);

letnegociacao=newNegociacao(data,parseInt(this._inputQuantidade.value),parseFloat(this._inputValor.value)

);

letdiaMesAno=converter.paraTexto(negociacao.data);

console.log(diaMesAno);}

//códigoposterioromitido

4.1ISOLANDOARESPONSABILIDADEDECONVERSÃODEDATAS 95

Page 119: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Figura4.3:Negociaçãonocontroller

Adatatambéméimpressacorretamentenosdoisformatos.Noentanto, talvez vocês possam estar se perguntando como ooperadornew funcionoucomDateConverter, umavezque aclassenãopossuiumconstructor.Nãoaconteceunenhumerroporque classes quenãodefinemumconstructor ganham umpadrão.

96 4.1ISOLANDOARESPONSABILIDADEDECONVERSÃODEDATAS

Page 120: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

NósconseguimosisolarocódigodentrodoHelper.Masseráquepodemosmelhorá-loaindamais?

Aprendemos que instâncias de uma classe podem terpropriedadesequecadainstânciapossuiseuprópriovalor.Masseprocurarmos por propriedades de instância na classeDateConverter , não encontraremos nenhuma. Sendo assim,

estamos criandouma instânciadeDateConverter apenasparapodermos chamar seusmétodos que não dependem de nenhumdadodeinstância.

Quando temos métodos que não fazem sentido seremchamados de uma instância, como no caso do métodoparaTexto() que criamos, podemos chamá-losdiretamenteda

classe na qual foram declarados, adicionando o modificadorstaticantesdonomedométodo:

//client/app/ui/converters/DateConverter.js

classDateConverter{

staticparaTexto(data){

returndata.getDate()+'/'+(data.getMonth()+1)+'/'+data.getFullYear();

}

staticparaData(texto){

returnnewDate(...texto.split('-').map((item,indice)=>item-indice%2));

}}

4.2MÉTODOSESTÁTICOS

4.2MÉTODOSESTÁTICOS 97

Page 121: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Agora, podemos chamar os métodos diretamente da classe.AlterandoocódigodeNegociacaoController:

//client/app/controllers/NegociacaoController.js//códigoanterioromitido

adiciona(event){

event.preventDefault();

//CHAMANDOOMÉTODOESTÁTICO

letnegociacao=newNegociacao(DateConverter.paraData(this._inputData.value),parseInt(this._inputQuantidade.value),parseFloat(this inputValor value)

);

console.log(negociacao.data);

//CHAMANDOOMÉTODOESTÁTICO

letdiaMesAno=DateConverter.paraTexto(negociacao.data);console.log(diaMesAno);

}

//códigoposterioromitido

98 4.2MÉTODOSESTÁTICOS

Page 122: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Figura4.4:Negociaçãonoconsole

Vimos uma novidade em termos de Orientação a Objeto: aclasse DateConverter tem métodos estáticos, o que tornadesnecessáriaacriaçãodeumainstância.

Apesarde funcional,nada impedeoprogramadordesavisadode criar uma instância de DateConverter e, a partir dessainstância,tentaracessaralgummétodoestático.Issoresultarianoerrodemétodooufunçãonãodefinida.

A boa notícia é que podemos pelo menos avisar aodesenvolvedor que determinada classe não deve ser instanciadaescrevendoumconstrutorpadrãoquelanceumaexceçãocomumamensagemclaradequeelenãodeveriafazerisso:

//client/app/ui/converters/DateConverter.js

classDateConverter{

4.2MÉTODOSESTÁTICOS 99

Page 123: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

constructor(){

thrownewError('Estaclassenãopodeserinstanciada');}

//códigoposterioromitido

NopróprioconsoledoChromepodemoscriaruma instânciade DateConverter e vermos o resultado. No console,digitaremos:

//dáerro!x=newDateConverter()

Veremososeguinteretorno:

Figura4.5:Mensagemdeerro

Aoverestamensagem,oprogramadorsaberáquetrabalhamoscom métodos estáticos. Se clicarmos no erro, veremos qual é aclasseeondeestáoproblema.

Ainda podemos melhorar um pouco mais a legibilidade danossa classe com um recurso simples, porém poderoso, que

100 4.2MÉTODOSESTÁTICOS

Page 124: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

veremosaseguir.

Vamos voltar ao arquivo DateConverter.js e analisar ométodoparaTexto.Nele,realizamosconcatenaçõesenvolvendostringsenúmeros:

//client/app/ui/converters/DateConverter.js

//códigoanterioromitido

staticparaTexto(data){

returndata.getDate()+'/'+(data.getMonth()+1)+'/'+data.getFullYear();

}

//códigoposterioromitido

Inclusivetivemosdeenvolveraexpressão(data.getMonth()+1)entreparênteses,paraqueelativesseprecedência.Noentanto,apartirdoES2015 (ES6), foi introduzidana linguagem templateliteral que nada mais são do que uma forma diferente de sedeclararstringsequeevitaoprocessodeconcatenação.

Atravésdoconsole,vamosrelembraraconcatenaçãoclássica.Primeiramente,digitaremosnele:

nome='Flávio';idade=18;console.log('Aidadede'+nome+'é'+idade+'.');

O primeiro passo é alterarmos a linha do nosso últimoconsole.log,queficaráassim:

nome='Flávio';

4.3TEMPLATELITERAL

4.3TEMPLATELITERAL 101

Page 125: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

idade=18;console.log(`Aidadedenomeéidade.`)

Observe que a string foi declara entre ` (backtick), mas seexecutarmosocódigodestaforma,seráexibidaafrase:Aidadedenomeé 18.. Ele não entendeu que o conteúdo da variávelnome deve ser considerado na template literal. Para que isso

aconteça, precisamos colocar todas as variáveis que desejamosdentrodaexpressão${}:

nome-'Flávio';idade=18;console.log(`Aidadede${nome}é${idade}.`)

Com o uso de ${} dentro da template literal, ele fará omecanismo de interpolação. A expressão vai interpolar oconteúdodas variáveisnome eidade na string.Essa forma émenos sujeita a erro do que a anterior, que continha váriasconcatenações, principalmente se a quantidade de variáveisenvolvidasfosseaindamaior.

Agora,podemostransporoconhecimentoaquiadquiridoparanossa classe DateConverter. Seu método paraTexto ficaráassim:

//client/app/ui/converters/DateConverter.js

classDateConverter{

constructor(){

thrownewError('Estaclassenãopodeserinstanciada');}

staticparaTexto(data){

return`${data.getDate()}/${data.getMonth()+1}/${data.getFullYear()}`;

102 4.3TEMPLATELITERAL

Page 126: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

}

//códigoposterioromitido

Desta vez, nem foi necessário adicionar parênteses, porquecadaexpressão seráavaliada individualmenteprimeiro,para logoemseguidaseuresultadoserinterpoladocomastring.

A terminologia "Template Literal" substitui o termo"TemplateString"nofinaldaimplementaçãodaespecificaçãodoESCMASCRIPT2015(ES6).

Este foi o nosso primeiro contato com o template literal.Veremosqueesteéumrecursomuitopoderosoaolongodonossoprojeto, colocando-o ainda mais à prova. Porém, ainda nãoacabou.

Nossa classe DateConverter está cada vez melhor.Entretanto, o que acontecerá se passarmos para seu métodoparaData a string11/12/2017, quenão segueopadrão com

hífenestipulado?Vamostestarnoconsole:

//testandonoconsoledate=DateConverter.paraData('11/12/2017')

Seráimpresso:

InvalidDate

Como o método internamente depende da estrutura que

4.4ABOAPRÁTICADOFAIL-FAST

4.4ABOAPRÁTICADOFAIL-FAST 103

Page 127: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

recebe uma data no formato dia/mês/ano, trocar o separadorfará com que o split usado internamente por paraDataretorneumvalorinválidoparaoconstructordeDate.Demossorte, já pensou se ele devolvesse uma Data, mas diferente daqualdesejamos?

Podemosusaraboapráticadofail-fast,emquevalidamososparâmetrosrecebidospelométodoejáfalhamosantecipadamenteantes que eles sejamusados pela lógica do programa.Mas claro,fornecendo uma mensagem de erro clara para o desenvolvedorsobreoproblemaocorrido.

Vamos apelar um pouquinho para as expressões regularesparaverificarseotextorecebidopelométodoparaDatasegueopadrãodia/mês/ano.Aexpressãoqueusaremoséaseguinte:

/aexpressãoqueutilizaremos^\d{4}-\d{2}-\d{2}$

Nela,estamosinteressadosemqualquertextoquecomececomquatro dígitos, seguido de um hífen e de um número com doisdígitos.Porfim,elaverificaseotextoterminacomumnúmerodedoisdígitos.

//client/app/ui/converters/DateConverter.js

classDateConverter{

//códigoanterioromitido

staticparaData(texto){

if(!/^\d{4}-\d{2}-\d{2}$/.test(texto)){thrownewError('Deveestarnoformatoaaaa-mm-dd');

}

returnnewDate(...texto.split('-').map((item,indice)=>

104 4.4ABOAPRÁTICADOFAIL-FAST

Page 128: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

item-indice%2));}

//códigoposterioromitido

A linha comothrownew só será executada se oif forfalse.Porisso,usamososinalde!.Aindapodemosnoslivrardoblocodoifdestaforma:

//client/app/ui/converters/DateConverter.js

classDateConverter{

//códigoanterioromitido

staticparaData(texto){

if(!/^\d{4}-\d{2}-\d{2}$/.test(texto))thrownewError('Deveestarnoformatoaaaa-mm-dd');

returnnewDate(...texto.split('-').map((item,indice)=>item-indice%2));

}

//códigoposterioromitido

Apesardeoif não termais um bloco, o interpretador doJavaScript entende que a próxima instrução imediatamente apósif será executada apenas se a condição fortrue. As demais

instruçõesnãosãoconsideradascomofazendopartedoblocodoif.Épor issoquea instruçãothrownew... foi indentada,

paradarumapistavisualparaoprogramadordequeelapertenceaoif.

Depoisderecarregarmosnossapágina,podemosrealizartestesnoconsole.Primeiro,comumastringválida:

DateConverter.paraData('2017-11-12')SunNov12201700:00:00GMT-0200(BRST)

4.4ABOAPRÁTICADOFAIL-FAST 105

Page 129: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

A data exibida está correta. Agora, forçaremos o erro noconsoleparavermosoqueacontece.

DateConverter.paraData('2017/11/12')UncaughtError:Deveestarnoformatoaaaa-mm-dd(..)

Perfeito, mas o que acontecerá se, no campo da data,digitarmosumanocommaisdequatrodígitos?Amensagemdeerro continuará sendo impressa no console. Mais tarde,aprenderemos a lidar com este tipo de erro, exibindo umamensagemmaisamigávelparaousuário.Porhora,ocódigoqueescrevemos é suficiente para podermos avançar com nossaaplicação.

Agora que já lidamos com as conversões de data, podemosatacaroutrorequisitodaaplicação,alistadenegociações.

O código completo deste capítulo você encontra emhttps://github.com/flaviohenriquealmeida/cangaceiro-javascript/tree/master/04

106 4.4ABOAPRÁTICADOFAIL-FAST

Page 130: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

CAPÍTULO5

"Naturezadagentenãocabeemnenhumacerteza." -GrandeSertão:Veredas

Todas as negociações cadastradas pelo usuário precisam serarmazenadas em uma lista que só deve permitir inclusões enenhumoutrotipodeoperaçãoalémdeleitura.Essarestriçãonãonos permite usar um simples array do JavaScript, devido àampla gama de operações que podemos realizar com ele. Noentanto, o array é uma excelente estrutura de dados paratrabalharmos.Eagora?

Umasoluçãoécriarmosumaclassequeencapsuleoarraydenegociações. O acesso ao array só poderá ser feito através dosmétodos dessa classe, pois estes implementam as regras quequeremosseguir.Emsuma,criaremosummodelodenegociações.

OBANDODEVESEGUIRUMAREGRA

5.1CRIANDOUMNOVOMODELO

5OBANDODEVESEGUIRUMAREGRA 107

Page 131: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Vamos criar o arquivoclient/app/domain/negociacao/Negociacoes.js . A nova

classecriadaterácomopropriedadeumarraydenegociaçõessemqualqueritem:

//client/app/domain/negociacao/Negociacoes.js

classNegociacoes{

constructor(){

this._negociacoes=[];}

adiciona(negociacao){

this._negociacoes.push(negociacao);}

}

Observequeusamosoprefixo_ para indicar que a lista sópodeseracessadapelosmétodosdaprópriaclasse.Devidoaessarestrição, adicionamos ométodo adiciona, que receberá umanegociação. Precisamos tambémde ummétodo quenos permitaleralistadenegociações.

VamoscriarométodoparaArray que, ao ser chamado eminstâncias de Negociacoes , retornará o array internoencapsuladopelaclasse:

classNegociacoes{

constructor(){

this._negociacoes=[];}

adiciona(negociacao){

108 5.1CRIANDOUMNOVOMODELO

Page 132: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

this._negociacoes.push(negociacao);}

paraArray(){

returnthis._negociacoes;}

}

Depois,noindex.html,temosdeimportaranossaclasse.

<!--códigoanterioromitido-->

<scriptsrc="app/domain/negociacao/Negociacao.js"></script><scriptsrc="app/controllers/NegociacaoController.js"></script><scriptsrc="app/app.js"></script><scriptsrc="app/ui/converters/DateConverter.js"></script>

<!--novoscriptaqui!-->

<scriptsrc="app/domain/negociacao/Negociacoes.js"></script>

<!--códigoposterioromitido-->

De volta ao NegociacaoController , adicionaremos apropriedade negociacoes que receberá uma instância deNegociacoes:

//client/app/controllers/NegociacaoController.js

classNegociacaoController{

constructor(){

let$=document.querySelector.bind(document);this._inputData=$('#data');this._inputQuantidade=$('#quantidade');this._inputValor=$('#valor');this._negociacoes=newNegociacoes();

}//códigoposterioromitido

Agora, no método adiciona do nosso controller, vamos

5.1CRIANDOUMNOVOMODELO 109

Page 133: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

adicionar a negociação criada com os dados do formulário emnossalistaatravésdométodothis._negociacoes.adiciona().Vamos aproveitar e remover o código que exibia a data danegociaçãoformatada,poiselenãoémaisnecessário:

//client/app/controllers/NegociacaoController.js

//códigoposterioromitidoadiciona(event){

event.preventDefault();

letnegociacao=newNegociacao(DateConverter.paraData(this._inputData.value),parseInt(this._inputQuantidade.value),parseFloat(this._inputValor.value)

);

//incluianegociaçãothis._negociacoes.adiciona(negociacao);

//imprimealistacomonovoelementoconsole.log(this._negociacoes.paraArray());

}//códigoposterioromitido

Pefeito,tudonolugar,jápodemostestar.

A cada inclusão de uma nova negociação, exibiremos noconsole o estado atual da lista encapsulada por Negociacoes.Contudo, essa nova solução parece não ter funcionado, poisqualquertesterealizadoexibiránoconsoleamensagem:

UncaughtReferenceError:NegociacoesisnotdefinedatnewNegociacaoController(NegociacaoController.js:9)atapp.js:1

5.2OTENDÃODEAQUILESDOJAVASCRIPT

110 5.2OTENDÃODEAQUILESDOJAVASCRIPT

Page 134: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

O problema é que, em app.js , estamos criando umainstância de NegociacaoController. Esta classe, por sua vez,dependedaclasseNegociacoesqueaindanãofoicarregada!Fazsentido, pois se olharmos a ordem de importação de scripts emclient/index.html,aimportaçãodeNegociacoes.jsfoifeitaapósapp.js:

<!--client/index.html--><!--códigoanterioromitido-->

<scriptsrc="app/domain/negociacao/Negociacao.js"></script><scriptsrc="app/controllers/NegociacaoController.js"></script><scriptsrc="app/app.js"></script><scriptsrc="app/ui/converters/DateConverter.js"></script>

<!--precisavirantesdeapp.js--><scriptsrc="app/domain/negociacao/Negociacoes.js"></script>

<!--códigoposterioromitido-->

EssadependênciaentrescriptséumdostendõesdeAquilesdoJavaScript. Isso faz com que o desenvolvedor assuma aresponsabilidadedeimportarosscriptsdesuaaplicaçãonaordemcorreta. Aprenderemos a solucionar esse problema através dosmódulosdoES2015(ES6),masaindanãoéahoradeabordarmosesse assunto, pois temos muita coisa a aprender antes. Porenquanto,vamosnospreocuparcomaordemdeimportaçãodosscripts.

Vamos ajustar o client/index.html, movendo app.jscomoúltimoscriptaserimportado:

<!--client/index.html--><!--códigoanterioromitido-->

<scriptsrc="app/domain/negociacao/Negociacao.js"></script><scriptsrc="app/controllers/NegociacaoController.js"></script><scriptsrc="app/ui/converters/DateConverter.js"></script>

5.2OTENDÃODEAQUILESDOJAVASCRIPT 111

Page 135: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

<scriptsrc="app/domain/negociacao/Negociacoes.js"></script><!--app.jsagorasempreseráoúltimoscriptporqueeleéoresponsáveleminstanciarNegociacaoControllerqueporconseguintedependedeoutrasclasses-->

<scriptsrc="app/app.js"></script><!--códigoposterioromitido-->

Agora nosso teste exibe a lista no console assim quecadastramosumanovanegociação:

Figura5.1:Negociaçãonoconsole

Tudo funciona, mas os dados do formulário continuampreenchidos depois do cadastro. É umaboa prática limparmos oformuláriopreparando-oparaapróximainclusão.

Vamos criar o método privado _limpaFormulario(), queconteráalógicaquelimparánossoformulário.

//client/app/controllers/NegociacaoController.js

classNegociacaoController{

112 5.2OTENDÃODEAQUILESDOJAVASCRIPT

Page 136: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

//códigoanterioromitido

_limpaFormulario(){

this._inputData.value='';this._inputQuantidade.value=1;this._inputValor.value=0.0this._inputData.focus();

}}

Agora, vamos chamar o método depois de adicionarmos anegociaçãonalista:

//client/app/controllers/NegociacaoController.js

classNegociacaoController{

//códigoanterioromitido

adiciona(event){

//códigoanterioromitido

//limpandooformuláriothis._limpaFormulario();

}

//coódigoposterioromitido}

Podemos incluir umanovanegociação, queo console exibirácorretamentealistacomseusdoisitens:

5.2OTENDÃODEAQUILESDOJAVASCRIPT 113

Page 137: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Figura5.2:Exibiçõesnoarray

Nossocódigo funciona,maspodemos torná-lo aindamelhor.Quetalisolarmosocódigoqueinstanciaumanegociaçãocombasenosdadosdo formulárioemummétodoprivadodaclasse?Comele,deixaremosclaranossaintençãodecriarumanovanegociação,jáqueseunomedeixaissobastanteexplícito.

NossaclasseNegociacaoControllernofinalficaráassim:

//client/app/controller/NegociacaoController.js

classNegociacaoController{

constructor(){

let$=document.querySelector.bind(document);this._inputData=$('#data');this._inputQuantidade=$('#quantidade');this._inputValor=$('#valor');this._negociacoes=newNegociacoes();

114 5.2OTENDÃODEAQUILESDOJAVASCRIPT

Page 138: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

}

adiciona(event){

event.preventDefault();this._negociacoes.adiciona(this._criaNegociacao());//mo

dificação!console.log(this._negociacoes.paraArray());this._limpaFormulario();

}

_limpaFormulario(){

this._inputData.value='';this._inputQuantidade.value=1;this._inputValor.value=0.0this._inputData.focus();

}

_criaNegociacao(){

//retornaumainstânciadenegociaçãoreturnnewNegociacao(

DateConverter.paraData(this._inputData.value),parseInt(this._inputQuantidade.value),parseFloat(this._inputValor.value)

);}

}

Depois de concluída as alterações, um novo teste exibe asinformaçõesesperadasnoconsole:

5.2OTENDÃODEAQUILESDOJAVASCRIPT 115

Page 139: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Figura5.3:Negociaçõescomoesperado

Tudoestáfuncionandocorretamente.Masseráqueinstânciasde Negociacoes realmente só permitem adicionar novasnegociaçõesequenenhumaoutraoperaçãoépermitida?Faremosumtesteaseguirparadescobrirmos.

Para sabermos se as negociações encapsuladas porNegociacao são imutáveis, podemos realizar um rápido teste,

apagandotodosos itensda lista.Para isso,vamosacessaroarrayretornado pelo método paraArray() de Negociacoes ,atribuindo0àpropriedadelengthquetodoarraypossui.Issoserásuficienteparaesvaziá-la:

//client/app/controller/NegociacaoController.js//códigoanterioromitido

adiciona(event){

event.preventDefault();this._negociacoes.adiciona(this._criaNegociacao());

5.3BLINDANDOONOSSOMODELO

116 5.3BLINDANDOONOSSOMODELO

Page 140: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

//seráqueconseguimosapagaralista?this._negociacoes.paraArray().length=0;

//qualoresultado?console.log(this._negociacoes.paraArray());this._limpaFormulario();

}

//códigoposterioromitido

Executandonossoteste:

Figura5.4:Arrayvazio

Conseguemidentificaroproblema?Qualquertrechodonossocódigo que acessar o array de negociações devolvido através dométodo paraArray terá uma referência para o mesmo arrayencapsulado de Negociacoes . Já vimos esse problema antes,quando trabalhamos com datas. Inclusive, aplicamos umaprogramação defensiva, uma estratégia que caimuito bem nestasituação.

A ideia é retornarmos um novo array, ou seja, uma novareferênciacombasenositensdoarraydenegociaçõesencapsuladopela classeNegociacoes. Criaremos um novo array através deum truque: faremos return com array vazio, seguido pelométodo concat() que receberá a lista cujos itens desejamos

5.3BLINDANDOONOSSOMODELO 117

Page 141: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

copiar:

//client/app/domain/negociacoes/Negociacoes.js

classNegociacoes{

constructor(){

this._negociacoes=[];}

adiciona(negociacao){

this._negociacoes.push(negociacao);}

paraArray(){

//retornaumanovareferênciacriadacomositensdethis._negociacoes

return[].concat(this._negociacoes);}

}

Aopassarmosothis._negociacoesdentrodoconcat(),o retornoseráumanovalista,umnovoarray.Agorasetentarmosutilizarainstruçãothis._negociacoes.paraArray().length=0 , nada acontecerá com a lista encapsulada pela instância deNegociacoes,porqueestamosmodificandoumacópiadoarray,enãooarrayoriginal.

118 5.3BLINDANDOONOSSOMODELO

Page 142: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Figura5.5:Arrayblindado

Agora que já temos tudo no lugar, vamos alterarNegociacaoController e remover do método adiciona o

códigoque tenta esvaziar a lista e todas as chamadas ao console,deixando apenas as três instruções essenciais para seufuncionamento:

//client/app/controllers/NegociacaoController.js//códigoanterioromitido

adiciona(event){

event.preventDefault();this._negociacoes.adiciona(this._criaNegociacao());this._limpaFormulario();

}

//códigoposterioromitido

Dessa forma, garantimos que nossa lista de negociação sópoderá receber novas negociações e nenhuma outra operaçãopoderáserrealizada.

Agoraquetemosnossocontrollerenossosmodelos,jáestamospreparadospara apresentarosdadosdonossomodelonapáginaindex.html.Esteéoassuntodopróximocapítulo.

5.3BLINDANDOONOSSOMODELO 119

Page 143: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

O código completo deste capítulo você encontra emhttps://github.com/flaviohenriquealmeida/cangaceiro-javascript/tree/master/05

120 5.3BLINDANDOONOSSOMODELO

Page 144: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

CAPÍTULO6

"Feitoflecha,feitofogo,feitofaca."-GrandeSertão:Veredas

Criamos as classes Negociacao e Negociacoes paramodelar as regras de negócio solicitadas. Inclusive criamosNegociaoControllerparaqueousuáriopudesseinteragircom

instâncias dessas classes.No entanto, aindanão apresentamos osestadosdessesmodelosemclient/index.html.

Precisamoscriarumasoluçãoquesejacapazde traduzirumainstânciadeNegociacoesemalgumaapresentaçãoqueousuárioentenda, por exemplo, uma <table> do HTML. Estamosquerendo implementar uma solução de View, o V do modeloMVC.

NomodeloMVC,aView éaestruturanaqualapresentamosmodelos. Um modelo pode ter diferentes apresentações, ealterações nessas apresentações não afetam o modelo. Essaseparaçãoévantajosa,porqueainterfacedousuáriomudaotempo

AMODANOCANGAÇO

6.1OPAPELDAVIEW

6AMODANOCANGAÇO 121

Page 145: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

todo e, quanto menos impacto as alterações na View causarem,mais fácil será a manutenção da aplicação. A questão agora édefinirmosaestratégiadeconstruçãodasnossasViews.

A estratégia de criação de View que adotaremos seráescrevermos a apresentação dos nossos modelos em HTML nopróprioJavaScript.

Vamos criar o arquivoclient/app/ui/views/NegociacoesView.js . Nele, vamos

declarar a classe NegociacoesView com o único método,chamadotemplate():

//client/app/ui/views/NegociacoesView.js

classNegociacoesView{

template(){

}}

A finalidade dométodo template será o de retornar umastring com a apresentação HTML que desejamos utilizar pararepresentar em breve omodelo Negociacoes. É por isso que,agora, vamos mover a <table> declarada emclient/index.html para dentro do método. Sabemos que, se

simplesmente movermos o código, o runtime do JavaScriptindicaráumasériedeerros.Éporissoquetemplate()retornaráamarcaçãoHTMLcomoumatemplateliteral:

//client/app/ui/views/NegociacoesView.js

6.2NOSSASOLUÇÃODEVIEW

122 6.2NOSSASOLUÇÃODEVIEW

Page 146: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

classNegociacoesView{

template(){

return`<tableclass="tabletable-hovertable-bordered">

<thead><tr>

<th>DATA</th><th>QUANTIDADE</th><th>VALOR</th><th>VOLUME</th>

</tr></thead>

<tbody></tbody>

<tfoot></tfoot>

</table>`

}}

Como retiramos o trecho do código referente à tabela, noindex.html,elajánãoserámaisexibidaabaixodoformulário.

Figura6.1:Formuláriosemtabela

6.2NOSSASOLUÇÃODEVIEW 123

Page 147: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Paranãocorrermosoriscodeesquecermos,vamosimportaronovo arquivo client/index.html . Mas atenção,

NegociacaoController dependerá da declaração deNegociacoesView,entãoonovoscriptdeveserimportadoantesdo script NegociacaoController.js . Nossoclient/index.htmlficaráassim:

<!--client/index.html--><!--códigoanterioromitido-->

<scriptsrc="app/domain/negociacao/Negociacao.js"></script><scriptsrc="app/controllers/NegociacaoController.js"></script><scriptsrc="app/ui/converters/DateConverter.js"></script><scriptsrc="app/domain/negociacao/Negociacoes.js"></script>

<!--importouantes!--><scriptsrc="app/ui/views/NegociacoesView.js"></script>

<scriptsrc="app/app.js"></script><!--códigoposterioromitido-->

Agora, em NegociacaoControler , adicionaremos apropriedade this._negociacoesView , que receberá umainstânciadeNegociacaoView:

//client/app/controllers/NegociacaoController.js

classNegociacaoController{

constructor(){

let$=document.querySelector.bind(document);this._inputData=$('#data');this._inputQuantidade=$('#quantidade');this._inputValor=$('#valor');this._negociacoes=newNegociacoes();this._negociacoesView=newNegociacoesView();

}//códigoposterioromitido

124 6.2NOSSASOLUÇÃODEVIEW

Page 148: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Após recarregarmos a página, vamos digitar no console asinstruções:

view=newNegociacoesView()view.template()//exibeotemplatedaview

Vemos no console o template como string, definido emNegociacoesView:

Figura6.2:Stringnoconsole

Com certeza não queremos exibir o template no console,queremosqueelesejaexibidoparaousuárioquandoelevisualizarclient/index.html.Comofaremosisso?

O primeiro passo é indicarmos o local emclient/index.html,noqualotemplatedanossaviewdeveser

exibido. Indicaremos através de uma <div> com oid="negociacoes":

<!--códigoanterioromitido--><br><divid="negociacoes"></div><!--entrounomesmolocalondetínhamosa<table>--><scriptsrc="app/domain/negociacao/Negociacao.js"></script><!--códigoposterioromitido-->

6.2NOSSASOLUÇÃODEVIEW 125

Page 149: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Excelente!Porém, comouma instânciadeNegociacaoViewsaberáquedeveserassociadaaoelementodoDOMda<div>queacabamosdeadicionar?

Adicionaremosumconstructor() emNegociacoesViewque receba um seletorCSS. A partir desse seletor, buscamos oelemento do DOM guardando-o na propriedadethis._elemento:

//client/app/ui/views/NegociacoesView.js

classNegociacoesView{

constructor(seletor){

this.elemento=document.querySelector(seletor);}

template(){

//códigodotemplateomitido}

}

Agora, em NegociacaoController , passaremos o seletor#negociacoes para o constructor() da instância deNegociacoesViewqueestamoscriando:

//client/app/ui/views/NegociacoesView.js

classNegociacaoController{

constructor(){

let$=document.querySelector.bind(document);this._inputData=$('#data');this._inputQuantidade=$('#quantidade');this._inputValor=$('#valor');this._negociacoes=newNegociacoes();

126 6.2NOSSASOLUÇÃODEVIEW

Page 150: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

//passamosparaoconstrutoroseletorCSSdeIDthis._negociacoesView=newNegociacoesView('#negociacoes

);}

//códigoposterioromitido

O código que escrevemos ainda não soluciona a questão decomo transformar o templateNegociacoesView em elementosdoDOM.Estes precisam ser inseridos como elementos filhosdoelemento indicado pelo seletor CSS passado para o seuconstructor() . A solução mora no método update que

criaremos:

//client/app/ui/views/NegociacoesView.js

classNegociacoesView{

constructor(seletor){

this._elemento=document.querySelector(seletor);}

//novométodo!update(){

this._elemento.innerHTML=this.template();}

template(){

//códigoomitido}

}

Ométodoupdate,quandochamado,atribuiráàpropriedadeinnerHTML o script devolvido pelo método template(). OinnerHTMLnaverdadeéumsetter,ouseja,quandoelerecebeseu valor, por debaixo dos panos um método é chamadoconvertendoastringemelementosdoDOM.

6.2NOSSASOLUÇÃODEVIEW 127

Page 151: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Agora, em NegociacaoController , precisamos chamarthis._negociacoesView.update() para que o template da

nossaviewsejarenderizado:

//client/app/controllers/NegociacoesController.js

classNegociacaoController{

constructor(){

let$=document.querySelector.bind(document);this._inputData=$('#data');this._inputQuantidade=$('#quantidade');this._inputValor=$('#valor');this._negociacoes=newNegociacoes();this._negociacoesView=newNegociacoesView('#negociacoes

);//atualizandoaviewthis._negociacoesView.update();

}//códigoposterioromitido

Apósasúltimasalterações,quandorecarregarmosapáginanonavegador,atabelajáserávisualizada.

Figura6.3:Tabelanapágina

Afunçãoupdate()funcionaeatabelajápodeservisualizada

128 6.2NOSSASOLUÇÃODEVIEW

Page 152: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

abaixodo formulário.Porém,osdadosdomodeloaindanãosãolevados em consideração na construção dinâmica da tabela.Podemos cadastrar quantas negociações foremnecessárias, que atabelacriadanãoexibiráosdadosdanossalista.Precisamosdeumtemplatedinâmico!

Para tornarmos nosso template dinâmico, o métodoupdate()passaráarecebercomoparâmetroomodelonoqualafunçãotemplate()devesebasear:

//client/app/ui/views/NegociacoesView.js

classNegociacoesView{

constructor(seletor){

this._elemento=document.querySelector(seletor);}

//recebeomodelupdate(model){

//repassaomodelparathis.template()this._elemento.innerHTML=this.template(model);

}

//PARÂMETROAQUI!//deveretornarotemplatebaseadonomodeltemplate(model){

//códigodotemplateomitido}

}

6.3 CONSTRUINDO UM TEMPLATEDINÂMICO

6.3CONSTRUINDOUMTEMPLATEDINÂMICO 129

Page 153: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Alteraremos NegociacaoController para passarmosthis._negociacoes como parâmetro para

this._negociacoesView.update():

//client/app/controllers/NegociacaoController.js

classNegociacaoController{

constructor(){

let$=document.querySelector.bind(document);this._inputData=$('#data');this._inputQuantidade=$('#quantidade');this._inputValor=$('#valor');this._negociacoes=newNegociacoes();this._negociacoesView=newNegociacoesView('#negociacoes

);//recebeinicialmenteomodeloqueencapsulaumalistav

aziathis._negociacoesView.update(this._negociacoes);

}//códigoposterioromitido

Sónosrestaimplementaralógicadométodotemplate()deNegociacoesView.Cadaobjetodanossalistadenegociaçõesserátransformadoemuma stringque correpondaauma<tr> comsuas <td>. Aliás, como o modelo recebido por template()possuiométodoparaArrayquenosdevolveoarrayencapsuladopor ele, podemos utilizar a poderosa funçãomap, que realizaráum"depara",deobjetoparastring,comalógicaquedefiniremos.

A transformação deve ser feita dentro de <tbody>. Sendoassim, vamos acessar omodelo através da expressão${}. Paraefeito de brevidade, temos apenas o trecho do template quemodificaremosaseguir:

<tbody>${model.paraArray().map(negociacao=>{

130 6.3CONSTRUINDOUMTEMPLATEDINÂMICO

Page 154: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

//precisaconverterantesderetornar!returnnegociacao;

})}</tbody>

Não podemos simplesmente retornar a negociação que estásendoiteradapelafunçãomap,poiscontinuaremoscomumarraydenegociações.Precisamosdeumarraydestring,emquecadaelementosejaumastringcomarepresentaçãodeuma<tr>.

Para resolvermos esta questão, para cada negociação iterada,vamos retornar uma nova template literal, representando uma<tr>equeinterpolaosdadosdanegociação:

<tbody>${model.paraArray().map(negociacao=>{

return`<tr>

<td>${DateConverter.paraTexto(negociacao.data)}</td><td>${negociacao.quantidade}</td><td>${negociacao.valor}</td><td>${negociacao.volume}</td>

</tr>`

})}</tbody>

Estamosquase lá!Agoraque temosumnovoarrayde string,precisamosconcatenarcadaelementoemumaúnica string.Esta,no final, será aplicada na propriedadeinnerHTML do elementoque associamos à view. Podemos fazer isso facilmente através dafunçãojoin(),presenteemtodoarray:

<tbody>${model.paraArray().map(negociacao=>{

return`<tr>

<td>${DateConverter.paraTexto(negociacao.data)}</td><td>${negociacao.quantidade}</td><td>${negociacao.valor}</td>

6.3CONSTRUINDOUMTEMPLATEDINÂMICO 131

Page 155: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

<td>${negociacao.volume}</td></tr>`

}).join('')}</tbody>

A função join() aceita um separador, no entanto, nãoqueremos separador algum, por isso passamos uma string embranco para ele. Por fim, não podemos nos esquecer dechamarmosthis._negociacoesView.update(this._negociacoes) logo

apósa inclusãodanegociaçãona lista,paraquenossa tabela sejarenderizadalevandoemconsideraçãoosdadosmaisatuais.

//client/app/controllers/NegociacaoController.js//códigoanterioromitido

adiciona(event){

event.preventDefault();this._negociacoes.adiciona(this._criaNegociacao());this._negociacoesView.update(this._negociacoes);this._limpaFormulario();

}

//códigoposterioromitido

Vamos ver o que será exibido no navegador, após opreenchimentodoformulário:

Figura6.4:Tabelacomvalores

Emseguida,adicionaremosumanovanegociaçãoeseusdadostambémserãoexibidosnatabela.

132 6.3CONSTRUINDOUMTEMPLATEDINÂMICO

Page 156: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Figura6.5:Tabelacomduasnegociações

Se realizarmos novas inclusões, nossa tabela será renderizadanovamente,representandooestadoatualdomodelo.

Atualmente,oinnerHTMLébemperformático.Noentanto,sea quantidade de dados a ser renderizada for exorbitante, asoluçãode incluircada<tr> individualmente sedestacaria,apesar de mais verbosa. Porém, é uma boa prática utilizarpaginação quando trabalhamos com uma massa de dadosconsiderável, o que minimizaria possíveis problemas deperformancedoinnerHTML.

De maneira elegante, usando apenas recursos do JavaScript,conseguimos fazer um template engine.Mas que tal enxugarmosumpoucomaisocódigodonossotemplate?

Todaarrowfunctionquepossuiapenasumainstruçãopodeterseubloco{}removido,inclusiveainstruçãoreturn,poiselajáassume como retorno o resultado da instrução. Alterando nossocódigo:

<tbody>${model.paraArray().map(negociacao=>`

<tr><td>${DateConverter.paraTexto(negociacao.data)}</td><td>${negociacao.quantidade}</td>

6.3CONSTRUINDOUMTEMPLATEDINÂMICO 133

Page 157: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

<td>${negociacao.valor}</td><td>${negociacao.volume}</td>

</tr>`).join('')}</tbody>

Agora, com nossa alteração, a única instrução da arrowfunction é a criação da template literal, que é retornadaautomaticamente.

Porfim,nossométodotemplatecompletoficouassim:

//client/app/ui/views/NegociacoesView.js//códigoanterioromitido

template(model){

return`<tableclass="tabletable-hovertable-bordered">

<thead><tr>

<th>DATA</th><th>QUANTIDADE</th><th>VALOR</th><th>VOLUME</th>

</tr></thead>

<tbody>${model.paraArray().map(negociacao=>`

<tr><td>${DateConverter.paraTexto(negociacao.data

)}</td><td>${negociacao.quantidade}</td><td>${negociacao.valor}</td><td>${negociacao.volume}</td>

</tr>`).join('')}

</tbody>

<tfoot></tfoot>

134 6.3CONSTRUINDOUMTEMPLATEDINÂMICO

Page 158: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

</table>`

}

//códigoposterioromitido

Nossa solução é uma caricatura da estratégia utilizada pelabiblioteca React (https://facebook.github.io/react/) pararenderização da apresentação de componentes através do JSX(https://facebook.github.io/react/docs/jsx-in-depth.html).Vejamosumexemplo:

//EXEMPLOAPENAS

letminhaLista=React.createClass({render:function(){return(<ul>{this.props.list.map(function(listValue){return<li>{listValue}</li>;

})}</ul>

)}

});

Voltandoparaanossaaplicação,sevocêachaqueterminamoscom nosso template, se enganou. Ainda precisamos completá-locomoutrainformação.

Precisamos totalizar o volume no rodapé da tabela do nossotemplate.Primeiro,naclasseNegociacoes, adicionaremosumapropriedadegetter,chamadavolumeTotal. Ela percorrerá a

6.4 TOTALIZANDO O VOLUME DENEGOCIAÇÕES

6.4TOTALIZANDOOVOLUMEDENEGOCIAÇÕES 135

Page 159: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

lista encapsulada pela classe, tornando o somatório dos volumesdasnegociações.

//client/app/domain/negociacao/Negociacoes.js

classNegociacoes{

constructor(){

this._negociacoes=[]}

//códigoanterioromitido

getvolumeTotal(){

lettotal=0;

for(leti=0;i<this._negociacoes.length;i++){total+=this._negociacoes[i].volume;

}

returntotal;}

}

Agora, vamos adicionar amarcação completa do<tdfoot>do nosso template e utilizar a expressão${} para interpolar ovolumetotal:

<tfoot><tr>

<tdcolspan="3"></td><td>${model.volumeTotal}</td>

</tr></tfoot>

Um teste demonstra que nossa solução funcionou comoesperado.

136 6.4TOTALIZANDOOVOLUMEDENEGOCIAÇÕES

Page 160: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Figura6.6:Tabelacomovolumetotal

Apesardefuncionar,alógicadogettervolumeTotalpodeserescritadeoutramaneira.

No mundo JavaScript, arrays são muito espertos. Podemosrealizar conversões através demap(), iterar em seus elementosatravés de forEach() , e concatenar seus elementos comjoin(). Inclusive, podemos reduzir todos os seus elementos a

umúnicovalorcomafunçãoreduce().

Emnossa aplicação,reduce() reduzirá todosos elementosdoarrayaumúnicovalor,nocaso,ovolumetotal.Estenadamaisé do que a soma do volume de todas as negociações.Vejamos anovaimplementação:

//client/app/domain/negociacao/Negociacoes.js//códigoanterioromitido

getvolumeTotal(){

returnthis._negociacoes.reduce(function(total,negociacao){returntotal+negociacao.volume

},0);}

//códigoposterioromitido

Antesdeentrarmosnosdetalhesdafunçãoreduce(),vamos

6.5TOTALIZANDOCOMREDUCE

6.5TOTALIZANDOCOMREDUCE 137

Page 161: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

lançar mão de uma arrow function para simplificar ainda maisnossocódigo:

//client/app/domain/negociacao/Negociacoes.js//códigoanterioromitido

getvolumeTotal(){

//agoracomarrowfunctions,semousodeblocoreturnthis._negociacoes

.reduce((total,negociacao)=>total+negociacao.volume,0);

}

//códigoposterioromitido

A função reduce() recebe dois parâmetros: a lógica dereduçãoeovalorinicialdavariávelacumuladora,respectivamente.A funçãocoma lógicade reduçãonosdáacessoaoacumulador,que chamamos de total , e ao item que estamos iterando,chamadodenegociacao.

Para cada item iterado, será retornada a soma do total atualcomovolumedanegociação.Ototalacumuladoserápassadoparaapróximachamadadafunçãoreduce,eesseprocessoserepetiráatéotérminodaiteração.Nofinal,reduce() retornaráo valorfinaldetotal.

Ao executarmos o código, vemos que ele funcionaperfeitamente:

Figura6.7:Tabelacomtotal

138 6.5TOTALIZANDOCOMREDUCE

Page 162: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Terminamosotemplatedatabela.Acadanegociaçãoincluída,a informação será exibida para o usuário com base nasinformações da lista. Mas será que podemos generalizar nossasolução?Éissoqueveremosnopróximocapítulo.

O código completo deste capítulo você encontra emhttps://github.com/flaviohenriquealmeida/cangaceiro-javascript/tree/master/06

6.5TOTALIZANDOCOMREDUCE 139

Page 163: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

CAPÍTULO7

"O correr da vida embrulha tudo, a vida é assim: esquenta eesfria,apertaedaíafrouxa,sossegaedepoisdesinquieta.Oqueelaquerdagenteécoragem."-GrandeSertão:Veredas

Já somos capazes de exibir novas negociações na tabela denegociações, no entanto, podemos melhorar a experiência dousuário exibindo uma mensagem indicando que a operação foirealizadacomsucesso.

Nolugardeusarmosumastringparaestafinalidade,criaremosa classeMensagem, que representará umamensagem em nossosistema. Ela não faz parte do domínio da aplicação que é o denegociações,porissovamoscriá-laemclient/app/ui/models:

//client/app/ui/models/Mensagem.js

classMensagem{

constructor(){

this._texto;

}

OPLANO

140 7OPLANO

Page 164: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

gettexto(){

returnthis._texto;}

}

Antes de continuarmos, importaremos o script emclient/index.html.Nãopodemosnosesquecerde importá-lo

antes de NegociacaoController.js , pois há uma ordem dedependência, algo que já vimos quando importamosNegociacoesView.

<!--client/index.html--><!--códigoanterioromitido--><scriptsrc="app/domain/negociacao/Negociacao.js"></script><scriptsrc="app/controllers/NegociacaoController.js"></script><scriptsrc="app/ui/converters/DateConverter.js"></script><scriptsrc="app/domain/negociacao/Negociacoes.js"></script><scriptsrc="app/ui/views/NegociacoesView.js"></script>

<!--importouaqui!--><scriptsrc="app/ui/models/Mensagem.js"></script>

<scriptsrc="app/app.js"></script><!--códigoposterioromitido-->

Usamos a convençãodoprefixo_ paramanter o_textoprivado, e criamos um getter para a propriedade. Mas e sequisermosalterarotextodamensagem?Paraisso,criaremosnossoprimeirosetter:

//client/app/ui/models/Mensagem.js

classMensagem{

constructor(){

this._texto;}

gettexto(){

7OPLANO 141

Page 165: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

returnthis._texto;}

settexto(texto){

this._texto=texto;}

}

Damesmamaneiraqueacessamosumgettercomosefosseumapropriedade,podemosatribuirumvaloraosettertambémdamesmamaneira.Recarregandonossapágina,podemosrealizaro seguintetestenoconsole:

mensagem=newMensagem();mensagem.texto='x';

//NOSSOTESTEALTEROUAPROPRIEDADE!console.log(mensagem.texto);

Podemosaindaadicionarumparâmetronoconstructor()daclasse,contendootextodamensagem.

//client/app/ui/models/Mensagem.js

classMensagem{

constructor(texto){

this._texto=texto;}

gettexto(){

returnthis._texto;}

settexto(texto){

this._texto=texto;}

142 7OPLANO

Page 166: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

}

Vamosfazerumnovotestenoconsole:

mensagem=newMensagem('FlávioAlmeida');

//PASSOUCORRETAMENTEOTEXTOPELOCONSTRUCTOR()console.log(mensagem.texto);

Noentanto,oqueacontecerásenãopassarmosumvalorparao constructor()?

mensagem=newMensagem();

//ORESULTADOÉUNDEFINED!console log(mensagem texto)

Nãopodemosdeixarqueissoaconteça.

Que talassumirmosumastringembranco,casooparâmetronãosejapassado?

//client/app/ui/models/Mensagem.js

classMensagem{

constructor(texto){

if(!texto){texto='';

}

this._texto=texto;}

gettexto(){

returnthis._texto;}

7.1PARÂMETRODEFAULT

7.1PARÂMETRODEFAULT 143

Page 167: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

settexto(texto){

this._texto=texto;}

}

Podemosnoslivrardoifcomoseguintetruque:

//client/app/ui/models/Mensagem.js

classMensagem{

constructor(texto){

this._texto=texto||'';}

//códigoposterioromitido

No código anterior, se o valor do parâmetro texto fordiferentedenull,undefined,0 e não for uma string embranco, ele será atribuído a this._texto ; caso contrário,receberáastringembranco.

Podemosenxugaraindamaisindicandonopróprioparâmetrodoconstructor()seuvalorpadrão,algopossívelapenasapartirdoES2015(ES6)emdiante:

//client/app/ui/models/Mensagem.js

classMensagem{

//senãoforpassado,seráumastringembrancoconstructor(texto=''){

this._texto=texto}

//códigoposterioromitido

144 7.1PARÂMETRODEFAULT

Page 168: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Este é um recurso interessante, porque podemos definir umparâmetrodefault,tantonoconstructor()comoemmétodos.

Com a classe criada, vamos adicionar a propriedadethis._mensagememNegociacaoController.js.ElaguardaráumainstânciadeMensagem:

//client/app/controllers/NegociacaoController.js

classNegociacaoController{

constructor(){

let$=document.querySelector.bind(document);this._inputData=$('#data');this._inputQuantidade=$('#quantidade');this._inputValor=$('#valor');this._negociacoes=newNegociacoes();this._negociacoesView=newNegociacoesView('#negociacoes

);this._negociacoesView.update(this._negociacoes);

//instanciandoomodelo!this._mensagem=newMensagem();

}

//códigoposterioromitido

Quando uma nova negociação for realizada, vamos atribuirumanovamensagemàpropriedadethis._mensagem.texto.

//client/app/controllers/NegociacaoController.js

//códigoanterioromitido

adiciona(event){

event.preventDefault();this._negociacoes.adiciona(this._criaNegociacao());this._mensagem.texto='Negociaçãoadicionadacomsucesso';this._negociacoesView.update(this._negociacoes);this._limpaFormulario();

7.1PARÂMETRODEFAULT 145

Page 169: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

}//códigoposterioromitido

Se preenchermos o formulário, os dados serão inseridos natabela, mas a mensagem não, porque ela ainda não possui umaapresentação,istoé,suaView.Chegouahoradecriá-la.

Vamos criar a View que representará nosso modeloMensagememclient/app/ui/views/MensagemView.js:

classMensagemView{

constructor(seletor){

this._elemento=document.querySelector(seletor);}

template(model){

return`<pclass="alertalert-info">${model.texto}</p>`;}

}

Usaremosoalertalert-info doBootstrap, seguidopelaexpressão ${model.texto} . Logo abaixo, adicionaremos ométodoupdate()quereceberáomodel.

//client/app/ui/views/MensagemView.js

classMensagemView{

constructor(seletor){

this._elemento=document.querySelector(seletor);}

7.2CRIANDOACLASSEMENSAGEMVIEW

146 7.2CRIANDOACLASSEMENSAGEMVIEW

Page 170: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

template(model){

return`<pclass="alertalert-info">${model.texto}</p>`;}

//métodoupdateupdate(model){

this._elemento.innerHTML=this.template(model);}

}

Agora vamos importar o arquivo que acabamos de criar emclient/index.html:

<!--client/index.html--><!--códigoanterioromitido-->

<scriptsrc="app/domain/negociacao/Negociacao.js"></script><scriptsrc="app/controllers/NegociacaoController.js"></script><scriptsrc="app/ui/converters/DateConverter.js"></script><scriptsrc="app/domain/negociacao/Negociacoes.js"></script><scriptsrc="app/ui/views/NegociacoesView.js"></script><scriptsrc="app/ui/models/Mensagem.js"></script>

<!--importou!--><scriptsrc="app/ui/views/MensagemView.js"></script>

<scriptsrc="app/app.js"></script>

<!--códigoposterioromitido-->

Ainda emclient/index.html, vamos adicionar a<div>com IDmensagemView para indicar o local de renderização deMensagemView:

<!--client/index.html-->

<!DOCTYPEhtml><html><head>

<metacharset="UTF-8"><metaname="viewport"content="width=device-width">

7.2CRIANDOACLASSEMENSAGEMVIEW 147

Page 171: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

<title>Negociações</title><linkrel="stylesheet"href="css/bootstrap.css"><linkrel="stylesheet"href="css/bootstrap-theme.css">

</head><bodyclass="container">

<h1class="text-center">Negociações</h1>

<!--novatag!--><divid="mensagemView"></div>

<formclass="form"><!--códigoanterioromitido-->

EmNegociacoesController,vamosadicionarapropriedade_mensagemViewquereceberáumainstânciadeMensagemView.NãopodemosnosesquecerdepassaroseletorCSSnoconstrutorda classe, para que ela encontre o elemento do DOM onde seutemplate deve ser renderizado. Inclusive, já vamos chamar seumétodo update , passando o modelo da mensagem comoparâmetro.

//client/app/controllers/NegociacaoController.js

classNegociacaoController{

constructor(){

let$=document.querySelector.bind(document);this._inputData=$('#data');this._inputQuantidade=$('#quantidade');this._inputValor=$('#valor');this._negociacoes=newNegociacoes();this._negociacoesView=newNegociacoesView('#negociacoes

);this._negociacoesView.update(this._negociacoes);this._mensagem=newMensagem();

//novapropriedade!this._mensagemView=newMensagemView('#mensagemView');this._mensagemView.update(this._mensagem);

}

148 7.2CRIANDOACLASSEMENSAGEMVIEW

Page 172: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

//códigoposterioromitido

AindaemNegociacaoController,vamosalterarseumétodoadiciona() para que chame a atualização da View logo após

receberamensagemquedefinimos:

//client/app/controllers/NegociacaoController.js

//códigoanterioromitido

adiciona(event){

event.preventDefault();this._negociacoes.adiciona(this._criaNegociacao());this._mensagem.texto='Negociaçãoadicionadacomsucesso';this._negociacoesView.update(this._negociacoes);

//atualizaaviewcomotextodamensagemqueacabamosdeatribuir

this._mensagemView.update(this._mensagem);this._limpaFormulario();

}

//códigoposterioromitido

Vamosversealgojáéexibidononavegador.

Figura7.1:Barraazul

7.2CRIANDOACLASSEMENSAGEMVIEW 149

Page 173: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Agora aparece uma barra com um fundo azul. Isto é umamensagemdoBootstrapvazia,evamosmelhorarissoembreve.Ocadastrodeusuáriospassaaexibirmensagensparaousuário.

Figura7.2:Cadastrocomsucesso

Conseguimos adicionar os dados à tabela, e a mensagem desucesso apareceu corretamente. Veja que conseguimos usar omesmo mecanismo de criação da View para lidarmos com asmensagens do sistema. Agora, vamos resolver o problema damensagemvazia.

Uma solução seria removermos a instruçãothis._mensagemView.update(this._mensagem) do

constructor()deNegociacaoController.Noentanto,vamosmanter dessa forma para padronizar nossa solução.Convencionamosqueafunçãoupdatedevesersemprechamadatodavezquecriarmosnossaview.Sendoassim,vamosàsegundasolução.

No template de MensagemView , podemos usar um ifternário que retornará um parágrafo vazio, sem as classes doBootstrap,casoamensagemestejaembranco.

150 7.2CRIANDOACLASSEMENSAGEMVIEW

Page 174: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Vamosalterarclient/app/ui/views/MensagemView.js:

//client/app/ui/views/MensagemView.js

classMensagemView{

//códigoanterioromitido

template(model){

returnmodel.texto?`<pclass="alertalert-info">${model.texto}</p>`:`<p></p>`;

}

//códigoposterioromitido}

Lembre-se deque, para o interpretador do JavaScript, umastring em branco, 0, undefine e null são consideradosfalse.Semodel.texto tiverqualquervalordiferentedosque

foramlistados,seráconsideradotrue.

Conseguimos resolver a parte dasmensagens para o usuário.Masseráqueconseguimosmelhoraraindamaisocódigo?

Temos duas Views criadas: MensagemView eNegociacoesView . Se observarmos, ambas possuem umconstructor() que recebe um seletor, além de possuírem a

propriedadeelemento.Asduastêmosmétodostemplate()eupdate().Ométodoupdate()éidênticonasduasclasses,jáotemplate() tem uma implementação diferente, apesar de

receberumúnicoparâmetroedevolversempreumastring.

7.3HERANÇAEREUTILIZAÇÃODECÓDIGO

7.3HERANÇAEREUTILIZAÇÃODECÓDIGO 151

Page 175: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Para cada nova View criada em nossa aplicação, teremos derepetir boa parte do código que já escrevemos - com exceção dafunçãotemplate(),queédiferenteparacadaclasse.Nãopareceserumtrabalhodecangaceiro ficarrepetindocódigo.Existeumasolução?

Para evitarmos a repetição, criaremos uma classe chamadaView,quecentralizarátodoocódigoqueécomumentretodasasViewsdanossaaplicação(nocaso,oconstructor()eométodoupdate()).

Vamoscriaressaclasseemclient/app/ui/views/View.js:

//client/app/ui/views/View.js

classView{

constructor(seletor){

this._elemento=document.querySelector(seletor);}

update(model){

this._elemento.innerHTML=this.template(model);}

}

Antes de continuarmos, vamos importar a classe emclient/index.html. Entretanto, ela não pode ser importada

comopenúltimoscriptdanossapágina,antesdeapp.js, comofizemoscomosdemaisscripts.TodosnossosarquivosdeViewquevamos criar dependerão diretamente da classe View , e ointerpretadorJavaScriptindicaráumerrocasoasclassesnãosejamcarregadasantesdasdemais.

Vamos importar a nova classe antes do script de

152 7.3HERANÇAEREUTILIZAÇÃODECÓDIGO

Page 176: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

NegociacoesView.js:

<!--client/index.html--><!--códigoanterioromitido-->

<scriptsrc="app/domain/negociacao/Negociacao.js"></script><scriptsrc="app/controllers/NegociacaoController.js"></script><scriptsrc="app/ui/converters/DateConverter.js"></script><scriptsrc="app/domain/negociacao/Negociacoes.js"></script>

<!--importouaqui-->

<scriptsrc="app/ui/views/View.js"></script>

<!--adeclaraçãodeNegociacoesViewdependerádadeclaraçãodeView--><scriptsrc="app/ui/views/NegociacoesView.js"></script><scriptsrc="app/ui/models/Mensagem.js"></script><scriptsrc="app/app.js"></script>

A classe View recebeu tudo o que as Views tinham emcomum: um constructor(elemento) - que guardaráinternamenteumelemento-eupdate().Vale lembrarqueométodotemplatenãopôdeserdefinidonaclasse,porquecadaViewemnossaaplicaçãopossuiráumcódigodiferente.

Agora que centralizamos o código comum em uma classe,vamos remover o construtor() e o método update() deNegociacaoViewedeMensagemView.Elasficarãoassim:

//client/app/ui/views/NegociacoesView.js

classNegociacoesView{

template(model){

//códigodotemplateomtido}

}

7.3HERANÇAEREUTILIZAÇÃODECÓDIGO 153

Page 177: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

//client/app/ui/views/MensagemView.js

classMensagemView{

template(model){

returnmodel.texto?`<pclass="alertalert-info">${model.texto}</p>`:`<p></p>`;

}}

Do jeito que está, nossa aplicação não funcionará, pois tantoNegociacoesView comoMensagemView dependemdo código

quebuscaoelementodoDOM(definidonoconstructor())eafunçãoupdate(),queforamremovidos.

Paraasclassesquealteramosvoltemafuncionar,faremoscomqueherdemocódigodaclasseView,aquelaqueagoracentralizaemumúnicolugaroconstrutor(),inclusiveafunçãoupdate.

É através da instruçãoextends que podemos herdar outraclasse.VamosalterarNegociacaoVieweMensagemView:

//client/app/ui/views/NegociacoesView.js

classNegociacoesViewextendsView{

template(model){

//códigodotemplateomtido}

}

//client/app/ui/views/MensagemView.js

classMensagemViewextendsView{

template(model){

154 7.3HERANÇAEREUTILIZAÇÃODECÓDIGO

Page 178: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

returnmodel.texto?`<pclass="alertalert-info">${model.texto}</p>`:`<p></p>`;

}}

Secadastrarmosumanovanegociação,veremosqueestátudofuncionando.

Figura7.3:Formuláriofuncionandocorretamente

A solução de template dentro domodeloMVC adotada peloautor foi a mais acessível e didática que ele encontrou paracolocar em prática recursos da linguagem JavaScript, semgastar páginas e mais páginas detalhando a criação de umframework, o que desvirtuaria a proposta do livro. Contudo,não será mera coincidência encontrar diversos conceitosapresentadosemframeworksebibliotecasdomercado.

Conseguimosevitarcódigoduplicadoegarantimosoreúsodecódigo através de herança. Contudo, ainda podemos melhorarnossocódigoumpouquinhomais.

7.3HERANÇAEREUTILIZAÇÃODECÓDIGO 155

Page 179: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Na programação orientada a objetos, existe o conceito declasses abstratas. Essas classes podem ter um ou mais métodosabstratos, que nadamais são do quemétodos que não possuemimplementação, apenas a assinatura que consiste no nome dométodoeoparâmetroquerecebe.

Esses métodos devem ser implementados pelas classes filhasqueherdamdaclasseabstrata.Nocasodométodotemplate(),éimpossívelquenossaclasseViewtenhaumaimplementaçãoparaserherdada,poiselanãotemcomosaberdeantemãoqualseráatemplateliteralretornadapelassuasclassesfilhas.

No JavaScript, não há classes abstratas, mas podemos tentaremulá-lasdaseguintemaneira.

Na classe View, vamos adicionar ométodo template(),cujaimplementaçãoconsistenolançamentodeumaexceção:

//client/app/ui/views/View.js

classView{

constructor(seletor){

this._elemento=document.querySelector(seletor);}

update(model){

this._elemento.innerHTML=this.template(model);}

//novométodo!template(model){

thrownewError('Vocêprecisaimplementarométodotempla

7.4CLASSESABSTRATAS?

156 7.4CLASSESABSTRATAS?

Page 180: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

te');}

}

Do jeito que está, todas as classes filhas que herdarem deViewganharamométodotemplate()padrão.Contudo,seessemétodonão forsobrescritopelaclasse filha,essecomportamentopadrãoresultaráemumerro,deixandoclaroparaoprogramadorqueeledeveimplementá-lo,casotenhaesquecido.

Podemosfazerumsimplestesteremovendotemporariamenteo métodotemplate()deMensagemViewerecarregandonossaaplicação.Receberemosnoconsoleaseguintemensagem:

UncaughtError:VocêprecisaimplementarométodotemplateatMensagemView.template(View.js:15)atMensagemView.update(View.js:10)atnewNegociacaoController(NegociacaoController.js:14)atapp.js:1

Diferente de outras linguagens com tipagem estática (comoJava, Rust ou C#), a exceção só será lançada em tempo deexecução. Entretanto, ainda assim é uma maneira de ajudar osdesenvolvedoresfornecendoumamensagemclaradoerroemseucódigo,casonãoimplementeosmétodos"abstratos"daclassefilha.

Emnossaaplicação,tantooconstructordeViewquantooconstructordesuasclassesfilhasrecebemamesmaquantidadede parâmetros. Contudo, quando a classe filha recebe umaquantidadedeparâmetrosdiferente,énecessárioescrevermosumpoucomaisdecódigo.

Vejamos um exemplo hipotético envolvendo as classes

7.5PARASABERMAIS:SUPER

7.5PARASABERMAIS:SUPER 157

Page 181: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

FuncionarioeGerente.

classFuncionario{

constructor(nome){

this._nome=nome;}

getnome(){

returnthis._nome;}

setnome(nome){

this._nome=nome;}

}

classGerenteextendsFuncionario{}

No exemplo anterior, a classe Gerente herdou deFuncionario e, por conseguinte, herdou seuconstrutor().

MasoqueaconteceráseGerente receber, alémdonome,umasenhadeacesso?

classGerenteextendsFuncionario{

constructor(nome,senha){/*oquefazer?*/

}}

AclasseGerenteprecisoudoseupróprioconstructor(),pois recebe dois parâmetros. Nesse caso, precisamos chamar oconstructor()daclassepai,passandoosparâmetrosdequeeleprecisa:

classGerenteextendsFuncionario{

158 7.5PARASABERMAIS:SUPER

Page 182: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

constructor(nome,senha){

super(nome);//chamaoconstructor()dopai

//propriedadeexclusivadeGerentethis._senha=senha;

}

//gettersesettersexclusivosdoGerentegetsenha(){

returnthis._senha;}

setsenha(senha){

returnthis._senha=senha;}

}

É importante que a chamada de super() seja a primeirainstrução dentro do constructor() da classe filha; casocontrário, o this da classe filha não será inicializado e ainstruçãothis._senha=senha resultará em um erro. Agoraque já fizemos o teste, podemos voltar com o métodotemplate()emMensagemView.

Aprendemosa favorecerousodelet emvezdevar nasdeclarações de variáveis, inclusive entendemos como funciona otemporaldeadzone.Noentanto,podemoslançarmãodeconst.Variáveis declaradas atravésdeconst obrigatoriamente devemser inicializadasenãopodemrecebernovasatribuições.Vejamosumexemploisolado:

7.6 ADQUIRINDO UM NOVO HÁBITO COMCONST

7.6ADQUIRINDOUMNOVOHÁBITOCOMCONST 159

Page 183: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

constnome='Flávio';nome='Almeida';//resultanoerroUncaughtTypeError:Assignmenttoconstantvariable

No exemplo anterior, não podemos usar novamente ooperador= para atribuir um novo valor a variável. O fato deusarmos essa declaração específica não quer dizer que a variávelsejaimutável.Vejamosoutroexemplo:

constdata=newDate();data.setMonth(10);//funcionaperfeitamenteconsole.log(data.getMonth());data=newDate()//resultanoerroUncaughtTypeError:Assignmenttoconstantvariable

Neste último exemplo, pormais que tenha declarado datacom const , através do método data.setMonth(10) ,conseguimos alterar o mês da data. Isso é a prova real de queconstnãocriaobjetosimutáveis.

Porfim,valemasseguintesrecomendaçõescangaceiras:

Useconstsemprequepossível.Utilize let apenas se a variável precisar recebernovas atribuições, por exemplo, uma variáveltotalizadora.Nãousevar, pois a únicamaneira da variável terescopoéquandodeclaradadentrodeumafunção.

Dessa forma, vamos alterar NegociacaoController parafazerusodeconst:

//client/app/controllers/NegociacaoController.js

classNegociacaoController{

constructor(){

160 7.6ADQUIRINDOUMNOVOHÁBITOCOMCONST

Page 184: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

//nãovamosatribuiroutorvaloràvariávelconst$=document.querySelector.bind(document);

//códigoposterioromitido}

//códigoposterioromitido

Podemos até alterar client/app/app.js para utilizarconstnadeclaraçãodavariávelcontroller:

//client/app/app.js

//USANDOCONSTconstcontroller=newNegociacaoController();

document.querySelector('.form')

.addEventListener('submit',controller.adiciona.bind(controller));

Umapequenamelhoriaqueutilizaremosaolongodoprojeto.

Será que podemos melhorar ainda mais nosso código, porexemplo, enxugando-o e removendo cada vez mais aresponsabilidadedoprogramador?Éissoqueveremosnopróximocapítulo.

O código completo deste capítulo você encontra emhttps://github.com/flaviohenriquealmeida/cangaceiro-javascript/tree/master/07

7.6ADQUIRINDOUMNOVOHÁBITOCOMCONST 161

Page 185: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

ProcuradopelaForçaVolante, famintoeabatido, ele chegouapensaremdesistirváriasvezes.Porém,avontadeinquebrantáveldedescobriroqueoesperavanofimeramaiordoqueseusofrimento.Então, ele recuperou suas forças e se pôs a andar. Durante suacaminhada,ajudouefoiajudado,lutousemserderrubado,correuefoi arrastado. Suas cicatrizes eram a marca da experiência queacumulava. Ele tinha muito o que aprender ainda, mas haviaespaçoemsuapeleparanovasexperiências.

Parte2-ForçaVolante

Page 186: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

CAPÍTULO8

"Temhoras emquepensoquea gente carecia, de repente, deacordar de alguma espécie de encanto." - Grande Sertão:Veredas

Nossa aplicação já funciona e totaliza o volume no fim databela.Masaindafaltaimplementarmosumaregradenegócionomodelo Negociacoes . Precisamos criar um método que nospermitaesvaziaralistadenegociaçõesencapsulada.Vamoscriá-lo.

Primeiro, vamos adicionar o método esvazia() emNegociacoes:

//client/app/domain/negociacao/Negociacoes.js

classNegociacoes{

constructor(){

this._negociacoes=[];}

adiciona(negociacao){

UMCANGACEIROSABEDELEGARTAREFAS

8UMCANGACEIROSABEDELEGARTAREFAS 163

Page 187: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

this._negociacoes.push(negociacao);}

paraArray(){

return[].concat(this._negociacoes);}

getvolumeTotal(){

returnthis._negociacoes.reduce((total,negociacao)=>

total+negociacao.volume,0);}

//novométodo!esvazia(){

this._negociacoes=[];}

}

O novo método, quando chamado, atribuirá um novo arraysem elementos à propriedade this._negociacoes . Agora,precisamosqueestenovométodosejachamadoquandoousuárioclicar no botão "Apagar" . Para isso, criaremos primeiro ométodoapaga()emNegociacaoController, aquelequeseráchamadoatravésdobotão"Apagar"deindex.html:

//client/app/controllers/NegociacaoController.js//códigoanterioromitido

apaga(){

this._negociacoes.esvazia();this._negociacoesView.update(this._negociacoes);this._mensagem.texto='Negociaçõesapagadascomsucesso';this._mensagemView.update(this._mensagem);

}

164 8UMCANGACEIROSABEDELEGARTAREFAS

Page 188: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

//códigoposterioromitido

Porfim,precisamosassociarocliquedobotão"Apagar"comachamadadométodoqueacabamosdecriaremnossocontroller.Isso não é novidade para nós, já fizemos algo semelhante emclient/app/app.js:

//client/app/app.js

constcontroller=newNegociacaoController();

document.querySelector('.form')

.addEventListener('submit',controller.adiciona.bind(controller));

//buscandooelementopeloseuIDdocument

.querySelector('#botao-apaga').addEventListener('click',controller.apaga.bind(controller))

;

Quandoclicarmosem"Apagar",atabelaficarávazia.AViewfoi atualizada com os dados do modelo da lista de negociaçõesatualizada.Inclusive,amensagemfoiexibida:

Figura8.1:Listaapagada

Apesar de funcionar como esperado, podemos melhorar um

8UMCANGACEIROSABEDELEGARTAREFAS 165

Page 189: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

pouco mais nosso modelo Negociacoes. Nele, a propriedade_negociacoesnãoestácongeladapermitindoquerecebanovas

atribuições. Aprendemos que através de Object.freeze

podemos impedir novas atribuições às propriedades da instânciade umobjeto.Que tal lançarmosmãodeste recurso também emNegociacoes?Alterandoaclasse:

//client/app/domain/negociacao/Negociacoes.js

classNegociacoes{

constructor(){

this._negociacoes=[];

//CONGELOUAINSTÂNCIAObject.freeze(this);

}

//códigoanterioromitido}

Excelente, mas como nossa instância não aceita novasatribuições à propriedade this._negociacoes o métodoesvazia()daclassenãofuncionará:

//client/app/domain/negociacao/Negociacoes.js

//códigoanterioromitido

esvazia(){

//NÃOACEITARÁANOVAATRIBUIÇÃOthis._negociacoes=[];

}

//códigoposterioromitido

Para solucionarmos este problema, podemos alterar aimplementaçãodonossométodoesvazia()para:

166 8UMCANGACEIROSABEDELEGARTAREFAS

Page 190: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

//client/app/domain/negociacao/Negociacoes.js

//códigoanterioromitido

esvazia(){

this._negociacoes.length=0;}

//códigoposterioromitido

Quando atribuímos 0 à propriedade length de um array,eliminamostodososseusitens.

Nossaaplicaçãofunciona,mastodavezquetivermosdealterarnossos modelos, precisaremos lembrar de chamar o métodoupdate()dasuarespectivaView.Achancedeesquecermosdechamar o método é alta, ainda mais se novas Views foremintroduzidasemnossaaplicação.SeráqueexistealgumamaneiradeautomatizarmosaatualizaçãodaView?

A alteração do modelo é um ponto inevitável em nossaaplicação.Quetalcriarmosumaestratégianaqual,todavezqueomodeloforalterado,asuarespectivaViewsejaatualizada?

Para que isso seja possível, o constructor() dos nossosmodelosprecisará receberuma estratégiade atualizaçãoque seráchamada toda vez que algum de seus métodos que alteram seuestado interno(suaspropriedades) forchamado.VamoscomeçarporNegociacoes:

//client/app/domain/negociacoes/Negociacoes.js

8.1ESEATUALIZARMOSAVIEWQUANDOOMODELOFORALTERADO?

8.1ESEATUALIZARMOSAVIEWQUANDOOMODELOFORALTERADO? 167

Page 191: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

classNegociacoes{

//agorarecebeumparâmetro!constructor(armadilha){

this._negociacoes=[];this._armadilha=armadilha;Object.freeze(this);

}

//códigoposterioromitido

Nossaclasseagorarecebeoparâmetroarmadilha, que seráguardado em uma propriedade da classe. Esse parâmetro nadamais é do queuma função coma lógica de atualizaçãodaView.Agora,nosmétodosadiciona()eesvazia(), chamaremos afunçãoarmazenadaemthis._armadilha:

//client/app/domain/negociacoes/Negociacoes.js

classNegociacoes{

constructor(armadilha){

this._negociacoes=[];this._armadilha=armadilha;Object.freeze(this);

}

adiciona(negociacao){

this._negociacoes.push(negociacao);

//chamandoafunçãothis._armadilha(this);

}

//códigoanterioromitido

esvazia(){

168 8.1ESEATUALIZARMOSAVIEWQUANDOOMODELOFORALTERADO?

Page 192: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

this._negociacoes.length=0;

//chamandoafunçãothis._armadilha(this);

}}

Veja que a função armazenada em this._armadilha échamadarecebendocomoparâmetrothis.Issopermitiráqueafunçãotenhaumareferênciaparaainstânciaqueestáexecutandoaoperação,nocaso,instânciasdeNegociacoes.

Agora, em NegociacaoController , vamos passar umaestratégiadeatualizaçãoparaoconstructor() domodeloqueacabamosdealterar.Porenquanto,usaremosumafunctionemvezdeumaarrowfunction:

//client/app/domain/negociacoes/Negociacoes.js

classNegociacaoController{

constructor(){

const$=document.querySelector.bind(document);this._inputData=$('#data');this._inputQuantidade=$('#quantidade');this._inputValor=$('#valor');

//passandoaestratégiaaserutilizada

this._negociacoes=newNegociacoes(function(model){

this._negociacoesView.update(model);});

this._negociacoesView=newNegociacoesView('#negociacoes);

//precisacontinuar,paraatualizar//nainicializaçãodocontroller

8.1ESEATUALIZARMOSAVIEWQUANDOOMODELOFORALTERADO? 169

Page 193: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

this._negociacoesView.update(this._negociacoes);

this._mensagem=newMensagem();this._mensagemView=newMensagemView('#mensagemView');this._mensagemView.update(this._mensagem);

}

//códigoposterioromitido

Agora,todavezqueosmétodosadiciona()eesvazia()de Negociacoes forem chamados, a estratégia da armadilhapassadanoconstructor()seráchamada.Vejaqueaarmadilhapassada comoparâmetro receberáthis ao ser chamada, que éumareferênciaàinstânciadeNegociacoes.Éestareferênciaqueacessaremos como model na função que passamos paraNegociacoes, emNegociacaoController.Vamos relembrar

dessetrechodecódigo:

//client/app/domain/negociacoes/Negociacoes.js//códigoanterioromitido

//modelseráainstânciadeNegociacoes//estiverchamandoadiciona()ouesvazia()this._negociacoes=newNegociacoes(function(model){

this._negociacoesView.update(model);});

//códigoposterioromitido

Podeparecerumtantoestranhoquea funçãodeclaradatenteutilizarthis._mensagemView antes de ter sido declarada, masnão há problema nenhum nisso. Isso porque ela não seráexecutada imediatamente,sóquandoosmétodosadiciona() eesvazia()foremchamados.

Agora, tanto em adiciona() quanto em apaga() deNegociacaoController , removeremos a instrução que faz a

170 8.1ESEATUALIZARMOSAVIEWQUANDOOMODELOFORALTERADO?

Page 194: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

atualização da view de Negociacoes. Os dois métodos devemficarassim:

//client/app/controllers/NegociacaoController.js//códigoposterioromitido

adiciona(event){

event.preventDefault();this._negociacoes.adiciona(this._criaNegociacao());

//nãochamamaisoupdatedenegociacoesView

this._mensagem.texto='Negociaçãoadicionadacomsucesso';this._mensagemView.update(this._mensagem);this limpaFormulario()

}

//códigoposterioromitido

apaga(){

this._negociacoes.esvazia();

//nãochamamaisoupdatedenegociacoesView

this._mensagem.texto='Negociaçõesapagadascomsucesso';this._mensagemView.update(this._mensagem);

}

Agora que temos todo o código em seu devido lugar, vamosrecarregarnossapáginaetestaroresultado.Nenhumaatualizaçãoéfeitaeaindarecebemosnoconsoleaseguintemensagemdeerro:

UncaughtTypeError:Cannotreadproperty'update'ofundefinedatNegociacoes._armadilha(NegociacaoController.js:12)atNegociacoes.adiciona(Negociacoes.js:12)atNegociacaoController.adiciona(NegociacaoController.js:25)

Pelo console, ao procurarmos a instrução que causou o erro,encontramosestetrechodecódigodeNegociacaoController:

8.1ESEATUALIZARMOSAVIEWQUANDOOMODELOFORALTERADO? 171

Page 195: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

//client/app/controllers/NegociacaoController.js

//códigoanterioromitido

this._negociacoes=newNegociacoes(function(model){

//CAUSADOERROthis._negociacoesView.update(model);

});

//códigoposterioromitido

Estávamos esperandoque a função armadilhapassadaparaoconstructor() de Negociacoes fosse capaz de ter acesso à

propriedade this._negociacoesView deNegociacaoController.Contudo,devidoànaturezadinâmica

de toda function() , o valor de this emthis._negociacoesView.update(model) passou a ser a

instância de Negociacoes, que não possui a propriedade queestamosacessando.

Sósabemosocontexto(this)deuma funçãoemtempodeexecução(runtime)donossocódigo.Podemosfazerumtesteparavermos esse comportamento com mais clareza adicionando umconsole.log(this)nafunçãoquepassamoscomoarmadilha:

//client/app/controllers/NegociacaoController.js//códigoanterioromitido

this._negociacoes=newNegociacoes(function(model){

//imprimiráNegociacoesnoconsoleemvezdeNegociacaoController

console.log(this);

this._negociacoesView.update(model);});

//códigoposterioromitido

172 8.1ESEATUALIZARMOSAVIEWQUANDOOMODELOFORALTERADO?

Page 196: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Verificandonoconsole,temos:

Negociacoes//exibiuNegociacoes!UncaughtTypeError:Cannotreadproperty'update'ofundefined

Elemostraquethis é oNegociacoes. É assimporque afunçãoestásendoexecutadanocontextodeNegociacoes. Seráquetemoscomomudarocontextodethis?

Umamaneiradesolucionarmosoproblemaéguardandoumareferência para o this de NegociacaoController em umavariável(porexemplo,self),eutilizandoessavariávelnafunçãodearmadilha.Vejamos:

//client/app/domain/negociacoes/Negociacoes.js

classNegociacaoController{

constructor(){

const$=document.querySelector.bind(document);this._inputData=$('#data');this._inputQuantidade=$('#quantidade');this._inputValor=$('#valor');

//selféNegociacaoControllerconstself=this;

this._negociacoes=newNegociacoes(function(model){

//continuasendoNegociacoesconsole.log(this);

//acessamosself//temoscertezadequeéNegociacaoController

self._negociacoesView.update(model);});

8.2DRIBLANDOOTHISDINÂMICO

8.2DRIBLANDOOTHISDINÂMICO 173

Page 197: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

//códigoposterioromitido

Essasolução funciona,noentanto,nosobrigaadeclararumavariável extra para guardarmos uma referência para o valor dethis que desejamos usar em nossa função de armadilha.

Podemosresolverdeoutramaneira.

Vamos voltar nosso código para o estágio anterior, que nãoestáfuncionandocomoesperado:

//client/app/domain/negociacoes/Negociacoes.js

classNegociacaoController{

constructor(){

const$=document.querySelector.bind(document);this._inputData=$('#data');this._inputQuantidade=$('#quantidade');this._inputValor=$('#valor');

this._negociacoes=newNegociacoes(function(model){

console.log(this);this._negociacoesView.update(model);

});

//códigoposterioromitido

Para que nossa função passada como armadilha para oconstructor()deNegociacoespasseaconsiderarainstânciade NegociacaoController como this , e não a instânciaNegociacoes , passaremos mais um parâmetro para oconstructor() de Negociacoes além da armadilha. Seu

primeiro parâmetro será o this , aquele que se refere aNegociacaoController:

//client/app/controller/NegociacaoController.js

174 8.2DRIBLANDOOTHISDINÂMICO

Page 198: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

classNegociacaoController{

constructor(){

const$=document.querySelector.bind(document);this._inputData=$('#data');this._inputQuantidade=$('#quantidade');this._inputValor=$('#valor');

//othispassadoaindaapontapara//umainstânciadeNegociacaoController

this._negociacoes=newNegociacoes(this,function(model){

console.log(this);this._negociacoesView.update(model);

});

//códigoposterioromitido

PrecisamosalterarNegociacoesparaquerecebaeguardeoparâmetrorecebido:

//client/app/domain/negociacoes/Negociacoes.js

classNegociacoes{

constructor(contexto,armadilha){

this._negociacoes=[];this._armadilha=armadilha;this._contexto=contexto;Object.freeze(this);

}

//códigoposterioromitido

Agora, vamos alterar a chamada de this._armadilha nosmétodosadiciona() e esvazia(). Faremos isso através dachamada da função call() que toda função em JavaScript

8.2DRIBLANDOOTHISDINÂMICO 175

Page 199: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

possui. Ela recebe dois parâmetros.Oprimeiro é o contexto queserá usado como this na chamada da função e o segundo, oparâmetrorecebidoporestafunção:

//client/app/domain/negociacoes/Negociacoes.js

classNegociacoes{

constructor(contexto,armadilha){

this._negociacoes=[];this._contexto=contexto;this._armadilha=armadilha;Object.freeze(this);

}

adiciona(negociacao){

this._negociacoes.push(negociacao);

//ALTERAÇÃOAQUIthis._armadilha.call(this._contexto,this);

}

//códigoanterioromitido

esvazia(){

this._negociacoes.length=0;

//ALTERAÇÃOAQUIthis._armadilha.call(this._contexto,this);

}}

Seexecutarmosocódigo,ocadastrovoltaráafuncionar:

176 8.2DRIBLANDOOTHISDINÂMICO

Page 200: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Figura8.2:Formuláriofuncionando

Estaéumasegundaformaderesolveroproblemageradopelodinamismo de this Contudo há uma terceira maneira desolucionarmosoproblema,porémmenosverbosaqueasduasqueexperimentamos.Veremosestanovaformaaseguir.

A primeira mudança que faremos para escrevemos menoscódigo é removermos o parâmetro contexto doconstructor()deNegociacoes. Inclusive,voltaremosparaa

chamadadiretadethis._armadilhanosmétodosadiciona()eesvazia():

classNegociacoes{

//nãorecebemaiscontextoconstructor(armadilha){

this._negociacoes=[];this._armadilha=armadilha;Object.freeze(this);

}

8.3 ARROW FUNCTION E SEU ESCOPOLÉXICO

8.3ARROWFUNCTIONESEUESCOPOLÉXICO 177

Page 201: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

adiciona(negociacao){

this._negociacoes.push(negociacao);

//chamadadiretadafunção,semcall()this._armadilha(this);

}

//códigoposterioromitido

esvazia(){

this._negociacoes.length=0;

//chamadadiretadafunção,semcall()this._armadilha(this);

}}

Precisamos alterar no constructor() deNegociacaoController os parâmetros passados para

Negociacoes,quepassouaserapenasafunçãodearmadilha:

//client/app/controllers/NegociacaoController.js

classNegociacaoController{

constructor(){

const$=document.querySelector.bind(document);this._inputData=$('#data');this._inputQuantidade=$('#quantidade');this._inputValor=$('#valor');

//nãorecebemaisthiscomoparâmetro,//somenteafunção!this._negociacoes=newNegociacoes(function(model){

console.log(this);this._negociacoesView.update(model);

});

//códigoposterioromitido

178 8.3ARROWFUNCTIONESEUESCOPOLÉXICO

Page 202: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Do jeito que está, nosso código não funcionará. Mas setrocarmos a function passada para o constructor() deNegociacoesporumaarrowfunction,teremosumasurpresa:

//client/app/controllers/NegociacaoController.js

classNegociacaoController{

constructor(){

const$=document.querySelector.bind(document);this._inputData=$('#data');this._inputQuantidade=$('#quantidade');this._inputValor=$('#valor');

//passouumarrowfunction!

this._negociacoes=newNegociacoes(model=>{

console.log(this);this._negociacoesView.update(model);

});

//códigoposterioromitido

Umtestedemonstraquenossocódigofuncionou,sendomuitomenos verboso do que o anterior. Como isso é possível? Istoocorreporqueaarrowfunctionnãoéapenasumamaneirasucintade escrevermos uma função, ela também tem uma característicapeculiar: o escopo de seu this é léxico (estático) em vez dedinâmico.

Othisdeumaarrowfunctionobtémseuvalordo"códigoaoredor", mantendo esse valor independente do lugar onde échamado.ÉporissoqueothisdafunçãodaarmadilhapassadaapontaparaainstânciadeNegociacaoController.

Nossasoluçãoé funcional,maspodemosmelhorá-la.Éoque

8.3ARROWFUNCTIONESEUESCOPOLÉXICO 179

Page 203: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

veremosnopróximocapítulo.

O código completo deste capítulo você encontra emhttps://github.com/flaviohenriquealmeida/cangaceiro-javascript/tree/master/08

180 8.3ARROWFUNCTIONESEUESCOPOLÉXICO

Page 204: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

CAPÍTULO9

"Jagunçonãoseescabreiacomperdanemderrota-quasetudoparaeleéoigual."-GrandeSertão:Veredas

Para liberarmos o desenvolvedor da responsabilidade deatualizar programaticamente a View sempre que o modelo foratualizado,nóscolocamosalógicadeatualizaçãoem"armadilhas",funçõesqueeramchamadasemmétodosquealteravamoestadodomodelo.

No entanto, esta soluçãodeixa a desejar porquenos obriga areceberaarmadilhaemtodasasnossasclassesdemodelo,alémdetermos de lembrar de chamá-la em cada método que altera oestadodomodelo.

Apropriedadethis._armadilhadeNegociacoesnãotemqualquerrelaçãocomodomínioqueaclasserepresenta.Ouseja,elaestáláapenasporumaquestãodeinfraestrutura.Relembremosdocódigo:

ENGANARAMOCANGACEIRO,SERÁ?

9ENGANARAMOCANGACEIRO,SERÁ? 181

Page 205: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

//APENASRELEMBRANDO!

//client/app/domain/negociacoes/Negociacoes.js

classNegociacoes{

constructor(armadilha){

this._negociacoes=[]

//estranhononinho,//semrelaçãocomodomíniodenegociaçõesthis._armadilha=armadilha;

Object.freeze(this);}

Ummodelododomínioéumadasclassesmaisreutilizáveisdeumsistema,justamentepornãoconternenhumamágicaextraquenãodigarespeitoaoproblemadodomínioqueresolve.Contudo,nossocódigoparecenãoseguiressarecomendação.

Em nossa aplicação, se mais tarde quisermos usar nossosmodeloscomframeworksfamososdomercado,comoAngularouReact, a propriedadearmadilha não serámais necessária,masainda fará parte da definição da classe domodelo, obrigando osdesenvolvedoresalidarcomela.Eagora?

De um lado, queremos preservar o modelo, do outro,queremos facilitar a vida do desenvolvedor. Será que podemosconseguiromelhordosdoismundos?

Vimosquenãoé interessantepoluirmosnossomodelocomafunçãoqueatualizarásuarespectivaViewequeaindaprecisamos

9.1OPADRÃODEPROJETOPROXY

182 9.1OPADRÃODEPROJETOPROXY

Page 206: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

deumasoluçãoqueatualizeaViewautomaticamentetodavezqueo modeloforalterado.Existeumpadrãodeprojetoquepodenosajudaraconciliaressesdoisopostos,oProxy.

O padrão de projeto Proxy, em poucas palavras, trata-se de"um farsante". Lidamos com um Proxy como se ele fosse ainstância do objeto que estamos querendo manipular, porexemplo, uma instânciadeNegociacoes. Ele é uma casquinhaque envolve a instância que desejamos manipular e, para cadapropriedade emétodo presente nessa instância, o Proxy terá umcorrespondente.

Sechamarmosométodoadiciona()noProxy,eledelegaráachamada domesmométodo na instância que encapsula. Entre achamada do método do Proxy e a chamada do métodocorrespondentenainstância,podemosadicionarumaarmadilha-nocasodanossaaplicação,umcódigoqueatualizaránossaView.Dessaforma,nãoprecisamosmodificaraclassedomodelo,apenascriarumProxyquesaibaexecutarocódigoquedesejamos.Comoimplementá-lo?

Aboanotíciaéquenãoprecisamos implementaressepadrãode projeto.OECMAScript, a partir da versão 2015, já possui naprópria linguagem um recurso de Proxy. Vamos aprender autilizá-loaseguir.

O primeiro passo é editarmos NegociacaoController ecomentarmos temporariamente o trecho do código que não

9.2 APRENDENDO A TRABALHAR COMPROXY

9.2APRENDENDOATRABALHARCOMPROXY 183

Page 207: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

funciona mais, aquele que passa a armadilha para oconstructor()deNegociacoes:

//client/app/controllers/NegociacaoController.js

classNegociacaoController{

constructor(){

const$=document.querySelector.bind(document);this._inputData=$('#data');this._inputQuantidade=$('#quantidade');this._inputValor=$('#valor');

//códigocomentado/*this._negociacoes=newNegociacoes(model=>{

console.log(this);this._negociacoesView.update(model);

});*/

//códigoposterioromitido

Em seguida, vamos remover a armadilha passada para oconstructor()deNegociacoes.Alémdisso,nãopodemosnosesquecer de remover as chamadas das armadilhas dos seusmétodos.Aclassenofinalficaráassim:

//client/app/domain/negociacao/Negociacoes.js

classNegociacoes{

constructor(){

this._negociacoes=[]Object.freeze(this);

}

adiciona(negociacao){

184 9.2APRENDENDOATRABALHARCOMPROXY

Page 208: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

this._negociacoes.push(negociacao);}

paraArray(){

return[].concat(this._negociacoes);}

getvolumeTotal(){

returnthis._negociacoes.reduce((total,negociacao)=>

total+negociacao.volume,0)}

esvazia(){

this._negociacoes.length=0;}

}

Com a alteração que acabamos de fazer, nossa aplicaçãodeixará de funcionar e receberemos no console a seguintemensagemdeerro:

UncaughtTypeError:Cannotreadproperty'paraArray'ofundefined

Não se preocupe com ela. Deixaremos nossa aplicaçãoquebrada enquanto aprendemos um novo recurso que ajudará arecompô-la.

Agora, faremos uma série de experimentos no console doChromeparaaprendermosa trabalharcomProxy.Vamosdeixarumpouco de lado a classeNegociacoes para trabalhar com aclasseNegociacao,poisessatrocafacilitaránossaaprendizagem.

Com o console do Chrome aberto, vamos executar as duasinstruçõesaseguir:

//NOCONSOLEDOCHROME

9.2APRENDENDOATRABALHARCOMPROXY 185

Page 209: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

negociacao=newNegociacao(newDate(),1,100);proxy=newProxy(negociacao,{});

Aprimeira instrução cria uma instância deNegociacao.AsegundacriaumainstânciadeumProxy.Oconstructor() doProxy recebe como primeiro parâmetro a instância deNegociacao que queremos encapsular e, como segundo, um

objetonoformatoliteral{},quecontémocódigodasarmadilhasque desejamos executar. Por enquanto, não definimos nenhumaarmadilha.

O Proxy criado possui todas as propriedades e métodos daclasse que encapsula, tanto que se acessarmos no console apropriedade proxy.valor , estaremos por debaixo dos panosacessandoamesmapropriedadenainstânciadeNegociacaoqueencapsula:

//NOCONSOLEDOCHROME

console.log(proxy.valor);//imprime100console.log(proxy.quantidade);//imprime1console.log(proxy.volume);//imprime100

Noentanto,nãoqueremosqueninguémtenhaacessodiretoàinstância encapsulada pelo nosso Proxy. Logo, podemos limparnossoconsoleecomeçardozerocomessainstrução:

//NOCONSOLEDOCHROME

negociacao=newProxy(newNegociacao(newDate(),1,100),{};

Usamoscomonomedevariávelnegociacao paramascararnosso Proxy. Em seguida, como primeiro parâmetro doconstructor()donossoProxy,instanciamosdiretamenteuma

instânciadeNegociacao.

186 9.2APRENDENDOATRABALHARCOMPROXY

Page 210: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

De nada adianta nosso Proxy se ele não é capaz de executarnossasarmadilhas.Precisamosdefini-lasnoobjetopassadocomosegundoparâmetroparaoProxy,umobjetochamadohandler.

Como vamos executar uma série de operações, será maiscômodo para nós escrever todas essas instruções dentro de umanova tag <script></script> , que adicionaremostemporariamenteemclient/index.html.Eladeveseraúltimatag<script>dapágina.

<!--client/index.html--><!--códigoposterioromitido--><!--últimatagscript!-->

<script>

constnegociacao=newProxy(newNegociacao(newDate(),2,100),{

});</script>

<!--códigoposterioromitido-->

Agoraquetemostudonolugar,vamosaprenderacriarnossasarmadilhasnohandlerpassadoparanossoProxy.

Para iniciarmos nossa jornada no mundo do Proxy,começaremos com algo bem modesto. Toda vez que aspropriedades do Proxy forem lidas, faremos com que ele exibaumamensagemindicandoonomedapropriedade.

Para isso, em nosso handler, adicionaremos a propriedade

9.3 CONSTRUINDO ARMADILHAS DELEITURA

9.3CONSTRUINDOARMADILHASDELEITURA 187

Page 211: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

get que receberá como parâmetro a função que desejamosexecutarnaleituradaspropriedadesdonossoProxy.Vejamqueapropriedadeget não foi por acaso, ela indica claramente queestamosplanejandoumcódigoparaumacessodeleitura:

<!--client/index.html--><!--códigoposterioromitido-->

<script>

constnegociacao=newProxy(newNegociacao(newDate(),2,100),{

get:function(target,prop,receiver){

console.log(`apropriedade"${prop}"caiunaarmadilha!`);

}});

console.log(negociacao.quantidade);console.log(negociacao.valor);</script>

<!--códigoposterioromitido-->

A função passada para a propriedade get recebe trêsparâmetrosespeciais.Oprimeiro,target,éumareferênciaparao objetoencapsuladopeloProxy,oobjetoverdadeiro.Osegundoparâmetro,prop,éumastringcomonomedapropriedadequeestásendoacessada.Porfim,receiveréumareferênciaparaopróprioProxy.

Podemossimplificarumpoucomaisnossocódigo.ApartirdoES2015 (ES6), podemos declarar propriedades de objetos queequivalemafunçõesdessaforma:

<!--client/index.html--><!--códigoposterioromitido-->

188 9.3CONSTRUINDOARMADILHASDELEITURA

Page 212: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

<script>

constnegociacao=newProxy(newNegociacao(newDate(),2,100),{

//AQUI!nãoémaisget:function(...){}

get(target,prop,receiver){

console.log(`apropriedade"${prop}"caiunaarmadilha!`);

}});

console.log(negociacao.quantidade);console.log(negociacao.valor);</script>

<!--códigoposterioromitido-->

Recarregandoapágina,vemosasaídadoConsole:

//EXIBIDONOCONSOLEDOCHROME

apropriedade"quantidade"caiunaarmadilha!undefinedapropriedade"valor"caiunaarmadilha!undefined

A mensagem da nossa armadilha funcionou perfeitamente.Entretanto, as impressões de console.log() resultaram emundefinedemvezdeexibirosvaloresdaspropriedades.Porqueissoaconteceu?

Todaarmadilha,quandoexecutada,deveseresponsabilizarpordefinir o valor que será retornado para quem acessar apropriedade.Comonãodefinimosretornoalgum,oresultado foiundefined.

Façamos um teste para entendermos ainda melhor a

9.3CONSTRUINDOARMADILHASDELEITURA 189

Page 213: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

necessidadedesseretorno:

<!--client/index.html--><!--códigoposterioromitido-->

<script>

constnegociacao=newProxy(newNegociacao(newDate(),2,100),{

get(target,prop,receiver){

console.log(`apropriedade"${prop}"caiunaarmadilha!`);

//MUDANÇAAQUI!:)return'Flávio';

}});

console.log(negociacao.quantidade);console.log(negociacao.valor);</script>

<!--códigoposterioromitido-->

Noexemploanterior,pormaisestranhoqueissopossaparecer,estamosretornandoastring"Flávio".Olhandono consoledoChrome, as instruções console.log() exibirão a string"Flávio", tanto para negociacao.quantidade quanto emnegociacao.valor:

Figura9.1:ReturnFlávio

190 9.3CONSTRUINDOARMADILHASDELEITURA

Page 214: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Nosso handler retorna algo, mas ainda não é o esperado.Queremos que sejam retornados os respectivos valores daspropriedadesquantidadeevalordoobjetoencapsuladopeloProxy.

Aboanotíciaéquetemostodososelementosnecessáriosparaisso. Quando temos um objeto JavaScript, podemos acessar suaspropriedadesatravésde.(ponto),ouutilizandoumcolchetequerecebeastringcomonomedapropriedadequedesejamosacessar.Se temos target (a referência para nossa instância) e temosprop (a string com o nome da propriedade que está sendo

acessada) podemosfazertarget[prop]

<!--client/index.html--><!--códigoposterioromitido--><script>

constnegociacao=newProxy(newNegociacao(newDate(),2,100),{

get(target,prop,receiver){

console.log(`apropriedade"${prop}"caiunaarmadilha!`);

//modificaçãoaquireturntarget[prop];

}});

console.log(negociacao.quantidade);console.log(negociacao.valor);

</script><!--códigoposterioromitido-->

9.3CONSTRUINDOARMADILHASDELEITURA 191

Page 215: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Figura9.2:AcessandoapropriedadedoobjetoencapsuladopeloProxy

Nós vimos como executar um código toda vez que aspropriedadesdeumobjetoforemlidas.Contudo,issonãoresolveo problemadeatualizaçãoautomáticadaView,porquequeremosexecutar uma armadilha apenas quando as propriedades foremmodificadas.

Para realizarmos um teste de escrita, não podemossimplesmente atribuir valores para negociacao.quantidade enegociacao.valor,porqueeles sãogetters.Qualquer valor

atribuído para eles é ignorado. Sendo assim, aplicaremos uma"licençapoética"paraacessardiretamenteaspropriedadesprivadas_quantidadee_valor.Estamosquebrandoaconvenção,masépor uma boa causa, para podermos entender como Proxyfunciona:

<!--client/index.html--><!--códigoposterioromitido--><script>

constnegociacao=newProxy(newNegociacao(newDate(),2,100),{

get(target,prop,receiver){

console.log(`apropriedade"${prop}"caiunaarmadilha!`);

returntarget[prop];}

192 9.3CONSTRUINDOARMADILHASDELEITURA

Page 216: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

});

//mesmoacessandoaspropriedadessomenteleitura//aarmadilhanãoédisparadanegociacao._quantidade=10negociacao._valor=100;

</script><!--códigoposterioromitido-->

Realmente,nadaacontece.Damesmaformaqueaprendemosaexecutar armadilhas para leitura, aprendemos a executararmadilhasparaescrita.

Paracriarmosarmadilhasdeescrita,emvezdeusarmosgetemnossohandler,usamosset.Eleébemparecidocomoget,adiferençaestánoparâmetroextravaluequeelerecebe:

<!--client/index.html--><!--códigoposterioromitido--><script>

constnegociacao=newProxy(newNegociacao(newDate(),2,100),{

get(target,prop,receiver){

console.log(`apropriedade"${prop}"caiunaarmadilha!`);

returntarget[prop];},

set(target,prop,value,receiver){

/*nossalógicaentraráaqui*/}

});

9.4 CONSTRUINDO ARMADILHAS DEESCRITA

9.4CONSTRUINDOARMADILHASDEESCRITA 193

Page 217: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

negociacao._quantidade=10negociacao._valor=100;

</script><!--códigoposterioromitido-->

O parâmetro value é o valor que está sendo atribuído àpropriedade que está sendo acessada no Proxy. Sendo assim,podemosimplementarnossaarmadilhadessaforma:

<!--client/index.html--><!--códigoposterioromitido--><script>

constnegociacao=newProxy(newNegociacao(newDate(),2,100),{

get(target,prop,receiver){

console.log(`apropriedade"${prop}"caiunaarmadilha!`);

returntarget[prop];},

set(target,prop,value,receiver){

console.log(`${prop}guarda${target[prop]},receberá${value}`);

//efetivaapassagemdovalor!target[prop]=value;

}});

negociacao._quantidade=10negociacao._valor=100;

</script><!--códigoposterioromitido-->

Um ponto que faz parte da especificação Proxy do ES2015(ES6)éanecessidadederetornarmostrue emnossaarmadilha

194 9.4CONSTRUINDOARMADILHASDEESCRITA

Page 218: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

paraindicarqueaoperaçãofoibem-sucedida.

<!--client/index.html--><!--códigoposterioromitido--><script>

constnegociacao=newProxy(newNegociacao(newDate(),2,100),{

get(target,prop,receiver){

console.log(`apropriedade"${prop}"caiunaarmadilha!`);

returntarget[prop];},

set(target,prop,value,receiver){

console.log(`${prop}guarda${target[prop]},receberá${value}`);

//seforumobjetocongelado,seráignoradotarget[prop]=value;

//retornatrueseconseguiualterarapropriedadedoobjeto

returntarget[prop]==value;}

});

//armadilhaserádisparada,masosvalores//nãoserãoalterados,poisestãocongelados

negociacao._quantidade=10negociacao._valor=100;

</script><!--códigoposterioromitido-->

A instrução target[prop] = value será ignorada parapropriedades congeladas do objeto, é por isso que retornamos oresultado de target[prop] == value para sabermos se a

9.4CONSTRUINDOARMADILHASDEESCRITA 195

Page 219: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

atribuiçãofoirealizadacomsucesso.

Verificandooconsole,temos:

Figura9.3:Novovalor

Podemosescreverumpoucomenoscomauxíliodeumrecursoqueaprenderemosaseguir.

Podemossimplificarumpoucomaisnossocódigocomauxílioda API Reflect (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect)introduzidanoECMAScript2015(ES6).EstaAPIcentralizaumasérie de métodos estáticos que permitem a leitura, escrita echamada de métodos e funções dinamicamente, um deles é oReflect.set().

OReflect.set() recebe três parâmetros. O primeiro é ainstância do objeto alvo da operação e o segundo o nome dapropriedadequeseráalterada.Oúltimoparâmetroéonovovalordapropriedade.Ométodoretornarátrueoufalsecasotenhaconseguidoalterarounãoapropriedadedoobjeto:

<!--client/index.html--><!--códigoposterioromitido--><script>

constnegociacao=newProxy(newNegociacao(newDate(),2,100

9.5REFLECTAPI

196 9.5REFLECTAPI

Page 220: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

),{

get(target,prop,receiver){

console.log(`apropriedade"${prop}"caiunaarmadilha!`);

returntarget[prop];},

set(target,prop,value,receiver){

console.log(`${prop}guarda${target[prop]},receberá${value}`);

returnReflect.set(target,prop,value);}

});

negociacao._quantidade=10negociacao._valor=100;

</script><!--códigoposterioromitido-->

A Reflect API possui outros métodos como oReflect.get() . No entanto, sua introdução tornaria nosso

código um pouco mais verboso, diferente do uso deReflect.set() que nos poupou uma linha de código. Por

exemplo, a instrução return target[prop] poderia ter sidosubstituída por return Reflect.get(target, prop), mas aforma atual nos permite escrever uma quantidade de caracteresmenor.

Agora que já compreendemos como um Proxy funciona,podemos voltar para Negociacoes , aliás, modelo cuja Viewprecisamosatualizar.

9.6UMPROBLEMANÃOESPERADO

9.6UMPROBLEMANÃOESPERADO 197

Page 221: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Agoraquejáconsolidamosnossoconhecimentosobrecriaçãode Proxy, deixaremos o modelo Negociacao de lado paraabordarmos o modelo Negociacoes , aquele cuja Viewprecisamos atualizar automaticamente. Vamos realizar um testecriando um Proxy e, através dele, chamaremos o métodoesvazia():

<!--client/index.html--><!--códigoposterioromitido--><script>

//começandodozero,apagandoocódigoanteriorqueexistia

constnegociacoes=newProxy(newNegociacoes(),{

set(target,prop,value,receiver){

console.log(`${prop}guarda${target[prop]},receberá${value}`);

returnReflect.set(target,prop,value);}

});

negociacoes.esvazia();

</script><!--códigoposterioromitido-->

Recarregando nossa página e observando o resultado noconsole,vemosapenasamensagemdeerroquejáeraexibidaantesporaindanãotermosterminadonossaaplicação.Nenhumaoutramensagemé exibida,ou seja,nossa armadilhanão foi executada!Seráqueomesmoacontececomométodoadiciona()?Vamostestá-lo.

Aindaemclient/index.html,vamossubstituirachamadade negociacoes.esvazia() por uma chamada denegociacoes.adiciona(),passandoumanovanegociação:

198 9.6UMPROBLEMANÃOESPERADO

Page 222: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

<!--client/index.html--><!--códigoposterioromitido--><script>

constnegociacoes=newProxy(newNegociacoes(),{

set(target,prop,value,receiver){

console.log(`${prop}guarda${target[prop]},receberá${value}`);

returnReflect.set(target,prop,value);}

});

//agora,chamandoométodoadicionanegociacoes.adiciona(newNegociacao(newDate(),1,100));

</script><!--códigoposterioromitido-->

Recarregando nossa página e observando o resultado noconsole,nadaacontece.

Isso acontece porque nossa armadilha só é disparada apenasquando houver atribuição de valor nas propriedades do objetoencapsuladopeloProxy.Osmétodosesvazia()eadiciona()não realizam qualquer atribuição. O primeiro fazthis._negociacoes.length=0eosegundoadicionaumnovoelementoemthis._negociacoes atravésdométodopush()dopróprioarray.Vamosrelembrardosmétodos!

//RELEMBRADOOCÓDIGO,NÃOHÁMODIFICAÇÃOAQUI

//client/app/domain/negociacoes/Negociacoes.js

classNegociacoes{

//códigoomitido

adiciona(negociacao){

9.6UMPROBLEMANÃOESPERADO 199

Page 223: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

//nãoháatribuiçãoaqui!this._negociacoes.push(negociacao);

}

//códigoomitido

esvazia(){

//nãoháatribuiçãoaqui!this._negociacoes.length=0;

}

//códigoposterioromitido

Aliás,nãofazsentidothis._negociacoesreceberumanovaatribuição, pois é umapropriedade congelada.E agora? Seráqueexistealgumasoluçãoparaesseproblema?

Uma solução é sermos capazes de indicar quais métodos daclassedevemdispararnossas armadilhas.Comessa estratégia, osmétodosadiciona()eesvazia()nãoprecisammaisdependerde uma atribuição a alguma propriedade da instância deNegociacoes para que suas respectivas armadilhas sejam

disparadas.

Vamosdarinícioànossasoluçãocangaceiraparasolucionaroproblemadaseçãoanterior.Oprimeiropontoéentendermosque,pordebaixodospanos,quandooJavaScriptchamadaummétodo,elerealizaumgetparaobterumareferênciaaométodo,edepoisumapplyparapassarseusparâmetros.Porenquanto,vamosnosateraopapeldoget.

9.7 CONSTRUINDO ARMADILHAS PARAMÉTODOS

200 9.7CONSTRUINDOARMADILHASPARAMÉTODOS

Page 224: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

VamossubstituirosetquetemosnoProxyquecriamosemclient/index.htmlporumget.Lembre-sedequegetnãorecebeoparâmetrovalue:

<!--client/index.html--><!--códigoposterioromitido--><script>

constnegociacoes=newProxy(newNegociacoes(),{

get(target,prop,receiver){

/*eagora?*/}

});

negociacoes.adiciona(newNegociacao(newDate(),1,100));

</script><!--códigoposterioromitido-->

Opróximopassoéverificarmosseapropriedadequeestamosinterceptandoéumafunção/método,poisaestratégiaqueestamosconstruindo é para lidar com esse tipo de dado. Fazemos issoatravésdafunçãotypeof():

<!--client/index.html--><!--códigoposterioromitido-->

<!--NÃOTESTEOCÓDIGOAINDA!NÃOFUNCIONARÁ,NÃOTERMINAMOS--><script>

constnegociacoes=newProxy(newNegociacoes(),{

get(target,prop,receiver){

if(typeof(target[prop])==typeof(Function){

/*faltacódigoainda*/}

}

9.7CONSTRUINDOARMADILHASPARAMÉTODOS 201

Page 225: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

});

negociacoes.adiciona(newNegociacao(newDate(),1,100));

</script><!--códigoposterioromitido-->

Se deixarmos do jeito que está, aplicaremos nossa estratégiapara qualquer método, e não é isso o que queremos. Não fazsentido o método paraArray() de Negociacoes dispararatualizaçõesdeView,apenasmétodosquealteramoestado.

Alémdeverificarmosotipodapropriedade,vamostestarseelaestáincluídanalistademétodosquedesejamosinterceptar:

<!--client/index.html--><!--códigoposterioromitido--><script>

constnegociacoes=newProxy(newNegociacoes(),{

get(target,prop,receiver){

if(typeof(target[prop])==typeof(Function)&&['adiciona','esvazia']

.includes(prop)){

/*faltacódigoainda*/}

}});

negociacoes.adiciona(newNegociacao(newDate(),1,100));

</script><!--códigoposterioromitido-->

Através dométodoincludes() do array, verificamos se a

9.8UMAPITADADOES2016(ES7)

202 9.8UMAPITADADOES2016(ES7)

Page 226: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

propriedade que está sendo interceptada consta em nossa lista.Aliás, ométodoincludes() foi introduzidonoES2016 (ES7),umaversãoposterioraoES2015(ES6).

A especificação ES2016 (ES7) defineArray.prototype.includes e o operador exponencial

apenas.

Agora, se apropriedadenão forummétodo/funçãodanossalista,deixamosocursoacontecernaturalmente.

<!--client/index.html--><!--códigoposterioromitido--><script>

constnegociacoes=newProxy(newNegociacoes(),{

get(target,prop,receiver){

if(typeof(target[prop])==typeof(Function)&&['adiciona','esvazia']

.includes(prop)){

/*faltacódigoainda*/

}else{

//realizandoumgetpadrãoreturntarget[prop];

}}

});

negociacoes.adiciona(newNegociacao(newDate(),1,100));

</script><!--códigoposterioromitido-->

9.8UMAPITADADOES2016(ES7) 203

Page 227: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Agora que temos tudo encaixado, precisamos implementar ocódigo que será executado pela armadilha dos métodos quedecidimosconsiderar.Éaquiqueentraopulodogato,eminglês,catjump.

Paraosmétodoslistadosemnossoarraydenomesdemétodosa serem interceptados, vamos fazer com que sua leitura retorneuma nova function em vez de uma arrow function, com ocódigo que desejamos executar, inclusive aquele que aplicará osparâmetrosdafunção(casoexistam):

<!--client/index.html--><!--códigoposterioromitido--><script>

constnegociacoes=newProxy(newNegociacoes(),{

get(target,prop,receiver){

if(typeof(target[prop])==typeof(Function)&&['adiciona','esvazia']

.includes(prop)){

//retornaumanovafunçãocomcontextodinâmico

//antesqueoapplyaplicadoporpadrãopelo

//interpretadorsejachamado.

returnfunction(){

console.log(`"${prop}"disparouaarmadilha`);

target[prop].apply(target,arguments);}

}else{//seépropriedade,retornaovalornormalmentereturntarget[prop];

}}

204 9.8UMAPITADADOES2016(ES7)

Page 228: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

});

negociacoes.adiciona(newNegociacao(newDate(),1,100));

</script><!--códigoposterioromitido-->

Vamosrepassarpelamágicaquefizemos.Lembrem-sedequeo interpretadordoJavaScriptprimeirorealizaumgetparalerométodo,edepoisexecutaumapplypordebaixodospanosparaexecutá-lo, passando seus parâmetros. A function() queestamos retornando substituirá o método original antes que oapply entre em ação. Nela, adicionamos o código da nossa

armadilha.

Atravésdetarget[prop].apply(nãoconfundacomoapplyque o interpretador fará), estamos chamando o método usandocomo contexto o próprio target . O método apply() éparecidocomométodocall()quejávimos,adiferençaéqueoprimeirorecebeseusparâmetrosemumarray.Perfeito,mastemosumproblema.

Nossa function() substituta precisa saber se o métodooriginalpossuiounãoparâmetrosparapassá-losna chamadadetarget[prop].apply.Conseguimosessa informaçãoatravésdearguments.

Avariávelimplícitaargumentspossuiestruturasemelhanteaumarray.Elaestápresenteemtodométodooufunção,edáacessoaosparâmetrospassados,mesmoqueelesnãosejamexplicitados.Vejamosumexemploatravésdoconsole:

//TESTARNOCONSOLE,EXEMPLOAPENAS

9.8UMAPITADADOES2016(ES7) 205

Page 229: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

//NÃORECEBEPARÂMETROALGUM

functionexibeNomeCompleto(){alert(`Nomecompleto:${arguments[0]}${arguments[1]}`);

}

//funciona!exibeNomeCompleto('Flávio','Almeida');

Oarguments foi fundamental paranossa solução, poisnãoteríamos como prever quantos parâmetros cada métodointerceptado receberia. Agora que temos nossa solução pronta,vamosalterarNegociacaoControllerparaquefaçausodela.

VamosalteraraclasseNegociacaoController atribuindo àpropriedadethis._negociacoesumProxy,usandoalógicaquejáimplementamosemclient/index.html.Épraticamenteumcorta e cola, a diferença é que fazemos uma chamada a

this._negociacoesView.update() logo abaixo doconsole.log()emqueométododisparouaarmadilha:

//client/app/controllers/NegociacaoController.js

classNegociacaoController{

constructor(){

const$=document.querySelector.bind(document);this._inputData=$('#data');this._inputQuantidade=$('#quantidade');this._inputValor=$('#valor');

//agoraéumproxy!this._negociacoes=newProxy(newNegociacoes(),{

9.9 APLICANDO A SOLUÇÃO EMNEGOCIACAOCONTROLLER

206 9.9APLICANDOASOLUÇÃOEMNEGOCIACAOCONTROLLER

Page 230: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

get(target,prop,receiver){

if(typeof(target[prop])==typeof(Function)&&['adiciona','esvazia']

.includes(prop)){

returnfunction(){

console.log(`"${prop}"disparouaarmadilha`);

target[prop].apply(target,arguments);

//targetéainstânciarealdeNegociacoes

//contudo,TEREMOSPROBLEMASAQUI!this._negociacoesView.update(target);

}

}else{

returntarget[prop];}

}});

//códigoposterioromitido}

Não precisamos mais da tag <script> com nossos testes,podemos removê-la. Por fim, quando executamos uma novanegociação,recebemosestamensagemdeerronoconsole:

//EXIBIDONOCONSOLEDOCHROME

UncaughtTypeError:Cannotreadproperty'update'ofundefined

Isso acontece porque o this dethis._negociacoesView.update(target) é o Proxy, e nãoNegociacaoController. Se tivéssemos usado arrow function,

9.9APLICANDOASOLUÇÃOEMNEGOCIACAOCONTROLLER 207

Page 231: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

resolveríamos esse problema, mas precisamos que o this dafunção seja dinâmico; caso contrário, nossa solução nãofuncionará.

Umasoluçãoque jávimosantes éguardarumareferênciadethisnavariávelself,paraquepossamosutilizá-ladepoisna

armadilha:

classNegociacaoController{

constructor(){

const$=document.querySelector.bind(document);this._inputData=$('#data');this._inputQuantidade=$('#quantidade');this._inputValor=$('#valor');

//guardandoumareferência//paraainstânciadeNegociacaoControllerconstself=this;

this._negociacoes=newProxy(newNegociacoes(),{

get(target,prop,receiver){

if(typeof(target[prop])==typeof(Function)&&['adiciona','esvazia']

.includes(prop)){

returnfunction(){

console.log(`"${prop}"disparouaarmadilha`);

target[prop].apply(target,arguments);

//AGORAUSASELF!self._negociacoesView.update(target);

}

}else{

208 9.9APLICANDOASOLUÇÃOEMNEGOCIACAOCONTROLLER

Page 232: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

returntarget[prop];}

}});

this._negociacoesView=newNegociacoesView('#negociacoes);

this._negociacoesView.update(this._negociacoes);

this._mensagem=newMensagem();this._mensagemView=newMensagemView('#mensagemView');this._mensagemView.update(this._mensagem);

}//códigoposterioromitido

Nossasoluçãofunciona,mas imagineaplicá-laparaomodeloMensagem? Teríamos de repetir muito código, inclusive sua

manutenção não será lá uma das mais fáceis. Então, antes degeneralizarmosnossasolução,nopróximocapítulovamospediraajudadeoutropadrãodeprojetoparacolocarosertãoemordem.

O código completo deste capítulo você encontra emhttps://github.com/flaviohenriquealmeida/cangaceiro-javascript/tree/master/09

9.9APLICANDOASOLUÇÃOEMNEGOCIACAOCONTROLLER 209

Page 233: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

CAPÍTULO10

"... o mais importante e bonito, do mundo, é isto: que aspessoasnãoestãosempreiguais,aindanãoforamterminadas– mas que elas vão sempremudando.Afinamou desafinam.Verdademaior.Éoqueavidameensinou."-GrandeSertão:Veredas

Nocapítuloanterior,aplicamosopadrãodeprojetoProxyparadeixar clara uma separação entre o modelo e o código deinfraestrutura que criamos para mantê-lo sincronizado com aView. No entanto, vimos que podemos aplicar outro padrão deprojeto para tornar nossa solução mais genérica. Aplicaremos opadrãodeprojetoFactory.

O padrão de projeto Factory auxilia na criação de objetoscomplexos, encapsulando os detalhes de criação desses objetos,comonocasodosnossosproxies.

CÚMPLICENAEMBOSCADA

10.1OPADRÃODEPROJETOFACTORY

210 10CÚMPLICENAEMBOSCADA

Page 234: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Vamoscriaroarquivoclient/app/util/ProxyFactory.js.Apastautil não éumerrodedigitação.Éumapastanaqualficarão os arquivos que não fazem parte do domínio nem dainterface(UI)daaplicação.

AclasseProxyFactoryteráométodoestáticocreate,seuúnicométodo:

//client/app/util/ProxyFactory.js

classProxyFactory{

staticcreate(objeto,props,armadilha){

}

}

Ométodo create recebe três parâmetros. O primeiro é oobjetoalvodoproxy,osegundoéumarraycomosmétodosquedesejamos interceptar e, por fim, o último é a função armadilhaque desejamos executar para os métodos presentes no arrayprops.

Antesdemaisnada,vamosimportaroscriptquedevevirantesda importação de NegociacaoController , emclient/index.html:

<!--client/index.html--><!--códigoanterioromitido-->

<scriptsrc="app/domain/negociacao/Negociacao.js"></script><scriptsrc="app/controllers/NegociacaoController.js"></script><scriptsrc="app/ui/converters/DateConverter.js"></script><scriptsrc="app/domain/negociacao/Negociacoes.js"></script><scriptsrc="app/ui/views/View.js"></script><scriptsrc="app/ui/views/NegociacoesView.js"></script><scriptsrc="app/ui/models/Mensagem.js"></script><scriptsrc="app/ui/views/MensagemView.js"></script>

10.1OPADRÃODEPROJETOFACTORY 211

Page 235: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

<!--importouoProxyFactory--><scriptsrc="app/util/ProxyFactory.js"></script>

<scriptsrc="app/app.js"></script><!--códigoposterioromitido-->

Agora,vamosmoverocódigodeNegociacaoControler,quecria nosso proxy, para dentro do método create . Então,realizaremos os ajustes necessários para que ele leve emconsideraçãoosparâmetrosdométodo:

//client/app/util/ProxyFactory.js

classProxyFactory{

staticcreate(objeto,props,armadilha){

//recebeobjetocomoparâmetro

returnnewProxy(objeto,{

get(target,prop,receiver){//usaoarraypropspararealizaroincludesif(typeof(target[prop])==typeof(Function)

&&props.includes(prop)){

returnfunction(){

console.log(`"${prop}"disparouaarmadilha`);

target[prop].apply(target,arguments);

//executaaarmadilhaquerecebe//oobjetooriginal

armadilha(target);}

}else{

returntarget[prop];}

212 10.1OPADRÃODEPROJETOFACTORY

Page 236: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

}});

}}

Nãoprecisamosmaisdatag<script>,naqualrealizávamosnossostestes.Removaatagdeclient/index.html.

AgoraquetemosnossoProxyFactory,vamosutilizá-loparacriar nosso Proxy em NegociacaoController . Veja que nãoprecisaremos usar o self para termos acesso à instância deNegociacaoController emnossa armadilha, porquedessa vez

estamosusandoarrowfunction:

//client/app/controllers/NegociacaoController.js

classNegociacaoController{

constructor(){

const$=document.querySelector.bind(document);this._inputData=$('#data');this._inputQuantidade=$('#quantidade');this._inputValor=$('#valor');

//criandooproxycomauxíliodanossafábrica!

this._negociacoes=ProxyFactory.create(newNegociacoes(),['adiciona','esvazia'],model=>this._negociacoesView.update(model)

);

this._negociacoesView=newNegociacoesView('#negociacoes);

this._negociacoesView.update(this._negociacoes);

this._mensagem=newMensagem();this._mensagemView=newMensagemView('#mensagemView');this._mensagemView.update(this._mensagem);

}

10.1OPADRÃODEPROJETOFACTORY 213

Page 237: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

//códigoposterioromitido

Vejacomoconseguimosmelhoraremmuitoa legibilidadedonossocódigoe,porconseguinte,suamanutenção.

Agora,faremosamesmacoisacomomodeloMensagem.Masnocasodessemodelo,queremosmonitoraroacessoàpropriedadetexto:

//client/app/controllers/NegociacaoController.js

classNegociacaoController{

constructor(){

const$=document.querySelector.bind(document);this._inputData=$('#data');this._inputQuantidade=$('#quantidade');this._inputValor=$('#valor');

this._negociacoes=ProxyFactory.create(newNegociacoes(),['adiciona','esvazia'],model=>this._negociacoesView.update(model)

);

this._negociacoesView=newNegociacoesView('#negociacoes);

this._negociacoesView.update(this._negociacoes);

//criandooproxycomauxíliodanossafábrica!

this._mensagem=ProxyFactory.create(newMensagem(),['texto'],model=>this._mensagemView.update(model)

);

this._mensagemView=newMensagemView('#mensagemView');this._mensagemView.update(this._mensagem);

}

214 10.1OPADRÃODEPROJETOFACTORY

Page 238: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

//códigoposterioromitido

Porfim,vamosalterarosmétodosadiciona()eapaga(),removendoainstruçãoquerealizaoupdatemanualmentedaViewdeMensagem:

//client/app/controllers/NegociacaoController.js

classNegociacaoController{

constructor(){

//códigoomitido}

adiciona(event){

event.preventDefault();this._negociacoes.adiciona(this._criaNegociacao());this._mensagem.texto='Negociaçãoadicionadacomsucesso

;

//nãochamamaisoupdatedaviewdeMensagem

this._limpaFormulario();}

//códigoposterioromitido

apaga(){

this._negociacoes.esvazia();this._mensagem.texto='Negociaçõesapagadascomsucesso'

;//nãochamamaisoupdatedaviewdeMensagem

}}

Se recarregarmos a página no navegador, veremos que oformulário funciona corretamente, mas não atualizou amensagem.Oqueseráqueaconteceu?

10.1OPADRÃODEPROJETOFACTORY 215

Page 239: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

OProxyFactorysóestápreparadoparainterceptarmétodos;elenãoestápreparadoparainterceptarpropriedades.

Na classe Mensagem , get texto() é um getter e oProxyFactorynãoentendequeprecisainterceptarcomoget.Jáqueosgettersesetterssãoacessadoscomopropriedades,temostambémdecolocarnoProxyFactoryumcódigopara lidarmoscompropriedades.Paraisto,adicionaremosumsetnohandlerdonossoproxy:

//client/app/util/ProxyFactory.js

classProxyFactory{

staticcreate(objeto,props,armadilha){

//recebeumobjetocomoparâmetroreturnnewProxy(objeto,{

get(target,prop,receiver){

//códigoomitido!},

//NOVIDADE!set(target,prop,value,receiver){

constupdated=Reflect.set(target,prop,value);

//SÓEXECUTAMOSAARMADILHA//SEFIZERPARTEDALISTADEPROPS

if(props.includes(prop))armadilha(target);

returnupdated;}

10.2NOSSOPROXYAINDANÃOESTÁ100%!

216 10.2NOSSOPROXYAINDANÃOESTÁ100%!

Page 240: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

});}

}

Vamos recarregar a página no navegador e preencher oscampos do formulário. Tudo estará funcionando em nossaaplicação.

AindapodemosrealizarumamelhorianalegibilidadedonossoProxyFactory de último segundo. Nele temos a lógica que

determina se a propriedade é uma função, dentro do if .Podemos isolar essa comparação em um novo método estáticochamadoehFuncao(),eutilizá-lonacondiçãoparadeixarclaranossaintenção.

Comestaalteração,nossoProxyFactoryfinalestaráassim:

//client/app/util/ProxyFactory.js

classProxyFactory{

staticcreate(objeto,props,armadilha){

returnnewProxy(objeto,{

get(target,prop,receiver){

//USANDOOMÉTODOESTÁTICO

if(ProxyFactory._ehFuncao(target[prop])&&props.includes(prop)){

returnfunction(){

console.log(`"${prop}"disparouaarmadilha`);

target[prop].apply(target,arguments);armadilha(target);

}

10.2NOSSOPROXYAINDANÃOESTÁ100%! 217

Page 241: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

}else{

returntarget[prop];}

},

set(target,prop,value,receiver){

constupdated=Reflect.set(target,prop,value);

if(props.includes(prop))armadilha(target);returnupdated;

}

});}

//NOVOMÉTODOESTÁTICOstatic_ehFuncao(fn){

returntypeof(fn)==typeof(Function);}

}

Só alteramos amaneira pela qual organizamos nosso código,sem modificar seu comportamento. Mas aí vem aquela mesmaperguntadesempre:seráquepodemosmelhorarnossocódigo?

No capítulo anterior, criamos nosso ProxyFactory queencapsulougrandeparteda complexidadepara criarmosproxies.Isso possibilitou o reúso de todo o código que escrevemos semgrandescomplicações.Noentanto,temosdoispontosquedeixamadesejar.

Oprimeiropontoéprecisarmos lembrarderealizaroupdate

10.3 ASSOCIANDO MODELO E VIEWATRAVÉSDACLASSEBIND

218 10.3ASSOCIANDOMODELOEVIEWATRAVÉSDACLASSEBIND

Page 242: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

manual da View na inicialização do constructor() deNegociacoesController.Vejamos:

//REVISANDOOCÓDIGOAPENAS!

//client/app/controllers/NegociacaoController.js

classNegociacaoController{

constructor(){

const$=document.querySelector.bind(document);this._inputData=$('#data');this._inputQuantidade=$('#quantidade');this._inputValor=$('#valor');

this._negociacoes=ProxyFactory.create(newNegociacoes(),['adiciona','esvazia'],model=>this._negociacoesView.update(model)

);

this._negociacoesView=newNegociacoesView('#negociacoes);

//PRECISACHAMAROUPDATE

this._negociacoesView.update(this._negociacoes);

this._mensagem=ProxyFactory.create(newMensagem(),['texto'],model=>this._mensagemView.update(model)

);

this._mensagemView=newMensagemView('#mensagemView');

//PRECISACHAMAROUPDATEthis._mensagemView.update(this._mensagem);

}

Alémdisso, se observarmos atentamenteo código anterior, oupdatedaViewpassadoparaaarmadilhadoProxyépraticamente

10.3ASSOCIANDOMODELOEVIEWATRAVÉSDACLASSEBIND 219

Page 243: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

idêntico, sómudando aView que chamará ométodo update.Vamosnoslivrardessasresponsabilidades.

O que fizemos até agora se assemelha a um mecanismo deDatabinding(quetraduzidoparaoportuguês,significa"ligaçãodedados"). Associamos um modelo com a View, de modo quemudançasnomodeloatualizamautomaticamenteaView.VamoscriaraclasseBind, que será a responsável emexecutar as duasaçõesqueoprogramadoraindaprecisarealizar.

Criaremos o arquivo client/app/util/Bind.js , ondevamos declarar a classe Bind . Em seu constructor() ,receberemos três parâmetros. O primeiro será o modelo daassociação, o segundo, a View que deve ser atualizada a cadamudança do modelo e, por fim, o terceiro será o array com osnomes de propriedades e métodos que devem disparar aatualização.

//client/app/util/Bind.js

classBind{

constructor(model,view,props){

}}

Agora,noconstructor()deBind,vamoscriarumproxyatravés do nosso ProxyFactory , repassando os parâmetrosrecebidosparaométodocreate()dafactory:

//client/app/util/Bind.js

classBind{

constructor(model,view,props){

220 10.3ASSOCIANDOMODELOEVIEWATRAVÉSDACLASSEBIND

Page 244: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

constproxy=ProxyFactory.create(model,props,model=>{view.update(model)

});}

}

Sabemosque todaViewemnossa aplicaçãopossuiométodoupdate() que recebeummodelo, por isso, estamos chamandoview.update(model) na armadilha do proxy. Retiramos essa

responsabilidadedodesenvolvedor.

Agora,assimqueoproxyécriado,vamosforçarumachamadaao método update() da View, removendo mais umaresponsabilidadededesenvolvedor!

//client/app/util/Bind.js

classBind{

constructor(model,view,props){

constproxy=ProxyFactory.create(model,props,model=>{view.update(model)

});

view.update(model);}

}

Porfim,pormaisestranhoqueissopossaparecer,casooleitortenha vindo de outras linguagens como Java ou C#, faremos oconstructor() de Bind retornar o proxy que criamos. Em

JavaScript,umconstructor()poderetornarumobjetodeumtipodiferentedaclasseàqualpertence:

//client/app/util/Bind.js

classBind{

constructor(model,view,props){

10.3ASSOCIANDOMODELOEVIEWATRAVÉSDACLASSEBIND 221

Page 245: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

constproxy=ProxyFactory.create(model,props,model=>{view.update(model)

});

view.update(model);

returnproxy;}

}

Vamos importar o script que declara Bind emclient/index.html . Mas muito cuidado, pois

NegociacaoController depende de Bind, que depende deProxyFactory.Chegaráummomentoneste livro emque você

serálibertadodessegrandeproblemanalinguagemJavaScript,masaindanãoéahora.

<!--client/index.html--><!--códigoanterioromitido--><scriptsrc="app/domain/negociacao/Negociacao.js"></script><scriptsrc="app/controllers/NegociacaoController.js"></script><scriptsrc="app/ui/converters/DateConverter.js"></script><scriptsrc="app/domain/negociacao/Negociacoes.js"></script><scriptsrc="app/ui/views/View.js"></script><scriptsrc="app/ui/views/NegociacoesView.js"></script><scriptsrc="app/ui/models/Mensagem.js"></script><scriptsrc="app/ui/views/MensagemView.js"></script><scriptsrc="app/util/ProxyFactory.js"></script>

<!--importouaqui!--><scriptsrc="app/util/Bind.js"></script>

<scriptsrc="app/app.js"></script><!--códigoposterioromitido-->

Para terminar, alteraremos NegociacaoController eusaremos nossa classe Bind para fazer a associação entre omodeloeaView:

//client/app/controllers/NegociacaoController.js

222 10.3ASSOCIANDOMODELOEVIEWATRAVÉSDACLASSEBIND

Page 246: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

classNegociacaoController{

constructor(){

const$=document.querySelector.bind(document);this._inputData=$('#data');this._inputQuantidade=$('#quantidade');this._inputValor=$('#valor');

this._negociacoes=newBind(newNegociacoes(),newNegociacoesView('#negociacoes'),['adiciona','esvazia']

);

this._mensagem=newBind(newMensagem(),newMensagemView('#mensagemView'),['texto']

);}

//códigoposterioromitido

Recarregando a página, tudo continua funcionando comoantes, só que dessa vez simplificamos bastante a quantidade decódigo no constructor() de Negociacoes . Não foi maisnecessário declarar as propriedades _negociacaoView nem_mensagemView,nemmesmofoinecessáriochamaroupdate()manualmente das Views na inicialização deNegociacaoController.

AtravésdeBind,passamosainstânciadomodelo,ainstânciada View e as propriedades ou métodos que desejamos ativar aatualizaçãoautomática.Nemmesmofoinecessárioescrevermosalinhaquerealizaaatualizaçãodaviewapartirdomodelo.

Para fechar com chave de ouro, que tal enxugarmos uma

10.3ASSOCIANDOMODELOEVIEWATRAVÉSDACLASSEBIND 223

Page 247: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

gotinhanocódigoqueacabamosdeescrever?

EmnossoBind,onomedaspropriedadesedosmétodosquedesejamos executar nossas armadilhas foram passados dentro deum array. Apesar de ainda não funcionar, vamos passar cadaparâmetroindividualmente,abdicandodoarray:

//client/app/controllers/NegociacaoController.js

classNegociacaoController{

constructor(){

const$=document.querySelector.bind(document);this._inputData=$('#data');this._inputQuantidade=$('#quantidade');this._inputValor=$('#valor');

//nãopassamosmais"adiciona"e"esvazia"dentrodeumarray

this._negociacoes=newBind(newNegociacoes(),newNegociacoesView('#negociacoes'),'adiciona','esvazia'

);

//nãopassamosmais"texto"dentrodeumarraythis._mensagem=newBind(

newMensagem(),newMensagemView('#mensagemView'),'texto'

);}

Agora,paraquenossocódigofuncione,vamosalteraraclasseBind adicionando três pontos ( ... ) antes do parâmetroprops:

10.4PARÂMETROSREST

224 10.4PARÂMETROSREST

Page 248: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

//client/app/util/Bind.js

classBind{

//...antesdepropsconstructor(model,view,...props){

constproxy=ProxyFactory.create(model,props,model=>{view.update(model)

});

view.update(model);

returnproxy;}

}

Quando adicionamos ... antes do nome do últimoparâmetro,estamosindicandoquetodososparâmetrosapartirdoterceiro, inclusive,serãoconsideradoscomofazendopartedeumarray.Issotornareflexívelaquantidadedeparâmetrosqueum

métodooufunçãopodereceber.Masatenção,sópodemosusaroREST operator no último parâmetro. Aliás, este recurso foiintroduzidonoES2015(ES6)emdiante.

Até aqui criamos um mecanismo de data binding parasimplificar aindamaisnosso código.Podemosdar a tarefa comoconcluída. Todavia, há um problema antigo que voltará a nosassombrarnopróximocapítulo,oproblemacomdatas!

O código completo deste capítulo você encontra emhttps://github.com/flaviohenriquealmeida/cangaceiro-javascript/tree/master/10

10.4PARÂMETROSREST 225

Page 249: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

CAPÍTULO11

"O correr da vida embrulha tudo, a vida é assim: esquenta eesfria,apertaedaíafrouxa,sossegaedepoisdesinquieta.Oqueelaquerdagenteécoragem."-GrandeSertão:Veredas

Nossa aplicação continua funcionando como esperado, masprecisamosresolveraindaumproblemarelacionandoaocampodadatadonossoformulário.

O input do tipo date que utilizamos não faz parte daespecificação da W3C, sendo um tipo de elemento criado pelaequipedoGoogleChrome.Comooinputdotipodatenãofazpartedaespecificação,nãotemosagarantiadequeestejapresentenosmaisdiversosnavegadoresdomercado.Aliás, esse éoúnicomotivoquenosprendefortementeaestenavegador.

Precisamos alterar nossa aplicação para que utilize em seulugar um input do tipo text . Primeiro, vamos alterarclient/index.htmlemudarotipodoinput:

DATADOSINFERNOS!

11.1OPROBLEMACOMOINPUTDATE

226 11DATADOSINFERNOS!

Page 250: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

<!--client/index.html--><!--códigoanterioromitido--><divclass="form-group">

<labelfor="data">Data</label>

<!--eradate,passouasertext-->

<inputtype="text"id="data"class="form-control"requiredautofocus/></div><!--códigoposterioromitido-->

Contudo, essa alteração não é suficiente. Temos de alterar alógicadométodoparaData()daclasseDateConverter.Atéomomento,suaimplementaçãoestáassim:

//client/app/ui/converters/DateConverter.js

classDateConverter{

//códigoomitido

staticparaData(texto){

if(!/^\d{4}-\d{2}-\d{2}$/.test(texto))thrownewError('Deveestarnoformatoaaaa-mm-dd');

returnnewDate(...texto.split('-').map((item,indice)=>item-indice%2));

}}

Comonãoestamosmaisusandooinputdotipodate,nãorecebemos mais a string no formato aaaa-mm-dd. Recebemosexatamentecomoousuáriodigitounoformulário.

Nossoprimeiropassoseráajustarnossaexpressãoregularea

11.2AJUSTANDONOSSOCONVERTER

11.2AJUSTANDONOSSOCONVERTER 227

Page 251: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

mensagemdeerro,casootextoaserconvertidonãouseopadrão:

//client/app/ui/converters/DateConverter.js

classDateConverter{

//códigoomitido

staticparaData(texto){

//NOVAEXPRESSSÃOREGULAR//AMENSAGEMDEERROTAMBÉMMUDOU

if(!/\d{2}\/\d{2}\/\d{4}/.test(texto))thrownewError('Adatadeveestarnoformatodd/mm/a

aa');

returnnewDate(...texto.split('-').map((item,indice)=>item-indice%2));

}}

A alteração ainda não é suficiente, precisamos modificar alógica que desmembra nossa string.O primeiro passo é trocar oseparadorparasplit('/').Agora,precisamosinverteraordemdos elementos para ano, mês e dia. Antes não era necessário,porque a string recebida já estava nessa ordem. Resolvemos issofacilmenteatravésdafunçãoreverse()quetodoarraypossui:

//client/app/ui/converters/DateConverter.js

classDateConverter{

//códigoomitido

staticparaData(texto){

//NOVAEXPRESSSÃOREGULAR//AMENSAGEMDEERROTAMBÉMMUDOU

if(!/\d{2}\/\d{2}\/\d{4}/.test(texto))thrownewError('Adatadeveestarnoformatodd/mm/a

228 11.2AJUSTANDONOSSOCONVERTER

Page 252: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

aa');

//MUDOUOSEPARADORPARA/EUSOUREVERSE()

returnnewDate(...texto.split('/').reverse().map((item,indice)=>

item-indice%2));}

}

Pronto,issoésuficienteparaquenossaaplicaçãofuncioneemoutrosnavegadoresquenãosuporteoinputdotipodate.Masainda precisamos resolver um problema que já existia mesmoquandousávamosesteinput.

Sedigitarmosumadatainválida,nossovalidadorlançaráumaexceção que será apresentada apenas no console do navegador.Nenhuma mensagem informando o usuário sobre a entrada dadata errada é exibida para o usuário, e ele fica sem saber o queaconteceu.Chegouahoradelidarmoscomessaquestão.

Podemos capturar exceções em JavaScript lançadas comthrowsatravésdoblocotry,etratá-lasnoblococatch.No

bloco da instrução try , temos uma ou mais instruções quepotencialmentepodemlançarumaexceção.Casoalgumaexceçãoseja lançada, o fluxo do código é direcionado para o bloco docatchquerecebecomoparâmetroumobjetocominformações

daexceçãolançada.

Vamos alterar o método adiciona() deNegociacoesControllerparausarmosessanovasintaxe:

11.3LIDANDOCOMEXCEÇÕES

11.3LIDANDOCOMEXCEÇÕES 229

Page 253: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

//client/app/controllers/NegociacaoController.js//códigoanterioromitido

adiciona(event){

try{

event.preventDefault();this._negociacoes.adiciona(this._criaNegociacao());this._mensagem.texto='Negociaçãoadicionadacomsucesso

;this._limpaFormulario();

}catch(err){

console.log(err);this._mensagem.texto=err.message;

}}//códigoposterioromitido

Vejaque,noblocodocatch,aprimeiracoisaquefazemosélogarainformaçãodoerroquefoilançado,paralogoemseguidaatribuirmosotextodoerroàthis._mensagem.texto,atravésdeerr.message.

Setentarmosincluirumanovadatainválida,seráexibidaumamensagem de erro. Essa validação poderia ser feita de diversasformas,mas a forma atual possibilita introduzir certos conceitosimportantes para quem trilha o caminho de um cangaceiro emJavaScript.

Tudo muito bonito, mas há um detalhe que pode passardesapercebido por um ninja, mas nunca por um cangaceiro.Vamos alterar a última instrução do método adiciona()

forçandoumerrodedigitação:

//client/app/controllers/NegociacaoController.js//códigoanterioromitido

230 11.3LIDANDOCOMEXCEÇÕES

Page 254: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

adiciona(event){

try{

event.preventDefault();this._negociacoes.adiciona(this._criaNegociacao());this._mensagem.texto='Negociaçãoadicionadacomsucesso

;

//ATENÇÃO,FORÇANDOUMERRO//CHAMANDOUMMÉTODOQUENÃOEXISTEthis._limpaForm();

}catch(err){

console.log(err);this._mensagem.texto=err.message;

}}

//códigoposterioromitido

Oqueaconteceráaoincluirmosnegociações?Amensagemdeerroseráexibidaparaousuário:

Figura11.1:Mensagemdeerroquenãodeveriaaparecer

Só faz sentido exibir para o usuáriomensagens de alto nível,nãomensagemde erro de sintaxe.O problema é que não temoscomosaberotipodoerronoblocodocatch.

Umasoluçãoécriarmosnossasprópriasexceçõese,comajuda

11.3LIDANDOCOMEXCEÇÕES 231

Page 255: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

dainstruçãoinstanceof,perguntaríamosseaexceçãolançadaédo tipo em que estamos interessados. Vamos alterar o métodoadiciona():

//client/app/controllers/NegociacoesController.js

//códigoposterioromitidoadiciona(event){

try{

event.preventDefault();this._negociacoes.adiciona(this._criaNegociacao());this._mensagem.texto='Negociaçãoadicionadacomsucesso

;this._limpaForm();

}catch(err){

console.log(err);console.log(err.stack);

//TESTASEOTIPODOERROÉDEDATA,//SEFOR,EXIBEMENSAGEMPARAOUSUÁRIO

if(errinstanceofDataInvalidaException){

this._mensagem.texto=err.message;

}else{

//mensagemgenéricaparaqualquerproblemaquepossaacontecer

this._mensagem.texto='Umerronãoesperadoaconteceu. Entreemcontatocomosuporte';

}}

}

Ocódigo anteriornão funcionará, pois ainda não criamos aclasseDataInvalidaException.Porém,issonãonosimpedede

232 11.3LIDANDOCOMEXCEÇÕES

Page 256: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

analisá-lo. No bloco catch , verificamos através da instruçãoinstanceof se a exceção é uma instância de

DataInvalidaException,eexibimosamensagemdeerroparaousuário.Seoerronãofordaclassequeestamostestando,apenasexibimosamensagemnoconsole.

Agoraquesabemoscomonossocódigosecomportará,chegouahoradecriarmosnossaclasseDataInvalidaException,porquequemsabefazhoraenãoesperaacontecer!

Vamos criar nossa própria classe de erroDataInvalidaException herdando de Error . Criaremos o

arquivoclient/app/ui/converters/DataInvalidaException.js , no

qualdeclararemosnossaclasse,quecomeçaráassim:

//client/app/ui/converters/DataInvalidaException.js

classDataInvalidaExceptionextendsError{

constructor(){

super('Adatadeveestarnoformatodd/mm/aaaa');}}

Como a classe filha DataInvalidaException possui umconstructor()diferentedaclassepaiError,somosobrigadosa chamar super(), passando a informação necessária para oconstructor() do pai, a mensagem "A data deve estar no

formatodd/mm/aaaa".

11.4 CRIANDO NOSSA PRÓPRIA EXCEÇÃO:PRIMEIRATENTATIVA

11.4CRIANDONOSSAPRÓPRIAEXCEÇÃO:PRIMEIRATENTATIVA 233

Page 257: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Agora, vamos importar o script da classe que criamos emclient/index.htmlcomoúltimoscript:

<!--client/index.html--><!--códigoanterioromitido-->

<scriptsrc="app/domain/negociacao/Negociacao.js"></script><scriptsrc="app/controllers/NegociacaoController.js"></script><scriptsrc="app/ui/converters/DateConverter.js"></script><scriptsrc="app/domain/negociacao/Negociacoes.js"></script><scriptsrc="app/ui/views/View.js"></script><scriptsrc="app/ui/views/NegociacoesView.js"></script><scriptsrc="app/ui/models/Mensagem.js"></script><scriptsrc="app/ui/views/MensagemView.js"></script><scriptsrc="app/util/ProxyFactory.js"></script><scriptsrc "app/util/Bind js" </script

<!--importouaqui--><scriptsrc="app/ui/converters/DataInvalidaException.js"></script>

<scriptsrc="app/app.js"></script>

<!--códigoposterioromitido-->

Agora,precisamosalterara classeDateConverter paraqueseumétodoparaData()lanceaexceçãoqueacabamosdecriar:

//client/app/ui/converters/DateConverte.js

classDateConverter{

//códigoposterioromitido

staticparaData(texto){

//LANÇAANOVAEXCEÇÃO!

if(!/\d{2}\/\d{2}\/\d{4}/.test(texto))thrownewDataInvalidaException();

returnnewDate(...texto.split('/').reverse()

234 11.4CRIANDONOSSAPRÓPRIAEXCEÇÃO:PRIMEIRATENTATIVA

Page 258: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

.map((item,indice)=>item-indice%2));

}}

Vamos experimentar preencher os dados do formulário,mascomumadatainválida.Issofarácomqueamensagemdeerrosejaexibidaparaousuário.

Agora, se cadastramos uma negociação com data válida,teremoso errode chamadadeummétodoquenão existe sendoimpressoapenasnoconsole.Será?

Seolharmosasduasinformaçõesdeerroquesãoimpressasnoconsole,asegundaqueexibeerr.stackindicaqueoerrofoideError,enãoDataInvalidaException.Precisamosrealizaresseajusteatribuindoàpropriedadethis.nameherdadadeErrorovalordethis.constructor.name:

//client/app/ui/converters/DataInvalidaException.js

classDataInvalidaExceptionextendsError{

constructor(){

super('Adatadeveestarnoformatodd/mm/aaaa');

//ajustaonamedoerro!this.name=this.constructor.name;

}}

Umnovo testedemonstraqueháumaparidadedo resultadodeconsole.log(err)econsole.log(err.stack).

11.5 CRIANDO NOSSA PRÓPRIA EXCEÇÃO:SEGUNDATENTATIVA

11.5CRIANDONOSSAPRÓPRIAEXCEÇÃO:SEGUNDATENTATIVA 235

Page 259: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Apesar de a nossa solução funcionar, não é muito intuitivopara o programador ter de lembrar sempre de adicionar ainstruçãothis.name=this.constructor.nameparaajustaronome da exceção na stack. Hoje temos apenas um tipo de errocriadopornós,masesetivéssemosumadezenadeles?

Paraevitarqueodesenvolvedorprecise lembrarde realizaroajuste que vimos, vamos criar uma nova classe chamadaApplicationException . Todas as exceções que criarmos

herdarãodessaclasseemvezdeError.

Criando o arquivo emclient/app/util/ApplicationException.js:

//client/app/util/ApplicationException.js

classApplicationExceptionextendsError{

constructor(msg=''){

super(msg);this.name=this.constructor.name;

}}

Vamos importar o script em client/index.html . Masatenção: como haverá uma dependência entreDataInvalidaException eApplicationException, a última

deveserimportadaprimeiro:

<scriptsrc="app/domain/negociacao/Negociacao.js"></script><scriptsrc="app/controllers/NegociacaoController.js"></script><scriptsrc="app/ui/converters/DateConverter.js"></script><scriptsrc="app/domain/negociacao/Negociacoes.js"></script><scriptsrc="app/ui/views/View.js"></script><scriptsrc="app/ui/views/NegociacoesView.js"></script><scriptsrc="app/ui/models/Mensagem.js"></script><scriptsrc="app/ui/views/MensagemView.js"></script>

236 11.5CRIANDONOSSAPRÓPRIAEXCEÇÃO:SEGUNDATENTATIVA

Page 260: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

<scriptsrc="app/util/ProxyFactory.js"></script><scriptsrc="app/util/Bind.js"></script>

<!--importouaqui,precisaserantesdeDataInvalidaException-->

<scriptsrc="app/util/ApplicationException.js"></script><scriptsrc="app/ui/converters/DataInvalidaException.js"></script>

<scriptsrc="app/app.js"></script>

Agora,alterandoDataInvalidaException paraquepasse aherdardeApplicationException:

//client/app/ui/converters/DataInvalidaException.js

classDataInvalidaExceptionextendsApplicationException{

constructor(){

super('Adatadeveestarnoformatodd/mm/aaaa');}}

Ao recarregarmos a página e verificarmos as mensagens deerros impressasno consoleparaumadata inválida, vemosqueonomedoerroestácorretonaimpressãodastack.

Por fim, podemos voltar o nome correto da chamada dométodothis._limpaFormulario():

//client/app/controllers/NegociacaoController.js

//códigoanterioromitido

adiciona(event){

try{

event.preventDefault();this._negociacoes.adiciona(this._criaNegociacao());this._mensagem.texto='Negociaçãoadicionadacomsucesso

11.5CRIANDONOSSAPRÓPRIAEXCEÇÃO:SEGUNDATENTATIVA 237

Page 261: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

;

//VOLTOUCOMONOMECORRETODOMÉTODOthis._limpaFormulario();

}catch(err){//códigoomitido

}}

//códigoposterioromitido

Agora que já temos informações mais amigáveis para ousuário, podemos continuar com nossa aplicação. Aliás, umaaplicaçãonãoésófeitadeconversãodedatas,elatambéméfeitadeintegrações,assuntodopróximocapítulo.

O código completo deste capítulo você encontra emhttps://github.com/flaviohenriquealmeida/cangaceiro-javascript/tree/master/11

238 11.5CRIANDONOSSAPRÓPRIAEXCEÇÃO:SEGUNDATENTATIVA

Page 262: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

CAPÍTULO12

"Nocentrodosertão,oqueédoideiraàsvezespodeserarazãomaiscertaedemaisjuízo!"-GrandeSertão:Veredas

Nós aplicamos diversos recursos da linguagem JavaScriptduranteaconstruçãodanossaaplicação.Entretanto,paraqueelafiquecompleta,precisará importarnegociaçõesdeumaAPIweb.OprojetobaixadojápossuiumservidorcriadocomNode.js,quedisponibilizaaAPInecessáriaparanossaaplicação.

Vejamosa infraestruturamínimanecessáriaparaquesejamoscapazesdelevantaroservidor.

Para que seja possível levantar o servidor disponibilizado, éprecisoqueoNode.js (https://nodejs.org)esteja instaladoemsuamáquina.Masatenção,utilizesempreasversõespares,evitandoasversõesímparesdevidoàinstabilidadedaúltima.Aversão8.1.3do

PILHANDOOQUEINTERESSA!

12.1SERVIDOREINFRAESTRUTURA

12PILHANDOOQUEINTERESSA! 239

Page 263: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Node.jsfoiutilizadaduranteaescritadestelivro.

Para levantaroservidor,énecessárioummínimodetraquejocom o terminal (ou prompt de comando, noWindows) do seusistemaoperacional.Comoterminalaberto,vamosnosdirigiratéa pasta jscangaceiro/server . Com a certeza de estarmosdentrodapastacorreta,executaremosocomando:

npmstart

Istofarácomqueumservidorrodeesejaacessívelpormeiodoendereço http://localhost:3000 . Acessando esse endereço,automaticamenteapáginaindex.htmlqueestamostrabalhandoao longo do livro será carregada. Se preferir, pode digitarhttp://localhost:3000/index.html.

Apartirdeagora, sópodemosacessarindex.html atravésdo nosso servidor; caso contrário, não poderemos utilizarvários recursos que aprenderemos ao longo do livro quedependemderequisiçõesassíncronasrealizadasdoJavaScriptparaoservidor.

Além de servir nossa aplicação, o servidor disponibiliza trêsendereçosespeciais.Sãoeles:

localhost:3000/negociacoes/semanalocalhost:3000/negociacoes/anteriorlocalhost:3000/negociacoes/retrasada

Ao acessarmos o endereçohttp://localhost:3000/negociacoes/semana , sairemos da

240 12.1SERVIDOREINFRAESTRUTURA

Page 264: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

páginaindex.html e será exibida uma estrutura de dados noformatoJSON(JavaScriptObjectNotation):

Figura12.1:Dadosnoformatotexto

No entanto, não queremos exibir o arquivo JSON na telasaindo da página index.html. Nosso objetivo é clicarmos nobotão "Importar Negociações" , buscar os dados usandoJavaScript e inseri-los na tabela de negociações. Comoimplementamos nossomecanismo dedata binding, a tabela serárenderizadaautomaticamente.

Não usaremos jQuery ou nenhum outro atalho que blinde odesenvolvedordosdetalhesdecriaçãoderequisiçõesassíncronas.ComoestamostrilhandoocaminhodeumcangaceiroJavaScript,precisaremoslidardiretamentecomoobjetoXMLHttpRequest,ocentro nervoso do JavaScript para realização de requisiçõesassíncronas.

Precisamosfazerobotão"ImportarNegociações" chamaruma ação em nosso controller. Vejamos o HTML do botão emclient/index.html:

<!--client/index.html--><!--códigoanterioromitido-->

<divclass="text-center"><buttonid="botao-importa"class="btnbtn-primarytext-center

12.2 REQUISIÇÕES AJAX COM O OBJETOXMLHTTPREQUEST

12.2REQUISIÇÕESAJAXCOMOOBJETOXMLHTTPREQUEST 241

Page 265: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

type="button">ImportarNegociações

</button><buttonid="botao-apaga"class="btnbtn-primarytext-center"type="button">

Apagar</button></div>

<!--códigoposterioromitido-->

Vamos criar o método importaNegociacoes() emNegociacaoController que, por enquanto, exibirá na tela um

alerta:

//client/app/controllers/NegociacaoController.js

classNegociacaoController{

//códigoposterioromitido

importaNegociacoes(){

alert('Importandonegociações');}

}

Agora, alteraremos client/app/app.js e associaremos oeventoclickcomachamadadométodoqueacabamosdecriar:

//client/app/app.js

constcontroller=newNegociacaoController();

document.querySelector('.form')

.addEventListener('submit',controller.adiciona.bind(controller));

document.querySelector('#botao-apaga').addEventListener('click',controller.apaga.bind(controller))

;

242 12.2REQUISIÇÕESAJAXCOMOOBJETOXMLHTTPREQUEST

Page 266: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

//NOVIDADEAQUI!//associandooeventoàchamadadométodo

document.querySelector('#botao-importa')

.addEventListener('click',controller.importaNegociacoes.bind(controller));

Excelente, mas estamos repetindo várias vezes a instruçãodocument.querySelector . Faremos a mesma coisa que já

fizemosemNegociacaoController,vamoscriaroalias$para a instrução, tornando nosso código menos verboso. Nossoapp.jsficaráassim:

//client/app/app.js

constcontroller=newNegociacaoController();

//criouoalias

const$=document.querySelector.bind(document);

$('.form').addEventListener('submit',controller.adiciona.bind(controller));

$('#botao-apaga').addEventListener('click',controller.apaga.bind(controller))

;

//associandooeventoàchamadadométodo$('#botao-importa').addEventListener('click',controller.importaNegociacoes.bind(controller));

Serecarregarmosapáginanonavegadoredepoisclicarmosem"ImportarNegociações",oalertseráexibido.

12.2REQUISIÇÕESAJAXCOMOOBJETOXMLHTTPREQUEST 243

Page 267: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Figura12.2:Alertnonavegador

Comacertezadequerealizamosaassociaçãodoeventocomométododocontroller,substituiremosoalertporumainstânciadeXMLHttpRequest, umobjeto domundo JavaScript capaz derealizarrequisições:

//client/app/controllers/NegociacaoController.js

classNegociacaoController{

//códigoposterioromitido

importaNegociacoes(){

constxhr=newXMLHttpRequest();}

}

Agora, vamos solicitar à instânciaque acabamosde criarqueabraumaconexãocomoservidorparanós:

//client/app/controllers/NegociacaoController.js

classNegociacaoController{

//códigoposterioromitido

importaNegociacoes(){

constxhr=newXMLHttpRequest();xhr.open('GET','negociacoes/semana');

244 12.2REQUISIÇÕESAJAXCOMOOBJETOXMLHTTPREQUEST

Page 268: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

}}

Ométodoopen() recebeudois parâmetros: oprimeiro é otipoderequisiçãoaserrealizada(GET),osegundoéoendereçodoservidor.SetrabalhássemoscomoutroendereçodoserviçonaWeb,serianecessárioutilizaroendereçocompleto.Comoestamostrabalhandolocalmente,sóusamosnegociacoes/semana.

A requisição ainda não está pronta. Será preciso realizaralgumasconfiguraçõesantesdoenvio.Issoéfeitoatravésdeumafunção atribuída à propriedade onreadystatechange dainstânciadexhr:

//client/app/controllers/NegociacaoController.js

classNegociacaoController{

//códigoposterioromitido

importaNegociacoes(){

constxhr=newXMLHttpRequest();xhr.open('GET','negociacoes/semana');

xhr.onreadystatechange=()=>{

//realizaremosnossasconfiguraçõesaqui

};

}}

Todarequisiçãocomxhr(ouAJAX,termocomumutilizado)passa por estados - um deles nos dará os dados retornados doservidor.Essesestadossão:

12.2REQUISIÇÕESAJAXCOMOOBJETOXMLHTTPREQUEST 245

Page 269: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

0:requisiçãoaindanãoiniciada;

1:conexãocomoservidorestabelecida;

2:requisiçãorecebida;

3:processandorequisição;

4:requisiçãoestáconcluídaearespostaestápronta.

Oestado4éaquelequenosinteressa,porquenosdáacessoàresposta enviada pelo servidor. Precisamos descobrir em qualestado estamos quando o onreadystatechange for chamado.Paraisto,adicionaremosumif:

//client/app/controllers/NegociacaoController.js

classNegociacaoController{

//códigoposterioromitido

importaNegociacoes(){

constxhr=newXMLHttpRequest();xhr.open('GET','negociacoes/semana');

xhr.onreadystatechange=()=>{

if(xhr.readyState==4){

}};

}}

Oestado4indicaquearequisiçãofoiconcluídacomarespostapronta,contudonãopodemosconfiarnele.Podemosreceberumaresposta de erro do servidor que continua sendo uma resposta

246 12.2REQUISIÇÕESAJAXCOMOOBJETOXMLHTTPREQUEST

Page 270: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

válida.

AlémdetestarmosoreadyState da requisição, precisamostestar se xhr.status é igual a 200, um código padrão queindicaqueaoperaçãofoirealizadacomsucesso:

//client/app/controllers/NegociacaoController.js

classNegociacaoController{

//códigoposterioromitido

importaNegociacoes(){

constxhr=newXMLHttpRequest();xhr.open('GET','negociacoes/semana');

xhr.onreadystatechange=()=>{

if(xhr.readyState==4){

if(xhr.status==200){

console.log('Obtendoasnegociaçõesdoservidor.');}

}};

}}

Agora, toda vez que o readyState for 4 e o status fordiferentede200,exibiremosnoconsoleumamensagemindicandoquenão foi possível realizar a requisição. Inclusive, comoúltimainstrução do método, executaremos nossa requisição através dexhr.send():

//client/app/controllers/NegociacaoController.js

classNegociacaoController{

12.2REQUISIÇÕESAJAXCOMOOBJETOXMLHTTPREQUEST 247

Page 271: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

//códigoposterioromitido

importaNegociacoes(){

constxhr=newXMLHttpRequest();xhr.open('GET','negociacoes/semana');

xhr.onreadystatechange=()=>{

if(xhr.readyState==4){

if(xhr.status==200){

console.log('Obtendoasnegociaçõesdoservidor.');

}else{

console.log('Nãofoipossívelobterasnegociaçõesdasemana.');

}}

};

xhr.send();//executaarequisiçãoconfigurada}

}

Aquestãoagoraé:comoteremosacessoaosdadosqueforamretornadospeloservidor?

Temosacessoaosdadosretornadospelarequisiçãoatravésdapropriedade xhr.responseText , inclusive é na mesmapropriedadequerecebemosmensagensdeerros:

//client/app/controllers/NegociacaoController.js

classNegociacaoController{

//códigoposterioromitido

12.3REALIZANDOOPARSEDARESPOSTA

248 12.3REALIZANDOOPARSEDARESPOSTA

Page 272: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

importaNegociacoes(){

constxhr=newXMLHttpRequest();xhr.open('GET','negociacoes/semana');

xhr.onreadystatechange=()=>{

if(xhr.readyState==4){

if(xhr.status==200){

console.log('Obtendoasnegociaçõesdoservidor.');

console.log(xhr.responseText);}else{

console.log(xhr.responseText);this._mensagem.texto='Nãofoipossívelobt

erasnegociaçõesdasemana';}

}};

xhr.send();}

}

Em seguida, recarregaremos a página e veremos o que éexibidonoconsole:

Figura12.3:Textoexibidonoconsole

Vemosqueéumtextosendoexibido,enãoumarray.Comooprotocolo HTTP só trafega texto, o JSON é uma representaçãotextualdeumobjetoJavaScript.ComoauxíliodeJSON.parse(),convertemos o JSON de string para objeto, permitindo assimmanipulá-lofacilmente:

12.3REALIZANDOOPARSEDARESPOSTA 249

Page 273: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

//client/app/controllers/NegociacaoController.js

classNegociacaoController{

//códigoposterioromitido

importaNegociacoes(){

constxhr=newXMLHttpRequest();xhr.open('GET','negociacoes/semana');

xhr.onreadystatechange=()=>{

if(xhr.readyState==4){

if(xhr.status==200){

console.log('Obtendoasnegociaçõesdoservidor.');

//realizandooparseconsole.log(JSON.parse(xhr.responseText));

}else{console.log(xhr.responseText);

this._mensagem.texto='Nãofoipossívelobterasnegociaçõesdasemana';

}}

};

xhr.send();}

}

No console, vemos a saída deJSON.parse(), um array deobjetosJavaScript:

250 12.3REALIZANDOOPARSEDARESPOSTA

Page 274: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Figura12.4:Arraynoconsole

Pormais que tenhamos convertido o resultado da requisiçãoem um array de objetos, não podemos simplesmente adicionarcada item do array na lista de negociações através dethis._negociacoes.adiciona().OmétodoesperareceberumainstânciadeNegociacao e osdadosque recebemos são apenasobjetos com as propriedades data, quantidade e valor.Precisamos converter cada objeto para uma instância deNegociacaoantesdeadicioná-lo.

Realizaremosaconversãoatravésdafunçãomap() do arraydeobjetosqueconvertemos.Emseguida,comoarrayconvertido,vamos iterar em cada instância de Negociacao pela funçãoforEach,adicionandocadaumaemthis._negociacoes:

12.3REALIZANDOOPARSEDARESPOSTA 251

Page 275: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

//client/app/controllers/NegociacaoController.js//códigoposterioromitido

importaNegociacoes(){

constxhr=newXMLHttpRequest();xhr.open('GET','negociacoes/semana');

xhr.onreadystatechange=()=>{

if(xhr.readyState==4){

if(xhr.status==200){

//convertecadaobjetoparaumainstânciadeNegociacao

JSON.parse(xhr.responseText)

.map(objeto=>newNegociacao(objeto.data,objeto.quantidade,objeto.valor))

.forEach(negociacao=>this._negociacoes.adiciona(negociacao));

this._mensage.texto='Negociaçõesimportadascomsucesso!';

}else{console.log(xhr.responseText);

this._mensagem.texto='Nãofoipossívelobterasnegociaçõesdasemana';

}}

};

xhr.send();}

//códigoposterioromitido

Nosso código está quase completo, porque se tentarmosadicionar umanova negociação, receberemos umamensagemdeerro.

252 12.3REALIZANDOOPARSEDARESPOSTA

Page 276: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Agora, se acessarmos o endereçolocalhost:3000/negociacoes/semana,veremosqueadataestáemumformatodestringumpoucodiferente.

Figura12.5:Datanoformatoestranho

Nósestamospassandoobjeto.datanarepresentaçãotextualdiretamentenoconstructor() deNegociacao. Emvez isso,passaremos uma nova instância de Date que receberá em seuconstructor() a datano formato string, pois é uma estrutura

válida:

//client/app/controllers/NegociacaoController.js//códigoanterioromitido

//vejaqueagoratemosnewDate(object.data)

JSON.parse(xhr.responseText)

.map(objeto=>newNegociacao(newDate(objeto.data),objeto.quantidade,objeto.valor))

.forEach(negociacao=>this._negociacoes.adiciona(negociacao));

this._mensagem.texto='Negociaçõesimportadascomsucesso!';

//códigoposterioromitido

Figura12.6:Negociaçõesimportadascomsucesso

12.3REALIZANDOOPARSEDARESPOSTA 253

Page 277: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Amensagemseráexibidacorretamenteepodemosconsideraresta primeira parte finalizada. A seguir, faremos uma pequenabrincadeira para vermos o que acontece em um caso de erro.Mudaremos o endereço no importaNegociacoes() paranegociacoes/xsemana:

//client/app/controllers/NegociacaoController.js

//códigoanterioromitidoimportaNegociacoes(){

constxhr=newXMLHttpRequest();

//FORÇANDOUMERRO,UMENDEREÇOQUENÃOEXISTExhr.open('GET','negociacoes/xsemana');

//códigoposterioromitido

Neste caso, quando tentarmos recarregar a página, o usuárioveráaseguintemensagemdeerro:

Figura12.7:Mensagemdeerro

Sónãoesqueçamdevoltarcomoendereçocorretodepoisdoteste!.

Conseguimos realizar requisições assíncronas (Ajax) usandoJavaScript padrão, sem apelarmos para bibliotecas como jQuery.

254 12.3REALIZANDOOPARSEDARESPOSTA

Page 278: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Noentanto,nossocódigopodeficaraindamelhor.

Já somos capazes de importamos negociações, mas o queaconteceria se tivéssemos outros controllers e estes precisassemimportar negociações? Teríamos de repetir toda a lógica deinteração com o servidor. Além disso, o acesso de APIs não éresponsabilidadedeNegociacaoController,apenasacapturadeaçõesdousuárioerealizaraponteentreomodeleaview.

Vamos isolar a responsabilidade de interação como servidorem uma classe que a centralizará. Criaremos o arquivoclient/app/domain/negociacao/NegociacaoService.js:

//client/app/domain/negociacao/NegociacaoService.js

classNegociacaoService{

obterNegociacoesDaSemana(){

}}

Vamos importar o script que acabamos de criar emclient/index.html . Entretanto, como ele será uma

dependência no constructor() de NegociacaoController,deve ser importado antes deapp.js, pois é nesse arquivo quecriamosumainstânciadeNegociacaoController.

A classe terá um único método chamadoobterNegociacaoDaSemana() . Dentro desse método,

moveremos o código que escrevermos no métodoimportaNegociacoes()deNegociacaoController:

12.4SEPARANDORESPONSABILIDADES

12.4SEPARANDORESPONSABILIDADES 255

Page 279: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

<!--client/index.html--><!--códigoanterioromitido-->

<scriptsrc="app/domain/negociacao/Negociacao.js"></script><scriptsrc="app/controllers/NegociacaoController.js"></script><scriptsrc="app/ui/converters/DateConverter.js"></script><scriptsrc="app/domain/negociacao/Negociacoes.js"></script><scriptsrc="app/ui/views/View.js"></script><scriptsrc="app/ui/views/NegociacoesView.js"></script><scriptsrc="app/ui/models/Mensagem.js"></script><scriptsrc="app/ui/views/MensagemView.js"></script><scriptsrc="app/util/ProxyFactory.js"></script><scriptsrc="app/util/Bind.js"></script><scriptsrc="app/util/ApplicationException.js"></script><scriptsrc="app/ui/converters/DataInvalidaException.js"></script>

<!--importouaqui!--><scriptsrc="app/domain/negociacao/NegociacaoService.js"></script>

<scriptsrc="app/app.js"></script><!--códigoposterioromitido-->

Agora, vamos mover todo o código que já escrevemos dométodo importarNegociacoes de NegociacaoControllerparadentrodométododanossanovaclasse:

//client/app/domain/negociacao/NegociacaoService.js

classNegociacaoService{

obterNegociacoesDaSemana(){

//CÓDIGOMOVIDO!

constxhr=newXMLHttpRequest();xhr.open('GET','negociacoes/semana');

xhr.onreadystatechange=()=>{

if(xhr.readyState==4){

256 12.4SEPARANDORESPONSABILIDADES

Page 280: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

if(xhr.status==200){

JSON.parse(xhr.responseText)

.map(objeto=>newNegociacao(newDate(objeto.data),objeto.quantidade,objeto.valor))

.forEach(negociacao=>this._negociacoes.adiciona(negociacao));

this._mensagem.texto='Negociaçõesimportadascomsucesso!';

}else{console.log(xhr.responseText);

this._mensagem.texto='Nãofoipossívelobterasnegociaçõesdasemana';

}}

};

xhr.send();}

}

Como movemos o código, por enquanto o métodoimportaNegociacoes() ficará vazio em

NegociacaoController.

Agora, no constructor() de NegociacaoController ,vamosguardarnapropriedadethis._serviceumainstânciadeNegociacaoService , para que possamos acessar dentro de

qualquermétododaclasse:

//client/app/controllers/NegociacaoController.js

classNegociacaoController{

constructor(){

//códigoanterioromitido

this._service=newNegociacaoService();

12.4SEPARANDORESPONSABILIDADES 257

Page 281: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

}

Em seguida, ainda em nosso controller, no métodoimportaNegociacoes() que se encontra vazio, chamaremos o

métodoobterNegociacoesDaSemana()doserviço:

//client/app/controllers/NegociacaoController.js

//códigoanterioromitido

importaNegociacoes(){

this._service.obterNegociacoesDaSemana();}

//códigoposterioromitido

Do jeito que nosso código se encontra, não conseguiremosimportar as negociações, porque o métodoobterNegociacoesDaSemana() faz referência a elementos deNegociacaoController,eambosdevemserindependentes.

O primeiro passo para resolvermos esse problema éremovermosdométodoobterNegociacoesDaSemana()todasasreferênciasàspropriedadesdeNegociacaoController.Eleficaráassim:

//client/app/domain/negociacao/NegociacaoService.js

classNegociacaoService{

obterNegociacoesDaSemana(){

//CÓDIGOMOVIDO!

constxhr=newXMLHttpRequest();xhr.open('GET','negociacoes/semana');

xhr.onreadystatechange=()=>{

258 12.4SEPARANDORESPONSABILIDADES

Page 282: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

if(xhr.readyState==4){

if(xhr.status==200){

//NÃOFAZMAISOFOREACHJSON

.parse(xhr.responseText).map(objeto=>newNegociacao(newDate(ob

jeto.data),objeto.quantidade,objeto.valor));

//NÃOEXIBEMENSAGEM

}else{console.log(xhr.responseText);

//NÃOEXIBEMENSAGEM}

}};

xhr.send();}

}

Do jeito que deixamos nossa aplicação, não recebemos maismensagensdeerroaoimportamosnegociações,mastambémelasnão são inseridas na tabela. Inclusive, não temos qualquermensagem para o usuário de sucesso ou fracasso. De um lado,temos o método importaNegociacoes() deNegociacaoController que depende da lista de negociações

retornadas do servidor e, do outro, temos a classeNegociacaoService que sabebuscarosdados,masnão sabeo

quefazercomeles.

Para solucionarmos esse impasse, quem chama o métodothis._service.obterNegociacoesDaSemana()precisarápassarumafunçãocomalógicaqueseráexecutadaassimqueométodotrazer os dados do servidor. Para entendermos melhor essa

12.4SEPARANDORESPONSABILIDADES 259

Page 283: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

estratégia, primeiro vamos passar a função sem antes alterar ométodoobterNegociacoesDaSemana():

//client/app/controller/NegociacaoController.js//códigoanterioromitido

importaNegociacoes(){

this._service.obterNegociacoesDaSemana((err,negociacoes)=>{

if(err){this._mensagem.texto='Nãofoipossívelobternasne

gociaçõesdasemana';return;

}

negociacoes.forEach(negociacao=>this._negociacoes.adiciona(negociacao));

this._mensagem.texto='Negociaçõesimportadascomsucesso';

});}

//códigoposterioromitido

A funçãoquepassamos éumcallback (uma funçãoque seráchamada posteriormente), e ela segue o padrão Error-First-Callback.Essaabordagemémuitocomumquandoqueremoslidarcomcódigoassíncrono.Casoafunçãoquerecebeuocallbacknãoconsigaexecutarsuaoperação,noprimeiroparâmetrodacallbackrecebemosumerroe,nosegundo,null.

Caso a função que recebeu o callback consiga executar suaoperação,noprimeiroparâmetrodocallbackreceberemosnulle,nosegundo,osdadosresultantesdaoperação(emnossocaso,alista de negociações). A partir da ausência ou não de valor em

260 12.4SEPARANDORESPONSABILIDADES

Page 284: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

err,lidamoscomosucessooufracassodaoperação.

Agora, em NegociacaoService , vamos implementar ocallback(cb):

//client/app/domain/negociacao/NegociacaoService.js

classNegociacaoService{

obterNegociacoesDaSemana(cb){

constxhr=newXMLHttpRequest();xhr.open('GET','negociacoes/semana');

xhr.onreadystatechange=()=>{

if(xhr.readyState==4){

if(xhr.status==200){

constnegociacoes=JSON.parse(xhr.responseText)

.map(objeto=>newNegociacao(newDate(objeto.data),objeto.quantidade,objeto.valor));

//OPERAÇÃOCONCLUÍDA,SEMERROcb(null,negociacoes);

}else{console.log(xhr.responseText);

//ERRONAOPERAÇÃO!cb('Nãofoipossívelobternasnegociaçõesd

asemana',null);}

}};

xhr.send();}

}

Se ocorrer um erro, executaremos o cb exibindo uma

12.4SEPARANDORESPONSABILIDADES 261

Page 285: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

mensagemdealtonível,einformandoparaousuárioquenãofoipossível obter as negociações. Ao recarregarmos a página epreenchermos os dados do formulário, veremos a mensagem desucesso.

Figura12.8:Negociaçõesforamimportadascomsucesso

Nosso código funciona como esperado. Mas será que ele sesustenta ao longo do tempo? Há outra maneira de deixarmosnossocódigoaindamelhor?Veremosnopróximocapítulo.

O código completo deste capítulo você encontra emhttps://github.com/flaviohenriquealmeida/cangaceiro-javascript/tree/master/12

262 12.4SEPARANDORESPONSABILIDADES

Page 286: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

CAPÍTULO13

"Cavaloqueamaodonoatérespiradomesmojeito"-GrandeSertão:Veredas

O padrão Error-First-Callback é bastante comum,principalmente no mundo Node.js para lidar com códigoassíncrono.Contudo,sua implementaçãopoluia interfacedeusodemétodosefunções.

Hoje,sequisermosobterumalistadenegociações,chamamoso método obtemNegociacoesDaSemana() deNegociacaoService . Porém, o método só recebe o callback

comoparâmetroparatermosacessoaoresultadoouaoerrodesuachamada.O parâmetro de obtemNegociacoesDaSemana() nãofaz parte do domínio de negociações, é um parâmetro mais deinfraestruturadonossocódigo.

A mesma coisa acontece com o método get() deHttpService.Elerecebeaurldoservidoremquebuscaráas

informações. Já o segundo parâmetro é um callback paracapturarmosoretornodesucessoouerrodaoperação.

LUTANDOATÉOFIM

13LUTANDOATÉOFIM 263

Page 287: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Alémdoquevimos,precisamossempretestarpelaausênciaounão do valor de err para sabermos se a operação foi bem-sucedida.Parapiorar,podemoscairnoCallbackHELL.

OutropontodessaabordageméquepodemoscairnoCallbackHELL, uma estrutura peculiar em nosso código resultante deoperações assíncronas que lembra uma pirâmide deitada.Vejamos um exemplo no qual realizamos quatro requisiçõesget() com HttpService, para exibirmos no final uma lista

consolidadacomosdadosdetodasasrequisições:

//EXEMPLO,NÃOENTRAEMNOSSOCÓDIGO

constservice=newHttpService();

letresultado=[];

service.get('http://wwww.endereco1.com',(err,dados1)=>{resultado=resultado.concat(dados1);service.get('http://wwww.endereco2.com',(err,dados2)=>{resultado=resultado.concat(dados2);service.get('http://wwww.endereco3.com',(err,dados3)=>{resutado=resultado.concat(dados3);service.get('http://wwww.endereco4.com',(err,dados4)=>{

resultado=resultado.concat(dados4);console.log('Listacompleta');console.log(resultado);

});});

});});

Não precisamos meditar muito para percebemos que essaestrutura não é lá uma das melhores para darmos manutenção.Será que existe outro padrão que podemos utilizar para

13.1CALLBACKHELL

264 13.1CALLBACKHELL

Page 288: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

escrevermosumcódigomaislegívelefácildemanter?

Há o padrão de projeto Promise criado para lidar com acomplexidade de operações assíncronas.Aliás, a partir da versãoES2015 (ES6), este padrão tornou-se nativo na linguagemJavaScript.

Uma Promise é o resultado futuro de uma ação; e quandodizemos futuro, é porque pode ser fruto de uma operaçãoassíncrona. Por exemplo, quando acessamos um servidor, arequisiçãopodedemorarmilésimosousegundos,tudodependerádeváriosfatoresaolongodarequisição.

Antes de refatorarmos (alterarmos sem mudarcomportamento) por completo o nosso código, vamos alterarapenasNegociacaoController,maispropriamenteseumétodoimportaNegociacoes(),paraqueconsidereumaPromisecomoretorno do métodothis._service.obtemNegociacoesDaSemana().

Alterandonossocódigo:

//client/app/controllers/NegociacaoController.js

//códigoanterioromitidoimportaNegociacoes(){

//apagouocódigoanterior,começandodozeroconstpromise=this._service.obtemNegociacoesDaSemana();}

Duas coisas notáveis se destacam nessa linha de código. Aprimeira é a ausência da passagem do callback para o método

13.2OPADRÃODEPROJETOPROMISE

13.2OPADRÃODEPROJETOPROMISE 265

Page 289: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

this._service.obtemNegociacoesDaSemana(). O segundo, oresultado da operação é uma Promise. É através da funçãothen()queinteragimoscomaPromise.

A função then() recebe duas funções (callbacks) comoparâmetros. A primeira função nos dá acesso ao retorno daoperação assíncrona, já o segundo a possíveis erros que possamocorrerduranteaoperação.Alterandonossocódigo:

//client/app/controllers/NegociacaoController.js

//códigoanterioromitido

importaNegociacoes(){

constpromise=this._service.obtemNegociacoesDaSemana();

promise.then(negociacoes=>{negociacoes.forEach(negociacao=>this._negociacoes.a

diciona(negociacao));this._mensagem.texto='Negociaçõesimportadascomsu

cesso';},err=>this._mensagem.texto=err

);}

//códigoposterioromitido

Podemos simplificar o código anterior eliminando a variávelpromise:

//client/app/controllers/NegociacaoController.js

//códigoanterioromitido

importaNegociacoes(){

this._service.obtemNegociacoesDaSemana().then(

266 13.2OPADRÃODEPROJETOPROMISE

Page 290: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

negociacoes=>{negociacoes.forEach(negociacao=>this._negociaco

es.adiciona(negociacao));this._mensagem.texto='Negociaçõesimportadasco

msucesso';},err=>this._mensagem.texto=err

);}

//códigoposterioromitido

Vamos recapitular!Métodos que retornamumaPromise nãoprecisamreceberumcallback,evitandopoluirsuainterfacedeuso.Afunçãothen()evitaacondiçãoifpara tratarosucessooufracasso,pois trataambosseparadamentenas funçõesdecallbackque recebe. A primeira função é para obtermos o resultado daoperaçãoe,asegunda,errosquepossamacontecerequeimpedemqueaoperaçãosejaconcluída.

Excelente. Porém, nosso código ainda não funciona, pois ométodo obtemNegociacoesDaSemana() deNegociacaoServiceaindanãoretornaumaPromise.Chegoua

horadealterá-lo.

O primeiro passo para convertermos o métodoobtemNegociacoesDaSemana() de NegociacaoServide é

adicionar todo seu código existente dentro do bloco de umaPromise.Primeiro,vejamosaestruturadeumaPromisequandoaestamoscriando:

//EXEMPLO,NÃOENTRANAAPLICAÇÃOAINDA

newPromise((resolve,reject)=>{

13.3CRIANDOPROMISES

13.3CRIANDOPROMISES 267

Page 291: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

//lógicadapromise!

});

O constructor() de uma Promise recebe uma funçãopreparada para receber dois parâmetros. O primeiro é umareferência para uma função que deve receber o resultado daoperação assíncronaque encapsula. Já o segundo, possíveis errosquepossamacontecer.

Vamos alterar o método obtemNegociacoesDaSemana()envolvendo todo o código já existente dentro do bloco de umanovaPromise:

//client/domain/negociacao/NegociacaoService.js//códigoanterioromitido

obtemNegociacoesDaSemana(cb){

returnnewPromise((resolve,reject)=>{

/*iníciodoblocodaPromise*/

constxhr=newXMLHttpRequest();xhr.open('GET','negociacoes/semana');

xhr.onreadystatechange=()=>{

if(xhr.readyState==4){

if(xhr.status==200){

constnegociacoes=JSON.parse(xhr.responseText)

.map(objeto=>newNegociacao(newDate(objeto.data),objeto.quantidade,objeto.valor));

cb(null,negociacoes);}else{

268 13.3CRIANDOPROMISES

Page 292: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

cb('Nãofoipossívelobternasnegociações',null);

}}

};

xhr.send();

/*fimdoblocodaPromise*/

});}//códigoposterioromitido

Afunçãopassadaparaoconstructor()dePromisepossuidois parâmetros, resolve e reject . O primeiro é umareferênciaparaumafunçãonaqualpassamosovalordaoperação.Nosegundo,passamosacausadofracasso.

Agora,noslocaisqueusamoscb,utilizaremosresolve()ereject().Ométodoficaráassim:

//client/domain/negociacao/NegociacaoService.js//códigoanterioromitido

//NÃORECEBEMAISOCALLBACKobtemNegociacoesDaSemana(){

returnnewPromise((resolve,reject)=>{

constxhr=newXMLHttpRequest();xhr.open('GET','negociacoes/semana');

xhr.onreadystatechange=()=>{

if(xhr.readyState==4){

if(xhr.status==200){

constnegociacoes=JSON.parse(xhr.responseText).map(objeto=>newNegociacao(newDate(ob

13.3CRIANDOPROMISES 269

Page 293: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

jeto.data),objeto.quantidade,objeto.valor));

//CHAMOURESOLVEresolve(negociacoes);

}else{

//CHAMOUREJECTreject('Nãofoipossívelobternasnegociaçõe

s');}

}};

xhr.send();

});}//códigoposterioromitido

Perfeito!Aimportaçãodasnegociaçõescontinuafuncionandocom o novo padrão de organização de código com Promise.Noentanto,podemosdeixarnossocódigoaindamelhor.

O método obtemNegociacoesDaSemana() deNegociacaoService , apesar de funcional, mistura

responsabilidades.Nele,temosocódigoresponsávelemlidarcomo objetoXMLHttpRequest eoque trata a resposta específicadalistadenegociações.

Oproblemada abordagemanterior é aduplicaçãodo códigodeconfiguraçãodoXMLHttpRequest em todososmétodosqueprecisarem realizar este tipo de requisição. Para solucionarmosesse problema, vamos isolar em uma classe de serviço acomplexidade de se lidar com XMLHttpRequest, que também

13.4CRIANDOUMSERVIÇOPARAISOLARACOMPLEXIDADEDOXMLHTTPREQUEST

270 13.4CRIANDOUMSERVIÇOPARAISOLARACOMPLEXIDADEDOXMLHTTPREQUEST

Page 294: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

utilizaráopadrãoPromise:

Vamoscriaroarquivoclient/app/util/HttpService.js:

//client/app/util/HttpService.js

classHttpService{

get(url){

returnnewPromise((resolve,reject)=>{

constxhr=newXMLHttpRequest();

xhr.open('GET',url);

xhr.onreadystatechange=()=>{

if(xhr.readyState==4){

if(xhr.status==200){

//PASSOUORESULTADOPARARESOLVE//JÁPARSEADO!

resolve(JSON.parse(xhr.responseText));}else{

console.log(xhr.responseText);

//PASSOUOERROPARAREJECTreject(xhr.responseText);

}}

};

xhr.send();

});}}

Agora, não podemos nos esquecer de importar o script em

13.4CRIANDOUMSERVIÇOPARAISOLARACOMPLEXIDADEDOXMLHTTPREQUEST 271

Page 295: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

client/index.html . Atenção para a ordem de importação.ComooscriptseráumadependêncianoNegociacaoService,eledeveserimportadoantes:

<!--client/index.html--><!--códigoanterioromitido-->

<scriptsrc="app/domain/negociacao/Negociacao.js"></script><scriptsrc="app/controllers/NegociacaoController.js"></script><scriptsrc="app/ui/converters/DateConverter.js"></script><scriptsrc="app/domain/negociacao/Negociacoes.js"></script><scriptsrc="app/ui/views/View.js"></script><scriptsrc="app/ui/views/NegociacoesView.js"></script><scriptsrc="app/ui/models/Mensagem.js"></script><scriptsrc="app/ui/views/MensagemView.js"></script><scriptsrc="app/util/ProxyFactory.js"></script><scriptsrc="app/util/Bind.js"></script><scriptsrc="app/util/ApplicationException.js"></script><scriptsrc="app/ui/converters/DataInvalidaException.js"></script>

<scriptsrc="app/domain/negociacao/NegociacaoService.js"></script>

<!--importouaqui!--><scriptsrc="app/util/HttpService.js"></script>

<scriptsrc="app/app.js"></script>

<!--códigoposterioromitido-->

Vejamosumexemplodenossoserviçofuncionando:

//EXEMPLOAPENAS,AINDANÃOENTRANAAPLICAÇÃO

consthttp=newHttpService();http.get('negociacoes/semana').then(dados=>console.log(dados),

err=>console.log(err));

272 13.4CRIANDOUMSERVIÇOPARAISOLARACOMPLEXIDADEDOXMLHTTPREQUEST

Page 296: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Agora, precisamos integrar HttpService comNegociacaoService. Durante essa integração, novos detalhes

sobrePromisessaltarãoaosolhos.

O primeiro passo é adicionarmos uma instância deHttpService como dependência de NegociacaoService ,

atravésdapropriedadethis._http:

//client/app/domain/service/NegociacaoService.js

classNegociacaoService{

constructor(){

//NOVAPROPRIEDADE!this._http=newHttpService();

}

obtemNegociacoesDaSemana(){

//códigoanterioromitido}}

Vamos alterar o método obtemNegociacoesDaSemana() ,paraquefaçausodonossonovoserviço:

//client/app/domain/service/NegociacaoService.js

classNegociacaoService{

constructor(){

this._http=newHttpService();}

obtemNegociacoesDaSemana(){

//ATENÇÃO,RETURNAQUI!

returnthis._http

13.4CRIANDOUMSERVIÇOPARAISOLARACOMPLEXIDADEDOXMLHTTPREQUEST 273

Page 297: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

.get('negociacoes/semana').then(

dados=>{

constnegociacoes=dados.map(objeto=>newNegociacao(newDate(objeto.data),objeto.qu

antidade,objeto.valor));

//ATENÇÃOAQUI!returnnegociacoes;

},err=>{

//ATENÇÃOAQUI!thrownewError('Nãofoipossívelobterasnegociaç

ões');}

);}}

Não criamos uma nova Promise, e sim retornamos uma jáexistente, no caso, a Promise criada pelo método get() deHttpService.APromise retornada já sabe lidarcomosucesso

oufracassodarequisição.Alémdessedetalhe,hámaisdoispontosnotáveisdonossocódigo.

Oprimeiroéoreturnnegociacoesdocallbackdesucesso,passado para then(). Por regra, toda Promise que possui umreturn disponibilizará o valor retornadona próxima chamada

encadeada à função then() . Sendo assim, quem chamar ométodo obtemNegociacoesDaSemana() pode ter acesso aoreturnnegociacoes,dessamaneira:

//EXEMPLO,NÃOENTRANAAPLICAÇÃOAINDA

constservice=newNegociacaoService();service.obtemNegociacoesDaSemana()//AQUITEMOSACESSOAORETURNDAPROMISE.then(negociacoes=>console.log(negociacoes));

274 13.4CRIANDOUMSERVIÇOPARAISOLARACOMPLEXIDADEDOXMLHTTPREQUEST

Page 298: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Por fim, ainda nométodo obtemNegociacoesDaSemana(),quandoaPromisecriadapelométodoget()deHttpServicefalha, lançamosumaexceçãoatravésdethrownewError('Nãofoi possível obter as negociações') . Isso é necessárioquandoqueremosdevolverumanovarespostadeerro:

//EXEMPLO,NÃOENTRANAAPLICAÇÃOAINDA

constservice=newNegociacaoService();service.obtemNegociacoesDaSemana().then(negociacoes=>console.log(negociacoes),//AQUIACESSAMOSOERRODOTHROWSerr=>console.log(err);

);

Nosso código continua com o mesmo comportamento, sóalteramos sua organização. Continuamos importando asnegociações da semana clicando no botão "Importar

Negociações"doformulário.

Nossaaplicação,alémde importarasnegociaçõesda semana,deve importar as negociações da semana anterior. Inclusive, háumaAPInoservidordisponibilizadoqueretornaessainformação.No fim, queremos exibir todas asnegociações comoum todonamesmatabela.

Em NegociacaoService , criaremos o métodoobtemNegociacoesDaSemanaAnterior() que terá um código

idêntico ao método que já temos. A única coisa que muda é o

13.5 RESOLVENDO PROMISESSEQUENCIALMENTE

13.5RESOLVENDOPROMISESSEQUENCIALMENTE 275

Page 299: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

endereçodaAPIeamensagemdeerro:

//client/app/domain/negociacao/NegociacaoService.js//codigoposterioromitido

obtemNegociacoesDaSemanaAnterior(){

returnthis._http.get('negociacoes/anterior').then(

dados=>{

constnegociacoes=dados.map(objeto=>newNegociacao(newDate(objeto.data),objeto.

quantidade,objeto.valor));

returnnegociacoes;},err=>{

//ATENÇÃOAQUI!thrownewError('Nãofoipossívelobterasnegoci

açõesdasemanaanterior');}

);}

//códigoanterioromitido

Agora, vamos alterar ométodoimportaNegociacoes() deNegociacaoControllerparaimportarasnegociaçõesdasemanaanterior. Vejamos o código primeiro, para em seguidacomentarmosaseurespeito:

//client/app/controllers/NegociacaoController.js

//códigoanterioromitido

importaNegociacoes(){

constnegociacoes=[];

this._service.obtemNegociacoesDaSemana()

276 13.5RESOLVENDOPROMISESSEQUENCIALMENTE

Page 300: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

.then(semana=>{

//USANDOSPREADOPERATORnegociacoes.push(...semana);

//QUANDORETORNAMOSUMAPROMISE,SEURETORNO//ÉACESSÍVELAOENCADEARUMACHAMADAÀTHEN

returnthis._service.obtemNegociacoesDaSemanaAnterior();

}).then(anterior=>{

negociacoes.push(...anterior);negociacoes.forEach(negociacao=>this._negociacoes.a

diciona(negociacao));this._mensagem.texto='Negociaçõesimportadascomsu

cesso';}).catch(err=>this._mensagem.texto=err);

}

Criamosoarraynegociacoesvazioparaquepossareceberasnegociações da semana e da semana anterior. Começamoschamando o métodothis._service.obtemNegociacoesDaSemana() que retorna

uma Promise. Através do método then() , temos acesso aoretornodaoperação-nenhumanovidadeatéagora.

Ainda em then() , realizamos umnegociacoes.push(...anterior) usando o spread operator,

quedesmembrarácadaelementodoarrayanterior, passando-os como parâmetro para a função push(). Ela está preparadaparalidarcomumoumaisparâmetros.

Depois de incluirmos as negociações da semana no arraynegociacoes , executamos a enigmática instrução return

this._service.obtemNegociacoesDaSemanaAnterior() . O

13.5RESOLVENDOPROMISESSEQUENCIALMENTE 277

Page 301: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

returnnãoestáláporacaso,todafunçãothen()queretornaumaPromisetornaoresultadodessaPromiseacessívelnapróximachamadaàthen().Éporissoquetemos:

//REVISANDOAPENAS!//códigoanterioromitido

.then(anterior=>{

negociacoes.push(...anterior);negociacoes.forEach(negociacao=>this._negociacoes.adiciona(negociacao));

this._mensagem.texto='Negociaçõesimportadascomsucesso';})//códigoposterioromitido

Nessa chamada, anterior são as negociações das semanasanteriores, resultadodaPromiseque foi retornada.Vejaqueessaestratégia evita o Callback HELL, pois não temos chamadas defunçõesaninhadas.

Por fim, temos uma chamada inédita à função catch() ,presenteemtodaPromise.ElapermitecentralizarotratamentodeerrosdasPromisesemumúnicolugar.IssoevitaquetenhamosdepassarocallbackparatrataroerroemcadaPromise.

Recarregando nossa página e clicando no botão "ImportarNegociações",tudocontinuafuncionando,excelente.MasaindafaltamaisummétodoemNegociacaoService.

Precisamos importar também as negociações da semanaretrasada. Para isso, vamos criar o método

obtemNegociacoesDaSemanaRetrasada() emNegociacaoService . Assim como o método que criamos

anteriormente, ele será idêntico aoobtemNegociacoesDaSemana(),apenasoendereçodaAPIesua

278 13.5RESOLVENDOPROMISESSEQUENCIALMENTE

Page 302: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

mensagemdeerroserãodiferentes:

//client/app/domain/negociacao/NegociacaoService.js//códigoanterioromitido

obtemNegociacoesDaSemanaRetrasada(){

returnthis._http.get('negociacoes/retrasada').then(

dados=>{

constnegociacoes=dados.map(objeto=>newNegociacao(newDate(objeto.data),objeto.

quantidade,objeto.valor));

returnnegociacoes;},err=>{thrownewError('Nãofoipossívelobterasnegoci

açõesdasemanaretrasada');}

);}

Agora, repetiremos o que já fizemos anteriormente eincorporaremos a chamada do nosso método emimportaNegociacoes()deNegociacaoController:

//client/app/domain/negociacao/NegociacaoService.js//códigoanterioromitido

importaNegociacoes(){

constnegociacoes=[];

this._service.obtemNegociacoesDaSemana().then(semana=>{

negociacoes.push(...semana);

returnthis._service.obtemNegociacoesDaSemanaAnterior();

13.5RESOLVENDOPROMISESSEQUENCIALMENTE 279

Page 303: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

}).then(anterior=>{

negociacoes.push(...anterior);returnthis._service.obtemNegociacoesDaSemanaRetrasad

a()}).then(retrasada=>{

negociacoes.push(...retrasada);negociacoes.forEach(negociacao=>this._negociacoes.a

diciona(negociacao));this._mensagem.texto='Negociaçõesimportadascomsu

cesso';}).catch(err=>this._mensagem.texto=err);

}

//códigoposterioromitido

NossométodoresolvenossasPromisessequencialmente,istoé,ele só tentará resolver a segunda caso a primeira tenha sidoresolvida, e só resolverá a terceira caso a segunda tenha sidoresolvida. Todavia, em alguns cenários, pode ser interessanteresolvermos todas as Promises em paralelo, principalmentequando a ordem de resolução não é importante. É isso queveremosaseguir.

PodemosresolvernossasPromisesparelamentecomauxíliodePromise.all() , que recebe um array de Promises como

parâmetro. Vamos recomeçar do zero o métodoimportaNegociacoes()deNegociacaoController.

//client/app/controllers/NegociacaoController.js//códigoanterioromitido

13.6 RESOLVENDO PROMISESPARALELAMENTE

280 13.6RESOLVENDOPROMISESPARALELAMENTE

Page 304: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

importaNegociacoes(){

//RECEBEUMARRAYDEPROMISES

Promise.all([this._service.obtemNegociacoesDaSemana(),this._service.obtemNegociacoesDaSemanaAnterior(),this._service.obtemNegociacoesDaSemanaRetrasada()

]).then(periodo=>console.log(periodo)).catch(err=>this._mensagem.texto=err);

}

A função Promise.all() resolverá todas as Promises emparalelo, o que pode reduzir drasticamente o tempo gasto comessasoperações.Quandotodastiveremsidoresolvidas,achamadaàfunçãothen()nosdevolveráumarraydearrays, istoé, cadaposição do array terá a lista de negociações retornada por cadaPromise, na mesma ordem em que foram passadas paraPromise.all(). Podemos verificar essa informação olhando a

saídadeconsole.log(periodo).

Nãopodemossimplesmenteiterarnessearrayeadicionarcadanegociação em this._negociacoes, pois cada item do array,como vimos, é um array. Precisamos tornar periodo em umarray de única dimensão, ou seja, queremos achatar (flaten) estearray.

Podemos conseguir este resultado facilmente com auxílio dafunçãoreduce(),quejáutilizamosnoscapítulosanteriores:

//client/app/controllers/NegociacaoController.js//códigoanterioromitido

importaNegociacoes(){

Promise.all([this._service.obtemNegociacoesDaSemana(),

13.6RESOLVENDOPROMISESPARALELAMENTE 281

Page 305: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

this._service.obtemNegociacoesDaSemanaAnterior(),this._service.obtemNegociacoesDaSemanaRetrasada()

]).then(periodo=>{

//periodoaindaéumarraycom3elementosquesãoarray

periodo=periodo.reduce((novoArray,item)=>novoArray.concat(item),[]);

//umarraycomnomeelementosconsole.log(periodo);

}).catch(err=>this._mensagem.texto=err);

}

Comestaalteração,periodopassaaserumarraycomnoveelementosemvezdeumcomtrêselementosdotipoarray.Agora,podemos adicionar cada item do novo array em nossa lista denegociações:

//client/app/controllers/NegociacaoController.js//códigoanterioromitido

importaNegociacoes(){

Promise.all([this._service.obtemNegociacoesDaSemana(),this._service.obtemNegociacoesDaSemanaAnterior(),this._service.obtemNegociacoesDaSemanaRetrasada()

]).then(periodo=>{

periodo.reduce((novoArray,item)=>novoArray.concat(item),

[]).forEach(negociacao=>this._negociacoes.adiciona(neg

ociacao));

this._mensagem.texto='Negociaçõesimportadascomsucesso';

})

282 13.6RESOLVENDOPROMISESPARALELAMENTE

Page 306: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

.catch(err=>this._mensagem.texto=err);}

//códigoposterioromitido

Maisumcódigomelhorado,maseleaindafereumprincípio.Alógica de buscar as negociações doperíodonãodeveria estar emNegociacaoController,masemNegociacaoService.VamoscriarnestaclasseométodoobtemNegociacoesDoPeriodo():

//client/app/controllers/NegociacaoController.js//códigoanterioromitido

obtemNegociacoesDoPeriodo(){

//ACESSAAOSPRÓPRIOSMÉTODOSATRAVÉSDETHIS

returnPromise.all([this.obtemNegociacoesDaSemana(),this.obtemNegociacoesDaSemanaAnterior(),this.obtemNegociacoesDaSemanaRetrasada()

]).then(periodo=>{

//NÃOFAZMAISOFOREACHreturnperiodo

.reduce((novoArray,item)=>novoArray.concat(item),[]);

}).catch(err=>{

console.log(err);thrownewError('Nãofoipossívelobterasnegociaçõesdo

período')});

}

Por fim, em NegociacaoController , vamos chamar ométodoqueacabamosdecriar:

//client/app/controllers/NegociacaoController.js//códigoanterioromitido

13.6RESOLVENDOPROMISESPARALELAMENTE 283

Page 307: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

importaNegociacoes(){

this._service.obtemNegociacoesDoPeriodo().then(negociacoes=>{

negociacoes.forEach(negociacao=>this._negociacoes.adiciona(negociacao));

this._mensagem.texto='Negociaçõesdoperíodoimportadascomsucesso';

}).catch(err=>this._mensagem.texto=err);

}

//códigoposterioromitido

Excelente.VimosquePromise.all() recebeumarraycomtodasasPromisesquedesejamosresolverparalelamente.Aposiçãodas Promises no array é importante, pois ao serem resolvidas,teremos acesso a um array com o resultado de cada uma delas,atravésdethen,seguindoamesmaordemderesoluçãodoarray.

AsAPIsque acessamos são "boazinhas", cadauma retornaasdatas emordemdecrescente.Contudo, não podemos confiar emumaAPIquenão foi criadapornós, razãopela qual precisamosgarantiremnossocódigoaordenaçãoquedesejamos.

O método obtemNegociacoesDoPeriodo deNegociacaoServicedevolveumaPromiseque,aoserresolvida,devolve um array com um período de negociações. Podemosordenar esse array damaneira que desejarmos através da funçãosort(),presenteemtodoarray.

13.7ORDENANDOOPERÍODO

284 13.7ORDENANDOOPERÍODO

Page 308: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Afunçãosort() recebeuma estratégiade comparaçãopormeio de uma função. Esta função é chamada diversas vezes,comparando cada elemento com o próximo. A lógica decomparação deve retornar 0 se os elementos são iguais; 1 ousuperior, seoprimeiroelementoémaiorqueosegundo;e -1ouinferiorseoprimeiroelementoémenordoqueosegundo.

Alterandoométodo:

//client/app/domain/negociacao/NegociacaoService.js//códigoanterioromitido

obtemNegociacoesDoPeriodo(){

returnPromise.all([this.obtemNegociacoesDaSemana(),this.obtemNegociacoesDaSemanaAnterior(),this.obtemNegociacoesDaSemanaRetrasada()

]).then(periodo=>{

returnperiodo.reduce((novoArray,item)=>novoArray.concat(item),

[]).sort((a,b)=>a.data.getTime()-b.data.getTime());

}).catch(err=>{

console.log(err);thrownewError('Nãofoipossívelobterasnegociaçõesdo

período')});

}

//códigoposterioromitido

Oresultadodeb.data.getTime() retornaumnúmeroquerepresenta uma data, algo bem oportuno para nossa lógica, poispodemossubtrairoresultadodoprimeiroelementopelosegundo.Seforemiguais,oresultadoserásempre0;seoprimeiroformaior

13.7ORDENANDOOPERÍODO 285

Page 309: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

queosegundo, teremosumnúmeropositivo;e seoprimeiro formenorqueosegundo, teremosumnúmeronegativo.Atendemosplenamenteaexigênciadométodosort().

No entanto, queremos uma ordem descendente, por issoprecisamossubtrairb.data.getTime()dea.data.getTime():

//client/app/domain/negociacao/NegociacaoService.js//códigoanterioromitido

obtemNegociacoesDoPeriodo(){

returnPromise.all([this.obtemNegociacoesDaSemana(),this.obtemNegociacoesDaSemanaAnterior(),this.obtemNegociacoesDaSemanaRetrasada()

]).then(periodo=>{

returnperiodo.reduce((novoArray,item)=>novoArray.concat(item),

[]).sort((a,b)=>b.data.getTime()-a.data.getTime());

}).catch(err=>{

console.log(err);thrownewError('Nãofoipossívelobterasnegociaçõesdo

período')});

}

//códigoposterioromitido

Agora temos a garantia de que o período importado semprenosretornaráumalistaordenadacomordenaçãodescendentepeladata das negociações. Daí, vem aquela velha pergunta: será quepodemossimplificaraindamaisnossocódigo?

Vejamos o código que escrevemos por último, aquele que

286 13.7ORDENANDOOPERÍODO

Page 310: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

ordenaoperíodo:

//ANALISANDOOCÓDIGO.then(periodo=>{

returnperiodo.reduce((novoArray,item)=>novoArray.concat(item),[]).sort((a,b)=>b.data.getTime()-a.data.getTime());

})

Perceba que temos uma única instrução dentro do bloco dothen que retorna o novo array ordenado. Podemos remover o

blocodaarrowfunction,inclusiveainstruçãoreturn,poisarrowfunctions possuem uma única instrução sem bloco, mas jáassumemumreturnimplícito.

//ANALISANDOOCÓDIGO.then(periodo=>periodo.reduce((novoArray,item)=>novoArray.concat(item),[]).sort((a,b)=>b.data.getTime()-a.data.getTime()))

Aliás, se olharmos os outros métodos da classeNegociacaoService,todospossuemapenasumaúnicainstruçãonoblocodothen().

Com todas as simplificações, nossa classeNegociacaoServicenofinalestaráassim:

//client/app/domain/negociacao/NegociacaoService.js

classNegociacaoService{

constructor(){

this._http=newHttpService();}

obtemNegociacoesDaSemana(){

13.7ORDENANDOOPERÍODO 287

Page 311: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

returnthis._http.get('negociacoes/semana').then(

dados=>dados.map(objeto=>newNegociacao(newDate(objeto.data),obj

eto.quantidade,objeto.valor)),err=>{

thrownewError('Nãofoipossívelobterasnegociaçõesdasemana');

});

}

obtemNegociacoesDaSemanaAnterior(){

returnthis._http.get('negociacoes/anterior').then(

dados=>dados.map(objeto=>newNegociacao(newDate(objeto.data),objeto.

quantidade,objeto.valor)),err=>{

thrownewError('Nãofoipossívelobterasnegociaçõesdasemanaanterior');

});

}

obtemNegociacoesDaSemanaRetrasada(){

returnthis._http.get('negociacoes/retrasada').then(

dados=>dados.map(objeto=>newNegociacao(newDate(objeto.data),objeto.

quantidade,objeto.valor)),err=>{thrownewError('Nãofoipossívelobterasne

gociaçõesdasemanaretrasada');

288 13.7ORDENANDOOPERÍODO

Page 312: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

});

}

obtemNegociacoesDoPeriodo(){

returnPromise.all([this.obtemNegociacoesDaSemana(),this.obtemNegociacoesDaSemanaAnterior(),this.obtemNegociacoesDaSemanaRetrasada()

]).then(periodo=>periodo

.reduce((novoArray,item)=>novoArray.concat(item),[])

.sort((a,b)=>b.data.getTime()-a.data.getTime())).catch(err=>{

console.log(err);thrownewError('Nãofoipossívelobterasnegociaçõe

sdoperíodo')});

}}

Excelente!Todavia,sevocêssãobonsobservadores,devemterreparado que podemos importar várias vezes as mesmasnegociações.Que tal evitarmos que isso aconteça?A ideia é nãoimportarnovamentenegociaçõesdeumamesmodia,mêseano.

Precisamos adotar algum critério para que possamosdeterminarseanegociaçãoqueestamosimportandojáhaviasidoimportada ou não. Em outras palavras, precisamos de algumalógicaque indique seumanegociaçãoé igualaoutra.Seráqueooperador== pode nos ajudar?Vamos realizar alguns testes no

13.8 IMPEDINDO IMPORTAÇÕESDUPLICADAS

13.8IMPEDINDOIMPORTAÇÕESDUPLICADAS 289

Page 313: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Consoledonavegador.

n1=newNegociacao(newDate(),1,100);n2=newNegociacao(newDate(),1,100);n1==n2//oretornoéfalse!

Não podemos simplesmente usar o operador == paracomparar objetos que não sejam dos tipos literais string ,number,boolean,null eundefined, pois ele testa se as

variáveisapontamparaomesmoendereçonamemória.

Sequisermosquen1sejaigualan2,podemosfazerissonoconsole:

n1=n2n1==n2//oretornoétrue

No exemplo anterior, a variáveln1 passa a apontar para omesmoobjeton2.Comon1en2agorareferenciamomesmoobjeto, o operador == retornará true na comparação entreambos.

Que tal realizarmos uma comparação entre as datas dasnegociações? Cairíamos no mesmo problema, pois estaríamoscomparandodoisobjetosdotipoDate,eooperador==apenastestaria se as propriedades do objeto apontam para o mesmoobjetonamemória.Hásaída?Claroquehá.

Vamosrealizaracomparaçãoentredatasdessamaneira:

n1.data.getDate()==n2.data.getDate()&&n1.data.getMonth()==n2.data.getMonth()&&n1.data.getFullYear()==n2.data.getFullYear()//resultadoétrue!

Os métodos Date que usamos devolvem números e, porpadrão, o operador== comeste tipo literal comparao valor, e

290 13.8IMPEDINDOIMPORTAÇÕESDUPLICADAS

Page 314: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

não a referência na memória. Excelente, conseguimos saber seduasnegociaçõessãoiguais.Será?

Precisamosmelhorarocritériodecomparação,poispodemosternegociaçõesdeumamesmadataemnossalistadenegociações.Que tal considerarmos tambémna comparaçãoaquantidade eovalor?

Testandonoconsoledonavegador:

n1.data.getDate()==n2.data.getDate()&&n1.data.getMonth()==n2.data.getMonth()&&n1.data.getFullYear()==n2.data.getFullYear()&&n1.quantidade==n2.quantidade&&n1.valor==n2.valor//retornatrue!

Não é nada orientado a objetos termos de repetir essa lógicaem todos os lugares em que precisamos testar a igualdade entrenegociações, uma vez que ela faz parte do domínio de umanegociação. Vamos alterar a classe Negociacao e adicionar ométodoequals()queestarápresenteemtodasasinstânciasdaclasse:

//client/app/domain/negociacao/Negociacao.js

classNegociacao{

//códigoanterioromitido

equals(negociacao){

returnthis.data.getDate()==negociacao.data.getDate()&&this.data.getMonth()==negociacao.data.getMonth()&&this.data.getFullYear()==negociacao.data.get

FullYear()&&this.quantidade==negociacao.quantidade

&&this.valor==negociacao.valor;}

}

13.8IMPEDINDOIMPORTAÇÕESDUPLICADAS 291

Page 315: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Ométodo compara a própria instância que está chamandoométodoequals()comoutranegociaçãorecebidanométodo.

Recarregando nossa página e testando nosso método noconsoledonavegador,teremos:

n1=newNegociacao(newDate(),1,100);n2=newNegociacao(newDate(),1,100);n1.equals(n2);//retornatrue

Perfeito,maspodemosenxugarbastanteométodoequals()queacabamosdecriar.

Quandoo critériode comparação entreobjetos ébaseadonovalor de todas as propriedades que possui, podemos usar oseguintetruquecomJSON.stringify():

//client/app/domain/negociacao/Negociacao.js

classNegociacao{

//códigoanterioromitido

equals(negociacao){

returnJSON.stringify(this)==JSON.stringify(negociacao);

}}

A função JSON.stringify() converte um objeto em umarepresentaçãotextual,ouseja,umastring.Convertendososobjetosenvolvidos na comparação para string, podemos usartranquilamenteooperador==paratestaraigualdadeentreeles.Bem mais enxuto, não? Porém, essa solução só funciona se ocritériodecomparaçãoforosvaloresdetodasaspropriedadesqueo objetopossui.

292 13.8IMPEDINDOIMPORTAÇÕESDUPLICADAS

Page 316: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Agora, precisamos alterar a lógica do métodoimportaNegociacoes de NegociacaoController para que

importeapenasnovasnegociações.

Precisamosfiltraralistadenegociaçõesretornadasdoservidor,considerando apenas aquelas que ainda não constam no arrayretornadoporthis._negociacoes.paraArray().Aboanotíciaé que arrays já possuem uma função com essa finalidade, afilter().

Vejamosisoladamentenoconsolecomoelafunciona:

lista=['A','B','C','D'];listaFiltrada=lista.filter(letra=>letra=='B'||letra=='C);

Afunçãofilter()recebeumafunçãocomaestratégiaqueserá aplicada em cada elemento da lista. Se a função retornartrue para um elemento, ele será adicionado na nova lista

retornadaporfilter(). Se retornarfalse, o elemento seráignorado.

No exemplo anterior, listaFiltrada conterá apenas asletras"B"e"C".

//client/app/controllers/NegociacaoController.js//códigoanterioromitido

importaNegociacoes(){

this._service.obtemNegociacoesDoPeriodo().then(negociacoes=>{

13.9ASFUNÇÕESFILTER()ESOME()

13.9ASFUNÇÕESFILTER()ESOME() 293

Page 317: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

negociacoes.filter(novaNegociacao=>console.log('lógicadofilt

roaqui')).forEach(negociacao=>this._negociacoes.adiciona(neg

ociacao));

this._mensagem.texto='Negociaçõesdoperíodoimportadascomsucesso';

}).catch(err=>this._mensagem.texto=err);

}//códigoposterioromitido

Analisandoocódigo,vemosqueoencadeamentodachamadada função forEach() trabalhará com a lista filtrada. Porém,aindanãotemosqualquerlógicadefiltroaplicada.

Para cada item processado pelo nosso filtro, precisamosverificarseelejáexisteemthis_negociacoes.paraArray().Senãoexistir,oitemfarápartedanovalista.Podemosimplementaressa lógica de diversas formas,mas esse é omomento oportunoparaaprendemosautilizarafunçãosome().

Afunçãosome()iteraemcadaelementodeumalista.Assimque encontrar algum (some) elemento de acordo com algumalógicaqueretornetrue,pararáimediatamentedeiterarnoarrayretornandotrue. Se nenhum elemento atender ao critério decomparação,oarrayterásidopercorridoatéofimeoretornodesome()seráfalse.

Que tal combinarmos some() com filter() para nãoimportarmos negociações duplicadas? Primeiro, vejamos comonossocódigoficará,paraemseguidatecermoscomentáriossobreele:

//client/app/controllers/NegociacaoController.js

294 13.9ASFUNÇÕESFILTER()ESOME()

Page 318: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

//códigoanterioromitido

importaNegociacoes(){

this._service.obtemNegociacoesDoPeriodo().then(negociacoes=>{

negociacoes.filter(novaNegociacao=>

!this._negociacoes.paraArray().some(negociacaoExistente=>

novaNegociacao.equals(negociacaoExistente)))

.forEach(negociacao=>this._negociacoes.adiciona(negociacao));

this._mensagem.texto='Negociaçõesdoperíodoimportadascomsucesso';

}).catch(err=>this._mensagem.texto=err);

}}

//códigoposterioromitido

Alógicadafunçãofilter()dalistaquedesejamosimportaré o retorno da função some() aplicada na lista já existente.Lembre-se de que some() pode retornar true ou false,valores que satisfazem a funçãofilter(). No caso, queremosfiltrarnossa listadenegociaçõesa importarconsiderandoapenasasquenãofazempartedalistadenegociaçõesjáimportadas.

Através de some() aplicada na lista de negociações jáimportadas, verificamos se a negociação que desejamos importarnão existe na lista.Mas como isso, a função retornariafalse,então precisamos inverter esse valor para true , pois apenasretornandotrueafunçãofilter()consideraráoelementona

13.9ASFUNÇÕESFILTER()ESOME() 295

Page 319: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

nova lista.Vejaqueutilizamosa funçãoequals() que criamosemnossomodelodeNegociacao.

No final danossa cadeiade funções,forEach() adicionaráapenasasnegociaçõesqueaindanãoforamimportadas.

Tudomuito bonito,mas o que acontece se fecharmos nossonavegadordepoisde incluirmosduasoumaisnegociações?Comcerteza perderemos todo nosso trabalho, porque não hápersistência de dados em nossa aplicação. Aliás, este será nossopróximoassunto.

O código completo deste capítulo você encontra emhttps://github.com/flaviohenriquealmeida/cangaceiro-javascript/tree/master/13

296 13.9ASFUNÇÕESFILTER()ESOME()

Page 320: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Exausto,elenãoaguentouedesabou.Duranteseuesvaecimento,teve uma nova visão. Nela, Lampião tirava o chapéu, sentava aolado da fogueira e apontava para a direção oposta. Depois deacordar,foientãoqueelesedeucontadequeobutimdocaminhodocangaceiroéoprópriocaminho.Então,eledeuumúltimogoleemsuamoringaecomeçouatrilharocaminhoparalugarnenhume, aomesmo tempo, todo lugar, cada vezmais preparado para assurpresasdavida.

Parte3-Arevelação

Page 321: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

CAPÍTULO14

"Rir,antesdahora,engasga."-GrandeSertão:Veredas

Nossa aplicação é funcional. No entanto, se fecharmos eabrirmosnovamente o navegador, perdemos todos os dados quehavíamos cadastrado. Uma solução é enviarmos os dados daaplicação para umaAPI de algum servidor que os armazene emum banco de dados, para que mais tarde possamos reavê-losconsumindooutraAPI.

Contudo,nãousaremosessaabordagem.Gravaremososdadosemumbancodedadosquetodonavegadorpossui,oIndexedDB.

O IndexedDBéumbancodedados transacional especificadopela W3C (https://www.w3.org/), presente nos mais diversosnavegadores do mercado. Diferente de bancos relacionais que

AALGIBEIRAESTÁFURADA!

14.1 INDEXEDDB, O BANCO DE DADOS DONAVEGADOR

298 14AALGIBEIRAESTÁFURADA!

Page 322: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

utilizam SQL, IndexedDB não se pauta em esquemas (estruturaspré-definidas de tabelas, colunas e seus tipos), mas em objetosJavaScript que podem ser persistidos e buscados com menosburocracia. Este tipo de banco faz muito pouco para validar osdados persistidos, a validação é de responsabilidade da aplicaçãoqueoutiliza.

Nãopodemossair integrandooIndexedDBemnossoprojetoantesdesabermoscomoelefunciona.Éporissoquecriaremosoarquivo db.html, para que possamos praticar primeiro, e sódepoisointegraremosemnossaaplicação.Vamoscriaroarquivoclient/db htmlcomaseguinteestrutura

<!--client/dn.html-->

<!DOCTYPEhtml><html><head>

<metacharset="UTF-8"><title>AprendendoIndexedDB</title></head>

<body><script>

/*nossostestesentraramaqui!*/

</script></body></html>

OprimeiropassoérequisitarmosumaaberturadeconexãoaoIndexedDB.ÉpeloobjetoglobalmentedisponívelindexedDBeseumétodoopen()querealizamosessepasso:

<!DOCTYPEhtml><html><head><metacharset="UTF-8">

14.1INDEXEDDB,OBANCODEDADOSDONAVEGADOR 299

Page 323: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

</head><body><script>

constopenRequest=indexedDB.open("jscangaceiro",1);

</script></body>

Ométodoopen() recebe dois parâmetros. O primeiro é onomedobancoemquedesejamosnosconectar,eosegundo,umnúmerodefinidopornósqueindicaaversãodobanco-nocaso,ovalor1 foi escolhido.Por enquanto, esse segundoparâmetronãoterá grande importância para nós, por isso o deixaremostemporariamentedeladonasexplicações.

O retorno do método open() é uma instância deIDBOpenDBRequest, ou seja, uma requisição de abertura dobanco. Toda vez que realizamos uma requisição de abertura,precisaremoslidarcomumatríadedeeventos,sãoeles:

onupgradeneeded

onsuccess

onerror

300 14.1INDEXEDDB,OBANCODEDADOSDONAVEGADOR

Page 324: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

ATENÇÃO

Só recarregue a página no navegador quando esseprocedimentoforaqui indicado.Casoorecarregamentosejafeito antes de pontos-chaves do nosso código, você teráproblemas de atualização de banco que só serão resolvidosmaistarde.

<!--client/dn.html--><! códigoanterioromitido

<body><script>

constopenRequest=indexedDB.open('jscangaceiro',1);

//lidandocomatríadedeeventos!

openRequest.onupgradeneeded=function(e){

console.log('Criaoualteraumbancojáexistente');};

openRequest.onsuccess=function(e){

console.log('Conexãoobtidacomsucesso!');};

openRequest.onerror=function(e){

console.log(e.target.error);};

</script></body>

<!--códigoposterioromitido-->

14.1INDEXEDDB,OBANCODEDADOSDONAVEGADOR 301

Page 325: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Atribuímos a openRequest.onupgradeneeded uma funçãoque será chamada somente se o banco que estivermos nosconectando não existir, ou quando estivermos atualizando umbanco já existente, assunto que abordaremos em breve. Emseguida, em openRequest.onsuccess(), atribuímos a funçãoque será chamada quando uma conexão for obtida com sucesso.Porfim,emopenRequest.onerror,atribuímosafunçãoqueseráchamada caso algum erro ocorra no processo de abertura daconexão.Seráatravésdee.target.errorque teremosacessoàcausadoerro.

Antesdecontinuarmos quetalusarmosarrowfunctionsparatermos um código menos verboso? Não há problema nenhumutilizarestetipodefunçãoparaessestrêseventos:

<!--client/dn.html--><!--códigoanterioromitido-->

<body><script>

constopenRequest=indexedDB.open('jscangaceiro',1);

openRequest.onupgradeneeded=e=>{

console.log('Criaoualteraumbancojáexistente');};

openRequest.onsuccess=e=>{

console.log('Conexãoobtidacomsucesso!');};

openRequest.onerror=e=>{

console.log(e.target.error);};

</script>

302 14.1INDEXEDDB,OBANCODEDADOSDONAVEGADOR

Page 326: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

</body>

<!--códigoposterioromitido-->

Recarregue sua página no navegador para que possamosverificar no console se as mensagens de onupgradeneeed eonsuccess são exibidas. Lembre-se de que a primeira será

exibida porque estamos criando o banco pela primeira vez e asegunda,porqueteremosobtidoumaconexão.

Naprimeiravez,veremosasseguintesmensagens:

Figura14.1:Duasmensagensnoconsole

Serecarregarmosmaisumavezapágina,veremosapenasumadasmensagens.Fazsentido,poisobancojáfoicriado:

Figura14.2:Umamensagemnoconsole

14.1INDEXEDDB,OBANCODEDADOSDONAVEGADOR 303

Page 327: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Pormaisqueasmensagensanteriores indiquemqueobancofoicriado,umcangaceirosóacreditarávendo.Podemosvisualizaro banco criado no Chrome através de seu console, na abaApplication. No canto esquerdo, na seção Storage, temosIndexedDB.Clicandonesteitem,eleexpandiráexibindoobancojscangaceiro:

Figura14.3:VisualizandoobanconoChrome

Agoraquejáentendemosopapeldatríadedeeventos,chegouahoradeinteragirmoscomobancoqueacabamosdecriar.

Conexõessãoobtidasatravésdoeventoonsuccess.Oúnicoparâmetrorecebidopelafunçãoassociadaaoeventonosdáacessoa uma instância de IDBDatabase, aquela que representa nossa

14.2ACONEXÃOCOMOBANCO

304 14.2ACONEXÃOCOMOBANCO

Page 328: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

conexão.Acessandoaconexãoatravésdee.target.result:

<!--client/dn.html--><!--códigoanterioromitido-->

<body><script>

constopenRequest=indexedDB.open('jscangaceiro',1);

openRequest.onupgradeneeded=e=>{

console.log('Criandoouatualizandoobanco');};

openRequest.onsuccess=e=>{

console.log('Conexãorealizadacomsucesso');

//e.target.resultéumainstânciadeIDBDatabase

e.target.result;};

openRequest.onerror=e=>{

console.log(e.target.error);};

</script></body><!--códigoposterioromitido-->

Já sabemos onde se encontra a conexão, mas precisamosarmazená-la em alguma variável para podermos utilizá-la emdiferentes ocasiões em nosso programa. Para isso, vamos criar avariávelconnection antesdo códigoque já temos.Estandonoescopoglobal,elapodeguardaroresultadodee.target.result:

<!--client/dn.html--><!--códigoanterioromitido-->

14.2ACONEXÃOCOMOBANCO 305

Page 329: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

<body><script>

//PRECISASERLETPARAACEITARUMANOVAATRIBUIÇÃOletconnection=null;

constopenRequest=indexedDB.open('jscangaceiro',1);

openRequest.onupgradeneeded=e=>{

console.log('Criandoouatualizandoobanco');};

openRequest.onsuccess=e=>{

console.log('Conexãorealizadacomsucesso');

//avariávelguardaumareferênciaparaaconexão

connection=e.target.result;};

openRequest.onerror=e=>{

console.log(e.target.error);};

</script></body><!--códigoposterioromitido-->

Excelente,temosumaconexão.Porém,antesdepensarmoseminteragir comobanco, precisamos criarumanovaObject Store.EntendaumastorecomoalgoanálogoàstabelasdomundoSQL.

Criamos novas stores através de uma conexão com o bancoduranteoeventoonupgradeneeded.Porém,quandoesteeventoédisparado,ovalordavariávelconnectionaindaénull,pois

14.3NOSSAPRIMEIRASTORE

306 14.3NOSSAPRIMEIRASTORE

Page 330: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

elasóreceberáseuvalorduranteoeventoonsuccess.Eagora?

A boa notícia é que podemos obter uma conexão no eventoonupgradeneeded da mesma maneira que obtemos em

onsuccess:

<!--client/dn.html--><!--códigoanterioromitido-->

<body><script>

letconnection=null;

constopenRequest=indexedDB.open('jscangaceiro',1);

openRequest.onupgradeneeded=e=>{

console.log('Criaoualteraumbancojáexistente');

//obtendoaconexão!connection=e.target.result;};

openRequest.onsuccess=e=>{

console.log('Conexãorealizadacomsucesso');connection=e.target.result;};

openRequest.onerror=e=>{

console.log(e.target.error);};

</script></body><!--códigoposterioromitido-->

Excelente! Da maneira que estruturamos nosso código, aconexão pode vir tanto de onupgradeneeded quanto deonsuccess. Já temos amoringa e a cartucheira emmãos para

14.3NOSSAPRIMEIRASTORE 307

Page 331: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

criarmosnossostore.

Usaremos a seguinte lógica de criação da store. Primeiro,verificaremos se a store que estamos tentando criar já existe; seexistir,vamosapagá-laantesdecriá-la.Arazãodessaabordageméque o evento onupgradeneed também pode ser disparadoquandoestivermosatualizandonossobanco.

Para sabermos se uma store existe ou não durante umaatualização, usamos o métodoconnection.objectStoreNames.contains() passandoonome

dastore.Paraapagarmosumastoreecriarmosumanova,usamosconnection.deleteObjectStore() e

connection.createObjectStore(),respectivamente:

<!--client/dn.html--><!--códigoanterioromitido--><body>

<script>

letconnection=null;

constopenRequest=indexedDB.open('jscangaceiro',1);

openRequest.onupgradeneeded=e=>{

console.log('Criaoualteraumbancojáexistente');connection=e.target.result;

//podeserqueoeventoestejasendodisparadoduranteumaatualização,

//nessecaso,verificamosseastoreexiste,seexistir

//apagamosastoreatualantesdecriarmosumanova

if(connection.objectStoreNames.contains('negociacoes')){

connection.deleteObjectStore('negociacoes');}

308 14.3NOSSAPRIMEIRASTORE

Page 332: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

connection.createObjectStore('negociacoes',{autoIncrement:true});

};

//códigoposterioromitido

</script></body><!--códigoposterioromitido-->

Um ponto a destacar é o segundo parâmetro deconnection.createObjectStore().Nele, passamosumobjeto

JavaScript com a propriedade autoIncrement: true . Ela éimportante,poiscriaráinternamenteumidentificadorúnicoparaosobjetosquesalvarmosnastore.

Ainda sobre a lógica de atualização, ela poderia ser maisrebuscada,masapagarastoreantigaecriarumanovaésuficienteparanossaaplicação.Contudo,enfrentaremosumproblema.

Recarregandonossapágina, apenaso eventoonsuccess échamado. O evento onupgradeneeded só é chamado quandoestamos criandoumnovo banco ou atualizandoum já existente.Como já temos um banco criado, precisamos indicar para oIndexedDB que desejamos realizar uma atualização. Comoresolver?

Para que o evento onupgradeneeded seja disparadonovamente, a versão do banco que estamos criando deve sersuperioràversãodobancoexistentenonavegador.Sendoassim,basta passarmos como segundo parâmetro para a funçãoindexedDB.open()qualquervalorquetemoscertezadequeseja

14.4ATUALIZAÇÃODOBANCO

14.4ATUALIZAÇÃODOBANCO 309

Page 333: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

superioràversãodobancopresentenonavegador.Emnossocaso,o valor2ésuficiente:

<!--client/db.html--><!--códigoanterioromitido-->

<body><script>letconnection=null;

//INCREMENTAMOSONÚMERODAVERSÃOconstopenRequest=indexedDB.open('jscangaceiro',2);

//códigoposterioromitido</script></body<!--codigoposterioromitido-->

Desta forma, recarregando a página, o eventoonupgradeneeded será disparado e criará nossa storage

negociacoes.

Atenção, feche e abra o navegador antes de realizar o novoteste.AabaApplicationfazumcachedasúltimasinformaçõesqueexibeeaversãodobancocontinuará1,mesmocomanossamudança.Fechareabrirobrowser realizaumrefreshnestaárea,exibindocorretamenteanovaversãodonossobanco.Éalgoquepodeconfundiroleitor.

Por fim, ao acessarmos a abaApplication, veremos que aversãodobancomudou e que aObject Storenegociacoes foicriada,masaindaestávazia.

310 14.4ATUALIZAÇÃODOBANCO

Page 334: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Figura14.4:Objectstorenegociacoes

Temosnossa storeprontinhaparapersistirdados, assuntodapróximaseção.

Chegou ao momento de persistirmos algumas negociações.Paraisso,precisamosimportaroscriptNegociacao.jsparaquepossamoscriarinstânciasdessaclasse:

<!--client/db.html--><!DOCTYPEhtml><html><head>

<metacharset="UTF-8"><title>AprendendoIndexedDB</title></head><body>

<!--códigoanterioromitido--><scriptsrc="app/domain/negociacao/Negociacao.js"></script></body>

14.5TRANSAÇÕESEPERSISTÊNCIA

14.5TRANSAÇÕESEPERSISTÊNCIA 311

Page 335: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

</html>

Agora,vamoscriarafunçãoadiciona()quemaistardeseráchamadapornósparaadicionarmosnegociaçõesnobanco:

<!--client/db.html--><!DOCTYPEhtml><html><head>

<metacharset="UTF-8"><title>AprendendoIndexedDB</title></head>

<body><script>letconnection=null;

constopenRequest=indexedDB.open('jscangaceiro',2);

openRequest.onupgradeneeded=e=>{//códigoomitido

};

openRequest.onsuccess=e=>{//códigoomitido};

openRequest.onerror=e=>{//códigoomitido};

//novafunçãoparaadicionarnegociaçõesfunctionadiciona(){

}</script><scriptsrc="js/app/domain/negociacao/Negociacao.js"></script>

</body></html>

Quandoafunçãoforchamada,elaterádesercapazdegravarumainstânciadenegociaçãonoIndexedDB.

312 14.5TRANSAÇÕESEPERSISTÊNCIA

Page 336: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Oprimeiropassonoprocessodepersistênciaéobtermosumatransação de escrita para, em seguida, através dessa transação,termosacessoàstorenaqualdesejamospersistirobjetos.Primeiro,vejamosocódigo:

//client/dn.html//códigoanterioromitido

functionadiciona(){

//NOVAINSTÂNCIADENEGOCIACAOconstnegociacao=newNegociacao(newDate(),200,1);

consttransaction=connection.transaction(['negociacoes'],'readwrite');conststore=transaction.objectStore('negociacoes');}

//códigoposterioromitido

O método connection.transaction() recebe comoprimeiroparâmetroumarraycomonomedastorecujatransaçãoqueremoscriare,comosegundoparâmetro,oseutipo.Passamosreadwrite para uma transação que permita escrita. Porém, se

quiséssemossomenteleitura,bastariatrocarmosparareadonly.

Por intermédio da store transacional, podemos realizaroperaçõesdepersistência,comoinclusão,alteraçãoeremoção.

//client/dn.html//códigoanterioromitido

functionadiciona(){

constnegociacao=newNegociacao(newDate(),200,1);

consttransaction=connection.transaction(['negociacoes'],'readwrite');

conststore=transaction.objectStore('negociacoes');

14.5TRANSAÇÕESEPERSISTÊNCIA 313

Page 337: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

//ATRAVÉSDASTORE,REQUISITAMOSUMAINCLUSÃOconstrequest=store.add(negociacao);

}

//códigoposterioromitido

Após criarmos uma instância de Negociacao, utilizamos ométodostore.add()parapersisti-la.Noentanto,essaoperaçãoé apenas uma requisição de inclusão. Precisamos saber se ela foibem-sucedida ou não através das funções de callback quepassaremosparaoseventosonsucceseonerrordarequisição:

//client/dn.html//códigoanterioromitido

functionadiciona(){

constnegociacao=newNegociacao(newDate(),200,1);

consttransaction=connection.transaction(['negociacoes'],'readwrite');

conststore=transaction.objectStore('negociacoes');constrequest=store.add(negociacao);

request.onsuccess=e=>{

console.log('negociaçãosalvacomsucesso');};

request.onerror=e=>{

console.log('Nãofoipossívelsavaranegociação')};

}

//códigoposterioromitido

Agora sónos resta chamarmosométodoadiciona() peloconsoledoChrome:

314 14.5TRANSAÇÕESEPERSISTÊNCIA

Page 338: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Figura14.5:Chamandoadiciona()peloconsole

Comaexecuçãodométodo,temosaseguintesaída:

Figura14.6:Resultadodeadiciona()noconsole

Excelente, agora vamos verificar na aba Application osdadosdastorenegociacoes,clicandodiretamentenastore:

Figura14.7:Dadosdastore

Podemos simplificar ainda mais nosso código evitandodeclarar variáveis intermediárias, encadeando as chamadas defunção e removendo o bloco de algumas arrow functions.Nossafunçãoadiciona()ficaráassim:

//client/db.html

14.5TRANSAÇÕESEPERSISTÊNCIA 315

Page 339: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

//códigoanterioromitido

functionadiciona(){

constnegociacao=newNegociacao(newDate(),200,1);

//ENCADEOUASCHAMADASconstrequest=connection.transaction(['negociacoes'],'readwrite').objectStore('negociacoes').add(negociacao);

//REMOVEUBLOCOrequest.onsuccess=e=>console.log('negociaçãosalvacomsucesso');

//REMOVEUBLOCOrequest.onerror=e=>console.log('Nãofoipossívelsavaranegociação');

}

//códigoposterioromitido

Maisenxuto!

Conseguimos gravar a nossa primeira negociação! Maisadiante,seremoscapazesdelistarosdados.

Somos capazes de persistir negociações, mas faz parte dequalqueraplicaçãolistá-las.Vamoscriarimediatamenteabaixodafunçãoadiciona()afunçãolistaTodos():

<!--client/db.html--><!--códigoposterioromitido-->

<body><script>//códigoanterioromitido

14.6CURSORES

316 14.6CURSORES

Page 340: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

functionadiciona(){

//codigoomitido}

functionlistaTodos(){

}

</script><scriptsrc="app/domain/negociacao/Negociacao.js"></script></body>

<!--códigoposterioromitido-->

O processo é bem parecido com a inclusão de negociações.Precisamos de uma transação, mas em vez de chamarmos ométodo add() , em seu lugar chamaremos o métodoopenCursor(),quenosretornaráumareferênciaparaumobjetoquesabeiterarporcadaobjetogravadonastore:

//client/db.html//códigoanterioromitido

functionlistaTodos(){

constcursor=connection.transaction(['negociacoes'],'readwrite').objectStore('negociacoes').openCursor();

cursor.onsuccess=e=>{

};

cursor.onerror=e=>{

//error.name,paraficarmaissucintaainformação

console.log('Error:'+e.target.error.name);};

}

14.6CURSORES 317

Page 341: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

//códigoposterioromitido

O evento onsuccess do nosso cursor será chamado naquantidade de vezes correspondente ao número de negociaçõesarmazenadasemnossaobjectstore.Naprimeirachamada,teremosacesso a um objeto ponteiro que sabe acessar a primeiranegociação da store. Adicionaremos a negociação no arraynegociacoesquereceberátodasasnegociaçõesqueiterarmos.

Assimqueadicionarmosaprimeiranegociação,solicitamosaoobjetoponteiroqueváparaapróxima.Esseprocessodisparaumanovachamadaàfunçãoatribuídaaonsuccess.Tudoserepetiráaté que tenhamos percorrido todos os objetos da store.No final,teremosnossoarraycomtodasasnegociaçõeslidas.

Nossocódigoficaráassim:

//client/db.html//códigoanterioromitido

functionlistaTodos(){

constnegociacoes=[];

constcursor=connection.transaction(['negociacoes'],'readwrite').objectStore('negociacoes').openCursor();

cursor.onsuccess=e=>{

//objetoponteiroparaumanegociaçãoconstatual=e.target.result;

//sefordiferentedenull,éporqueaindahádado

if(atual){

//atual.valueguardaosdadosdanegociação

318 14.6CURSORES

Page 342: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

negociacoes.push(atual.value);

//vaiparaapróximaposiçãochamandoonsuccessnovamente

atual.continue();

}else{

//quandoatualfornull,éporquenãohámaisdados

//imprimimosnoconsolealistadenegociaçõesconsole.log(negociacoes);

}};

}

Antesde testarmosnossométodo,que tal adicionarmosmaisduasnegociaçõesparatermospelomenostrêselementosemnossastore? Para isso, basta abrirmos o console do Chrome echamarmosduasvezesmaisafunçãoadiciona().

Por fim, podemos ver nossa listagem chamando o métodolistaTodos()tambématravésdoterminal.Oresultadoserá:

Figura14.8:Listandonegociações

14.6CURSORES 319

Page 343: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Será impresso no console um array com todas as nossasnegociações. Contudo, se escrutinarmos cada item do array,veremos que é apenas um objeto com as propriedades _data,_quantidade e _valor . Isso acontece porque, quando

gravamosumobjetoemumastore, apenas suaspropriedades sãosalvas.

Sendoassim, antesde adicionarmosnoarraydenegociações,precisamos criar umanova instância deNegociacao com basenosdados:

//client/db.html//códigoanterioromitido

cursor.onsuccess=e=>{

constatual=e.target.result;

if(atual){

//criaumanovainstânciaantesdeadicionarnoarrayconstnegociacao=newNegociacao(

atual.value._data,atual.value._quantidade,atual.value._valor);

negociacoes.push(negociacao);atual.continue();

}else{

console.log(negociacoes);}

};

cursor.onerror=e=>console.log(e.target.error.name);

//códigoposterioromitido

Perfeito.Seolharmosasaídanoconsole,vemosquetemosum

320 14.6CURSORES

Page 344: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

arraydeinstânciasdaclasseNegociacao.

Com tudo o que vimos, temos o pré-requisito para integrarnossaaplicaçãocomoIndexedDB.Entretanto,vocêdevetervistoquenossocódigonãoéláumdosmelhores.Nopróximocapítulo,veremos como organizá-lo ainda melhor, recorrendo a algunspadrõesdeprojetodomercado.patterns.

O código completo deste capítulo você encontra emhttps://github.com/flaviohenriquealmeida/cangaceiro-javascript/tree/master/14

14.6CURSORES 321

Page 345: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

CAPÍTULO15

"Amorteéparaosquemorrem."-GrandeSertão:Veredas

No capítulo anterior, aprendemos a utilizar o IndexedDB, epersistimos e buscamos negociações. No entanto, gastamosbastante linhasdecódigo lidandocomaaberturadaconexão.Semais tarde precisarmos damesma conexão emoutros pontos danossaaplicação,teremoscódigorepetidoedifícildemanter.

Umasoluçãoéisolaracomplexidadedecriaçãodaconexãoemumaclasse.Aliás,utilizamosopadrãodeprojetoFactory pararesolverumproblemaparecidocomacomplexidadedecriaçãodeProxies.ChegouahoradecriarmosnossaConnectionFactory.

Vamos criar o arquivoclient/app/util/ConnectionFactory.js com o método

estático getConnection() . Este método nos devolverá uma

COLOCANDOACASAEMORDEM

15.1ACLASSECONNECTIONFACTORY

322 15COLOCANDOACASAEMORDEM

Page 346: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Promisepelofatodeaaberturadeumaconexãocomobancoserrealizadademaneiraassíncrona.Porfim,lançaremosumaexceçãonoconstructor(),poissóqueremosqueoacessosejafeitopelométodoestático:

//client/app/util/ConnectionFactory.js

classConnectionFactory{

constructor(){

thrownewError('Nãoépossívelcriarinstânciasdessaclasse');

}

staticgetConnection(){

returnnewPromise((resolve,reject)=>{

});}

}

Antes de prosseguirmos, vamos importar nossa classeclient/index.html:

<!--client/index.html--><!--códigoanterioromitido-->

<scriptsrc="app/domain/negociacao/Negociacao.js"></script><scriptsrc="app/controllers/NegociacaoController.js"></script><scriptsrc="app/ui/converters/DateConverter.js"></script><scriptsrc="app/domain/negociacao/Negociacoes.js"></script><scriptsrc="app/ui/views/View.js"></script><scriptsrc="app/ui/views/NegociacoesView.js"></script><scriptsrc="app/ui/models/Mensagem.js"></script><scriptsrc="app/ui/views/MensagemView.js"></script><scriptsrc="app/util/ProxyFactory.js"></script><scriptsrc="app/util/Bind.js"></script><scriptsrc="app/util/ApplicationException.js"></script><scriptsrc="app/ui/converters/DataInvalidaException.js"></script>

15.1ACLASSECONNECTIONFACTORY 323

Page 347: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

<scriptsrc="app/domain/negociacao/NegociacaoService.js"></script>

<scriptsrc="app/util/HttpService.js"></script>

<!--importouaqui--><scriptsrc="app/util/ConnectionFactory.js"></script>

<scriptsrc="app/app.js"></script><!--códigoposterioromitido-->

Temos o esqueleto da nossa classe, mas ela precisa seguiralgumasregras.Sãoelas:

Teremosumaconexãoparaaaplicaçãointeira,sendoassim, não importa quantas vezes seja chamado ométodo getConnection() , a conexão retornadadeveseramesma.

Toda conexão possui o método close() , mas oprogramadornãopodechamá-lo,porqueaconexãoéa mesma para a aplicação inteira. SóConnectionFactorypodefecharaconexão.

Tendo essas restrições em mente, continuaremos aimplementaçãodonossométodo,preparandoumarespostaparaatríadedeeventosdeaberturadeumaconexão:

//client/app/util/ConnectionFactory.js

//variávelqueguardaaconexão

classConnectionFactory{

constructor(){

thrownewError('Nãoépossívelcriarinstânciasdessaclasse');

}

324 15.1ACLASSECONNECTIONFACTORY

Page 348: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

staticgetConnection(){

returnnewPromise((resolve,reject)=>{

constopenRequest=indexedDB.open('jscangaceiro',2);

openRequest.onupgradeneeded=e=>{

};

openRequest.onsuccess=e=>{

};

openRequest.onerror=e=>{

};

});}

}

Observequeutilizamosovalor2,porquefoiaúltimaversãodobancousada.

Hoje nossa aplicação possui apenas uma store chamadanegociacoes.Masnadanosimpededetrabalharmoscommais

deumastore.Paratornarnossocódigoflexível,vamosdeclararumarray logo acima da classe que por enquanto guarda a storenegociacoes.Toda vezqueprecisarmosdenovas stores, basta

adicionarmosseunomenoarray:

//arraycomumastoreapenasconststores=['negociacoes'];

classConnectionFactory{

constructor(){

thrownewError('Nãoépossívelcriarinstânciasdessaclasse');

15.1ACLASSECONNECTIONFACTORY 325

Page 349: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

}

staticgetConnection(){

returnnewPromise((resolve,reject)=>{

constopenRequest=indexedDB.open('jscangaceiro',2);

openRequest.onupgradeneeded=e=>{

//ITERANOARRAYPARACONSTRUIRASSTORESstores.forEach(store=>{

//lógicaquecriaasstores});

};

openRequest.onsuccess=e=>{

};

openRequest.onerror=e=>{

};

});}

}

A criação do array de stores foi necessária porque nossaclasse não permite que propriedades sejam declaradas segundonossa convenção. Vale lembrar que ConnectionFactory teráapenasummétodoestático.Agora,sónosrestaimplementarmosalógicadecriaçãodasstores.

Naseçãoanterior,jápreparamosoterrenoparaquepossamosescrever a lógica de criação das nossas stores. Mas em vez de

15.2CRIANDOSTORES

326 15.2CRIANDOSTORES

Page 350: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

escrevermos a lógica diretamente na função passada paraonupgradeneeded, vamoscriaroutrométodoestático comesta

finalidade,deixandoclaranossaintenção:

//client/app/util/ConnectionFactory.js

conststores=['negociacoes'];

classConnectionFactory{

constructor(){

thrownewError('Nãoépossívelcriarinstânciasdessaclasse');

}

staticgetConnection(){

returnnewPromise((resolve,reject)=>{

constopenRequest=indexedDB.open('jscangaceiro',2);

openRequest.onupgradeneeded=e=>{

//PASSAACONEXÃOPARAOMÉTODOConnectionFactory._createStores(e.target.result);};

openRequest.onsuccess=e=>{

};

openRequest.onerror=e=>{

};

});}

//CONVENÇÃODEMÉTODOPRIVADO//SÓFAZSENTIDOSERCHAMADOPELA//PRÓPRIACLASSE

15.2CRIANDOSTORES 327

Page 351: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

static_createStores(connection){

stores.forEach(store=>{

//ifsembloco,maissucinto!

if(connection.objectStoreNames.contains(store))connection.deleteObjectStore(store);

connection.createObjectStore(store,{autoIncrement:true});

});}

}

Terminamos a implementação do evento onupgradeneed ,agora implementaremos os eventos onsuccess e onerror .Neles, simplesmente passamos a conexão para resolve() e oerroparareject():

//client/app/util/ConnectionFactory.js//códigoanterioromitido

openRequest.onsuccess=e=>{//passaoresultado(conexão)paraapromise!resolve(e.target.result);

};

openRequest.onerror=e=>{

console.log(e.target.error)//passaoerropararejectdapromise!reject(e.target.error.name)};

//códigoposterioromitido

Já temos uma classe testável. Depois de recarregarmos nossapáginaindex.html,atravésdoconsole,vamosutilizarométodogetConnection().

//NOCONSOLE!

328 15.2CRIANDOSTORES

Page 352: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

ConnectionFactory.getConnection().then(connection=>console.log(connection));

Ainstruçãoexibiránoconsole:

Figura15.1:Conexãoimpressanoconsole

Está tudofuncionandocorretamente.Porém,acadachamadadométodo getConnection(), criaremos uma nova conexão eissonãosecoadunacomaregranaqualprecisamosterumaúnicaconexãoparaaaplicação.Veremoscomosolucionaristoaseguir.

Paragarantirmosquereceberemosamesmaconexãotodavezque o método getConnection() for chamado, criaremos avariávelconnectionantesdadeclaraçãodaclasse.Elacomeçarácomvalornull.Emseguida,logonoiníciodoblocodaPromise,verificamos se ela possui valor; se possuir, passamos a conexãopara resolve() , evitando assim repetir todo o processo deobtenção de uma conexão. Caso connection seja null ,significa que estamos obtendo a conexão pela primeira vez efaremosissoemonsuccess,guardando-anavariávelepassando-atambémpararesolve():

//client/app/util/ConnectionFactory.js

15.3GARANTINDOUMACONEXÃOAPENASPORAPLICAÇÃO

15.3GARANTINDOUMACONEXÃOAPENASPORAPLICAÇÃO 329

Page 353: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

conststores=['negociacoes'];

//COMEÇASEMCONEXÃOletconnection=null;

classConnectionFactory{

constructor(){

thrownewError('Nãoépossívelcriarinstânciasdessaclasse');

}

staticgetConnection(){

returnnewPromise((resolve,reject)=>{

//SEUMACONEXÃOJÁFOICRIADA,//JÁPASSAPARARESOLVEERETORNALOGO!

if(connection)returnresolve(connection);

constopenRequest=indexedDB.open('jscangaceiro',2);

openRequest.onupgradeneeded=e=>{

ConnectionFactory._createStores(e.target.result);};

openRequest.onsuccess=e=>{

//SÓSERÁEXECUTADONAPRIMEIRAVEZQUEACONEXÃOFORCRIADA

connection=e.target.result;resolve(e.target.result);

};

openRequest.onerror=e=>{

console.log(e.target.error)reject(e.target.error.name)

};

330 15.3GARANTINDOUMACONEXÃOAPENASPORAPLICAÇÃO

Page 354: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

});}

//códigoanterioromitido

}

Agora, no console, não importa a quantidade de vezes que ométodogetConnection()sejachamado,elesempreretornaráamesma conexão! Excelente, mas nossa solução nos criou outroproblema.

Com a página recarregada, vamos até o console pararealizarmos um teste. Vamos digitar os nomes das variáveisstores e connection , que foram declaradas em

ConnectionFactory:

Figura15.2:Acessandoasvariáveis

Somoscapazesdeacessarosvaloresdasvariáveisporqueelasforamdeclaradasnoescopoglobal.Alémdisso,nadaimpedequeoprogramador acidentalmente, emoutra parte da nossa aplicação,

15.3GARANTINDOUMACONEXÃOAPENASPORAPLICAÇÃO 331

Page 355: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

realizeatribuiçõesindevidasparaelas.Eagora?

Paraevitarquestoreseconnectionvazemparaoescopoglobal,podemosconfiná-las emummódulo.Variáveise funçõesdeclaradasemummódulosóserãoacessíveisdentrodomódulo,apenas as variáveis e funçõesqueoprogramador explicitar serãoacessíveispelorestantedaaplicação.Emoutraspalavras,podemosdizerqueummódulotemseupróprioescopo.

Podemos criar um escopo dentro de um arquivo scriptenvolvendo todo seu código dentro de uma function() .Variáveis(var,letouconst)efunçõesdeclaradasnoescopodeumafunçãosãoacessíveisapenaspeloblocodafunção.

Vamos envolver todo o código do scriptConnectionFactory.jsdentrodafunçãotmp():

//client/app/util/ConnectionFactory.js

functiontmp(){

conststores=['negociacoes'];letconnection=null;

classConnectionFactory{

constructor(){

thrownewError('Nãoépossívelcriarinstânciasdessaclasse');

}

staticgetConnection(){

15.4 O PADRÃO DE PROJETO MODULEPATTERN

332 15.4OPADRÃODEPROJETOMODULEPATTERN

Page 356: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

//códigoomitido}

static_createStores(connection){

//códigoomitido}

}

}

tmp();//chamaafunção!

Reparem que a última instrução é a chamada da funçãotmp()paraqueseublocosejaexecutado.

Depoisderecarregarmosonavegador,setentarmosacessarasvariáveisstoreseconnection,receberemos"...isnotdefined",pois elas não vivem mais no escopo global. Resolvemos umproblema,mascriamosoutro,pois tambémnão seremoscapazesde acessar ConnectionFactory.getConnection() . Classestambémsãoencapsuladasnoescopodefunções:

Figura15.3:ProblemaaoacessarConnectionFactory

15.4OPADRÃODEPROJETOMODULEPATTERN 333

Page 357: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Aideiaéminimizarmosousodoescopoglobal,maseleaindaéummalnecessário,poiséatravésdelequetornamosumaclasseacessívelparaoutra.

Se todo o código do script está dentro da função tmp() etemos interesse em tornar global a definição da classe, podemosfazer com que a função retorne a classe. Ao armazenarmos oretorno em outra variável declarada fora do escopo da funçãotmp(), ela serámais uma vez acessível globalmente pela nossa

aplicação:

//client/app/util/ConnectionFactory.s

functiontmp(){

conststores=['negociacoes'];letconnection=null;

//RETORNAADEFINIÇÃODACLASSEreturnclassConnectionFactory{

constructor(){

thrownewError('Nãoépossívelcriarinstânciasdessaclasse');

}

staticgetConnection(){

//códigoomitido}

static_createStores(connection){

//códigoomitido}

}

}

//AVARIÁVELVIVENOESCOPOGLOBAL

334 15.4OPADRÃODEPROJETOMODULEPATTERN

Page 358: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

//PORQUEFOIDECLARADAFORADAFUNÇÃOconstConnectionFactory=tmp();

Depoisderecarregarnossapágina,podemosrealizarumnovoteste. Conseguiremos acessar a classe ConnectionFactory ,mantendostoreseconnectioninacessíveis:

Figura15.4:Acessandoaclassepeloconsole

Muitobom,masnossocódigopodeficaraindamelhor.Comousamosafunçãotmp()paracriarnossomódulo,elaéacessívelpelo console, pois está no escopo global e nada nos impede dechamá-laquantasvezesquisermos.Outropontoéqueprecisamoslembrardechamá-lalogodepoisdesuadeclaração;casocontrário,alógicadeseublocojamaisseráprocessada.

Podemos resolver esse problema através de uma IIFE(Immediately-invoked function expression), ou funções imediatasno português. O primeiro passo é tornar a função tmp()anônima,istoé,removendoseunome.Comonãotemosmaisumnome,precisaremosretirarainstruçãodachamadadetmp():

//client/app/util/ConnectionFactory.s

function(){

conststores=['negociacoes'];

15.4OPADRÃODEPROJETOMODULEPATTERN 335

Page 359: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

letconnection=null;

returnclassConnectionFactory{

//códigoomitido}

}

Dojeitoqueestá,ocódigonãofuncionará,poisnãopodemosdeclarar funções anônimas dessa forma. Contudo, podemosenvolverafunçãoentreparênteses:

//client/app/util/ConnectionFactory.s

(function(){

conststores=['negociacoes'];letconnection=null;

returnclassConnectionFactory{

//códigoomitido}

})

Esse passo evita erro de sintaxe, mas ainda não é capaz deinvocarnossafunção.Paraisso,bastaadicionarmosumachamada()logoapósosparênteses:

//client/app/util/ConnectionFactory.s

(function(){

conststores=['negociacoes'];letconnection=null;

returnclassConnectionFactory{

//códigoomitido}

336 15.4OPADRÃODEPROJETOMODULEPATTERN

Page 360: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

})();

Assimquenossoscriptforcarregado,afunçãoanônimadentrodosparêntesesseráexecutada.Comonãotemosmaisumnomedefunção,ninguémpoderáchamá-laatravésdoterminal.Porfim,elaprecisaguardarseuretornoemumavariável:

constConnectionFactory=(function(){

conststores=['negociacoes'];letconnection=null;

returnclassConnectionFactory{

//códigoomitido}

})();

O conjunto das soluções que acabamos de implementarseguemopadrãodeprojetoModulePattern.Nocaso,criamosummóduloqueexportaaclasseConnectionFactory.Comofoiditonos capítulos anteriores, o ES2015 (ES6) trouxe uma soluçãonativa para a criação de módulos, mas ainda não é o momentocertodeabordá-la.

Porfim,podemosaindausarumaarrowfunctionnolugardefunction():

//client/app/util/ConnectionFactory.js

constConnectionFactory=(()=>{

conststores=['negociacoes'];letconnection=null;

returnclassConnectionFactory{

//códigoomitido

15.4OPADRÃODEPROJETOMODULEPATTERN 337

Page 361: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

}

})();

Agora que garantimos apenas uma conexão para a aplicaçãointeira, precisamos verificar a segunda regra que consiste nofechamento de uma conexão apenas pela classeConnectionFactory:

Umadasregrasqueprecisamosseguirénãopermitirmosqueodesenvolvedor feche a conexão através da conexão criada paraConnectionFactory, pois se ele fizer isso, estará fechando a

conexão da aplicação inteira. Nada impede o desenvolvedor dechamarométodoclose()daconexãoparafechá-la,atéporque,éum"caminhonatural"parafechá-la:

//EXEMPLODEFECHAMENTODECONEXÃOPELODESENVOLVEDOR

ConnectionFactory.getConnection().then(conn=>conn.close());

A única forma aceitável de fechamento é através da própriaConnectionFactory,porexemplo,pormeiodeummétodoqueaindacriaremos,chamadocloseConnection():

//EXEMPLODECOMODEVEMOSFECHARACONEXÃO,MÉTODONÃOEXISTEAINDAConnectionFactory.closeConnection();

Se todaconexãopossuiométodoclose(), comopodemosevitarqueoprogramadoro chame?UmasoluçãoéaplicarmosoMonkey Patch, que consiste na modificação de uma API jáexistente.Nocaso,vamosalterarométodoclose()originalda

15.5 MONKEY PATCH: GRANDES PODERESTRAZEMGRANDESRESPONSABILIDADES

338 15.5MONKEYPATCH:GRANDESPODERESTRAZEMGRANDESRESPONSABILIDADES

Page 362: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

conexãoparaquelanceumaexceçãoalertandoodesenvolvedordequenãopodefechá-lo:

//client/app/util/ConnectionFactory.js//códigoanterioromitido

openRequest.onsuccess=e=>{

connection=e.target.result;connection.close=()=>{

thrownewError('Vocênãopodefechardiretamenteaconexão');

};resolve(connection);

};

//códigoposterioromitido

A natureza dinâmica da linguagem JavaScript nos permitealterar funções em tempo de execução. No caso, estamosatribuindoumanovalógicaparaoclose()daconexão.Depoisde recarregarmos nossa página, vamos realizar um novo testefechandoaconexão:

Figura15.5:Conexãoimpressanoconsole

Agora, para concluirmos nossa classe, criaremos o métodoestático closeConnection() em ConnectionFactory . Estemétodoseráoencarregadodefecharaconexãoparanós:

//client/app/util/ConnectionFactory.js

15.5MONKEYPATCH:GRANDESPODERESTRAZEMGRANDESRESPONSABILIDADES 339

Page 363: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

constConnectionFactory=(()=>{

conststores=['negociacoes'];letconnection=null;

returnclassConnectionFactory{

staticgetConnection(){

//códigoomitido}

static_createStores(connection){

//códigoomitido}

//novométodo!

staticcloseConnection(){

if(connection){connection.close();

}}

}})();

Para testarmos, basta recarregar a página e, no console,executar primeiro a instrução que cria uma conexão, para entãofechá-lacomnossonovométodo:

//NOCONSOLEConnectionFactory.getConnection().then(conn=>console.log(conn));ConnectionFactory.closeConnection();

Recebemosumamensagemdeerro,aquelaqueprogramamosanteriormenteaotentarmosfecharaconexão:

340 15.5MONKEYPATCH:GRANDESPODERESTRAZEMGRANDESRESPONSABILIDADES

Page 364: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Figura15.6:Nãofoipossívelfecharaconexão

É fácil descobrirmos a causa. Nós substituímos o métodoclose()daconexãoparaevitarqueoprogramadordesavisadoafeche. No entanto, precisamos do comportamento original paraqueConnectionFactorysejacapazdefecharaconexão.Eagora?

Uma solução é guardarmos uma referência para a funçãooriginal antes de substituí-la. Essa referência será utilizada pelométodo closeConnection() da nossa ConnectionFactory .Nossaclassenofinalficaráassim:

constConnectionFactory=(()=>{

conststores=['negociacoes'];letconnection=null;

//VARIÁVELQUEARMAZENARÁAFUNÇÃOORIGINALletclose=null;

returnclassConnectionFactory{

constructor(){

thrownewError('Nãoépossívelcriarinstânciasdessaclasse');

15.5MONKEYPATCH:GRANDESPODERESTRAZEMGRANDESRESPONSABILIDADES 341

Page 365: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

}

staticgetConnection(){

returnnewPromise((resolve,reject)=>{

if(connection)returnresolve(connection);

constopenRequest=indexedDB.open('jscangaceiro',2);

openRequest.onupgradeneeded=e=>{

ConnectionFactory._createStores(e.target.result);

};

openRequest.onsuccess=e=>{

connection=e.target.result;

//GUARDANDOAFUNÇÃOORIGINAL!close=connection.close.bind(connection);

connection.close=()=>{thrownewError('Vocênãopodefechardir

etamenteaconexão');};resolve(connection);

};

openRequest.onerror=e=>{

console.log(e.target.error)reject(e.target.error.name)

};

});}

static_createStores(connection){

//códigoomitido}

342 15.5MONKEYPATCH:GRANDESPODERESTRAZEMGRANDESRESPONSABILIDADES

Page 366: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

staticcloseConnection(){

if(connection){

//CHAMANDOOCLOSEORIGINAL!close();

}}

}})();

É na variávelclose que guardamos uma referência para afunção original antes de realizarmos o monkey patch. Aliás,tivemos de apelar mais uma vez para a função bind() comconnection.close.bind(connection), paraque a funçãonão

percaseucontexto(nocaso,ainstânciadeconnection).

Comtudoemordem,recarregaremosmaisumavezapáginaerealizaremosnovamentenossoteste:

//NOCONSOLEConnectionFactory.getConnection().then(conn=>console.log(conn));ConnectionFactory.closeConnection();

Figura15.7:FechandoaconexãoporConnectionFactory

Excelente, conseguimos fechar a conexão pela

15.5MONKEYPATCH:GRANDESPODERESTRAZEMGRANDESRESPONSABILIDADES 343

Page 367: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

ConnectionFactorye,comisso,atendemos todososrequisitospara lidarcomaconexão.Ainda faltaorganizarmosasoperaçõesdepersistência,assuntodopróximocapítulo.

O código completo deste capítulo você encontra emhttps://github.com/flaviohenriquealmeida/cangaceiro-javascript/tree/master/15

344 15.5MONKEYPATCH:GRANDESPODERESTRAZEMGRANDESRESPONSABILIDADES

Page 368: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

CAPÍTULO16

"Obedecer é mais fácil do que entender." - Grande Sertão:Veredas

No capítulo anterior, aplicamos mais uma vez o padrão deprojetoFactorycriandoumaclassecomaresponsabilidadedecriarconexões. No entanto, o código anterior deixa a desejar, poisprecisamoslidarcomosdetalhesdepersistênciadenegociações.Seprecisarmos incluir negociações em diversas partes da nossaaplicação,teremosderepetirmuitocódigo.

Vejamos um exemplo de uso da nossa conexão para incluirumaconexão:

//EXEMPLODEUSOAPENAS

constnegociacao=newNegociacao(newDate(),200,1);

ConnectionFactory.getConnection().then(connection=>{

constrequest=connection.transaction(['negociacoes'],'readwrite').objectStore('negociacoes')

ENTRANDONALINHA

16ENTRANDONALINHA 345

Page 369: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

.add(negociacao);

request.onsuccess=e=>{

console.log('negociaçãosavlarcomsucesso');}

request.onerror=e=>{

console.log('Nãofoipossívelsalvaranegociação')}

});

Aboanotíciaéqueháoutropadrãodeprojetoquepodenosajudar a esconder a complexidade de persistência, o padrão deprojetoDAO.

O padrão de projeto DAO (Data Access Object) tem comoobjetivo esconder os detalhes de acesso aos dados, fornecendométodos de alto nível para o desenvolvedor. Em nosso caso,criaremos a classeNegociacaoDao que terá como dependênciaumaconexão.VejamosumexemplodecomoseráapersistênciadeumanegociaçãoatravésdoDAOquecriaremosaseguir:

//EXEMPLODOQUEQUEREMOSCONSEGUIRFAZERCOMNOSSODAO

constnegociacao=newNegociao(newDate(),1,100);

ConnectionFactory.getConnection().then(conn=>newNegociacaoDao(conn)).then(dao=>dao.adiciona(negociacao)).then(()=>console.log('Negociacaosalvacomsucesso').catch(err=>console.log('Nãofoipossívelsalvaranegociaç

ão'));});

16.1OPADRÃODEPROJETODAO

346 16.1OPADRÃODEPROJETODAO

Page 370: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Agora que já sabemos até onde queremos chegar, daremosinícioàconstruçãodonossoDAOnapróximaseção.

O primeiro passo para criarmos nosso DAO é adotarmos aseguinte convenção. O nome da classe será o nome do modelopersistido, seguido do sufixo Dao . Se em nossa aplicaçãorealizamos operações de persistência com Negociacao , entãoteremosaclasseNegociacaoDao.

Alémdaconvençãoquevimos,aclasseDAOprecisaráreceberem seu constructor() a conexão com o banco. Isso porque,casoqueiramosutilizaromesmoDAOcomooutrobanco,bastapassarmos a conexão correta, sem termos de alterar qualquercódigonaclasseDAO.

Porfim,todososmétodosdepersistênciadoDAOretornarãoPromises, pois operações de persistência com IndexedDB sãoassíncronas.

Combasenas convenções que acabamosde aprender, vamoscriar o arquivoclient/app/domain/negociacao/NegociacaoDao.jsedeclararo esqueletodonossoDAO:

//client/app/domain/negociacao/NegociacaoDao.js

classNegociacaoDao{

constructor(connection){

this._connection=connection;

16.2 CRIANDO NOSSO DAO DENEGOCIAÇÕES

16.2CRIANDONOSSODAODENEGOCIAÇÕES 347

Page 371: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

this._store='negociacoes';}

adiciona(negociacao){

returnnewPromise((resolve,reject)=>{

/*lidaremoscomainclusãoaqui*/});

}

listaTodos(){

returnnewPromise((resolve,reject)=>{

/*lidaremoscomoscursosaqui*/});

}}

O esqueleto do nosso DAO possui também a propriedadethis._storage. Ela guarda o nome da storage que será usada

pelo DAO. Sem hiato, vamos importar nossa nova classe emclient/index.html:

<!--client/index.html--><!--códigoanterioromitido-->

<scriptsrc="app/domain/negociacao/Negociacao.js"></script><scriptsrc="app/controllers/NegociacaoController.js"></script><scriptsrc="app/ui/converters/DateConverter.js"></script><scriptsrc="app/domain/negociacao/Negociacoes.js"></script><scriptsrc="app/ui/views/View.js"></script><scriptsrc="app/ui/views/NegociacoesView.js"></script><scriptsrc="app/ui/models/Mensagem.js"></script><scriptsrc="app/ui/views/MensagemView.js"></script><scriptsrc="app/util/ProxyFactory.js"></script><scriptsrc="app/util/Bind.js"></script><scriptsrc="app/util/ApplicationException.js"></script><scriptsrc="app/ui/converters/DataInvalidaException.js"></script>

<scriptsrc="app/domain/negociacao/NegociacaoService.js"></script>

348 16.2CRIANDONOSSODAODENEGOCIAÇÕES

Page 372: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

<scriptsrc="app/util/HttpService.js"></script><scriptsrc="app/util/ConnectionFactory.js"></script>

<!--importouaqui--><scriptsrc="app/domain/negociacao/NegociacaoDao.js"></script>

<scriptsrc="app/app.js"></script>

<!--códigoposterioromitido-->

Agora que já temos o esqueleto do nosso DAO pronto,podemos dar início à implementação da lógica do métodoadiciona() , aquele responsável pela inclusão de novas

negociações.

Vamos implementar o método adiciona() deNegociacaoDao. Não teremos nenhuma novidade, aplicaremos

tudooqueaprendemosatéagora:

//client/app/domain/negociacao/NegociacaoDao.js//códigoanterioromitido

adiciona(negociacao){

returnnewPromise((resolve,reject)=>{

constrequest=this._connection.transaction([this._store],'readwrite').objectStore(this._store).add(negociacao);

request.onsuccess=e=>resolve();request.onerror=e=>{

console.log(e.target.error);reject('Nãofoipossívelsalvaranegociação');

16.3 IMPLEMENTANDO A LÓGICA DEINCLUSÃO

16.3IMPLEMENTANDOALÓGICADEINCLUSÃO 349

Page 373: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

}});

}

//códigoposterioromitido

Não há surpresas, simplesmente quando a operação deinclusão é bem-sucedida, chamamosresolve() passandoumamensagem de sucesso. E quando há algum erro, chamamosreject()passandoumamensagemdealtonível,indicandoqueaoperaçãonãofoirealizada.

Veja que temos console.log(e.target.error) , muitoimportante para que o desenvolvedor escrutinando o consoleidentifique a causa do erro. Recarregando a página, faremos umteste. No console, vamos executar a seguinte instrução que criaumaconexãoeonossoDAO,eadicionaumanovanegociação:

//EXECUTARNOCONSOLEDONAVEGADOR

constnegociacao=newNegociacao(newDate(),7,100);

ConnectionFactory.getConnection().then(conn=>newNegociacaoDao(conn)).then(dao=>dao.adiciona(negociacao)).then(()=>console.log('Negociaçãosalvacomsucesso!')).catch(err=>console.log(err));

Veremosqueelegravouperfeitamenteasnegociações.

350 16.3IMPLEMENTANDOALÓGICADEINCLUSÃO

Page 374: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Figura16.1:Sucessonasgravação

Agora, vamos entender como se deu o encadeamento dasfunções. Primeiro, através da Promise retornada porConnectionFactory.getConnection(),temosacessoàconexãoencadeando uma chamada à função then() . Ainda nestethen(),depossedaconexão,retornamosimplicitamenteatravésde uma arrow function uma instância de NegociacaoDao querecebeaconexãoretornadapelaPromise.

Lembre-se de que as funções then() podem retornarqualquer valor, seja este síncrono ou o resultado da chamada deoutra Promise. Dessa forma, na próxima chamada a then() ,temos acesso à instância de NegociacaoDao , cujo o métodoadiciona() chamamos ao passar a negociação que desejamos

gravar.Comoométodoadiciona()éumaPromise,apróximachamadaencadeadaàthen()sóseráexecutadaseaoperaçãoforrealizadacomsucesso.

Por fim, temos a chamada da função catch() que podecapturarqualquererroquepossaocorrernaobtençãodaconexão

16.3IMPLEMENTANDOALÓGICADEINCLUSÃO 351

Page 375: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

ou da operação de persistência. Falta ainda implementarmos ométodo listaTodos(), aquele que interage com o cursor doIndexedDB.

Vamos implementar o método listaTodos() deNegociacaoDao.Ainda não teremos nenhumanovidade, então

aplicaremosnossoconhecimentojáadquirido:

//client/app/domain/negociacao/NegociacaoDao js//códigoanterioromitido

listaTodos(){

returnnewPromise((resolve,reject)=>{

constnegociacoes=[];

constcursor=this._connection.transaction([this._store],'readwrite').objectStore(this._store).openCursor();

cursor.onsuccess=e=>{

constatual=e.target.result;

if(atual){

constnegociacao=newNegociacao(atual.value._data,atual.value._quantidade,atual.value._valor);

negociacoes.push(negociacao);atual.continue();

}else{

16.4 IMPLEMENTANDO A LÓGICA DALISTAGEM

352 16.4IMPLEMENTANDOALÓGICADALISTAGEM

Page 376: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

//resolvendoapromisecomnegociacoesresolve(negociacoes);

}};

cursor.onerror=e=>{console.log(e.target.error);reject('Nãofoipossívellistarnasnegociações');

}

});}//códigoposterioromitido

Precisamostestarnossométodo,e faremosissomaisumavezatravésdoconsolecomaseguinteinstrução:

//NOCONSOLE

ConnectionFactory.getConnection().then(conn=>newNegociacaoDao(conn)).then(dao=>dao.listaTodos()).then(negociacoes=>console.log(negociacoes)).catch(err=>console.log(err));

Será exibido no console um array com todas as negociaçõesquepersistimosatéomomento.TemososdoismétodosdoDAOque criamos. Porém, toda vez que precisarmos de um DAO, oprogramador terá de obter uma conexão através deConnectionFactory, para em seguida obter uma instância do

DAO.PodemossimplificaresseprocessoisolandoacomplexidadedecriaçãodoDAOemumanovaclasse.

CriaremosaclasseDaoFactory que isolará a criaçãodeum

16.5CRIANDOUMADAOFACTORY

16.5CRIANDOUMADAOFACTORY 353

Page 377: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

DAO para nós. Vamos criar o arquivoclient/util/DaoFactory.js e declarar nossa classe que

possuiráapenasométodoestáticogetNegociacaoDao():

//client/app/util/DaoFactory.js

classDaoFactory{

staticgetNegociacaoDao(){

returnConnectionFactory.getConnection().then(conn=>newNegociacaoDao(conn));

}}

OmétodogetNegociacaoDao() retornaumaPromiseque,ao ser resolvida, nos dá acesso a uma instância deNegociacaoDao.

Vamosimportaraclasseemclient/index.html,para logoemseguidarealizarmosumteste:

<!--client/index.html--><!--códigoanterioromitido-->

<scriptsrc="app/domain/negociacao/Negociacao.js"></script><scriptsrc="app/controllers/NegociacaoController.js"></script><scriptsrc="app/utils/DateConverter.js"></script><scriptsrc="app/domain/negociacao/Negociacoes.js"></script><scriptsrc="app/ui/views/View.js"></script><scriptsrc="app/ui/views/NegociacoesView.js"></script><scriptsrc="app/ui/models/Mensagem.js"></script><scriptsrc="app/ui/views/MensagemView.js"></script><scriptsrc="app/util/ProxyFactory.js"></script><scriptsrc="app/util/Bind.js"></script><scriptsrc="app/util/ApplicationException.js"></script><scriptsrc="app/ui/converters/DataInvalidaException.js"></script>

<scriptsrc="app/domain/negociacao/NegociacaoService.js"></script>

354 16.5CRIANDOUMADAOFACTORY

Page 378: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

<scriptsrc="app/util/HttpService.js"></script><scriptsrc="app/util/ConnectionFactory.js"></script><scriptsrc="app/domain/negociacao/NegociacaoDao.js"></script>

<!--importouaqui--><scriptsrc="app/util/DaoFactory.js"></script>

<scriptsrc="app/app.js"></script>

<!--códigoposterioromitido-->

Apóso recarregamentodapágina, vamos executar a seguinteinstruçãonoconsoledonavegador:

//NOCONSOLE

DaoFactory.getNegociacaoDao().then(dao=>console.log(dao))

Noconsole,seráexibidaainstânciadeNegociacaoDao:

Figura16.2:DaoFactoryemação

Agoraque játemostodainfraestruturapronta,chegouahoraderealizarmosapersistênciadasnegociaçõesquecadastrarmos.

Na classe NegociacaoController , precisamos alterar ométodo adiciona() para que ele persista a negociação doformulário. Apenas se a operação for bem-sucedida,

16.6COMBINANDOPADRÕESDEPROJETO

16.6COMBINANDOPADRÕESDEPROJETO 355

Page 379: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

adicionaremosanegociaçãonatabela.

Vamos alterar o bloco try que fará uso da classeDaoFactory:

//client/app/controllers/NegociacaoController.js

//códigoanterioromitido

adiciona(event){

try{

event.preventDefault();

//negociaçãoqueprecisamosincluirnobancoenatabela

constnegociacao=this._criaNegociacao();

DaoFactory.getNegociacaoDao().then(dao=>dao.adiciona(negociacao)).then(()=>{

//sótentaráincluirnatabelaseconseguiuantesincluirnobanco

this._negociacoes.adiciona(negociacao);this._mensagem.texto='Negociaçãoadicionadacom

sucesso';this._limpaFormulario();

}).catch(err=>this._mensagem.texto=err);

}catch(err){

//códigoomitido}

}

//códigoposterioromitido

356 16.6COMBINANDOPADRÕESDEPROJETO

Page 380: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Vamosrecarregarapágina,paradepois incluirmosumanovanegociação. Será exibida a mensagem de sucesso, inclusive anegociaçãoseráinseridanatabela:

Figura16.3:Negociaçãoadicionadacomsucesso

Paranãotermossombradedúvidasdagravaçãodanegociação,vamos abrir o Console e acessar a aba Application, para emseguidavisualizarmosoconteúdodastorenegociacoes:

Figura16.4:Dadosdanegociação

Excelente!Agoraprecisamosexibirtodasasnegociaçõessalvasnobanco.

16.6COMBINANDOPADRÕESDEPROJETO 357

Page 381: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Em NegociacaoDao , já temos o método listaTodos()implementado, só precisamos chamá-lo assim queNegociacaoControllerforinstanciado.Faremosissoatravésdeseuconstructor():

//client/app/controllers/NegociacaoController.js

classNegociacaoController{

constructor(){

//códigoanterioromitido

DaoFactory.getNegociacaoDao().then(dao=>dao.listaTodos()).then(negociacoes=>

negociacoes.forEach(negociacao=>this._negociacoes.adiciona(negociacao)))

.catch(err=>this._mensagem.texto=err);}

//códigoposterioromitido}

Adicionamos cada negociação trazida do banco na lista denegociações que alimenta nossa tabela. Dessa forma, a tabela jáseráexibidalogodeinícioparaousuáriocomasnegociaçõesqueelejácadastrou.

16.7EXIBINDOTODASASNEGOCIAÇÕES

358 16.7EXIBINDOTODASASNEGOCIAÇÕES

Page 382: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Figura16.5:Bancocomtodasasinformações

Apesar de funcionar, é uma boa prática isolarmos em ummétodotodoocódigodeinicializaçãodeumaclassequenãodigarespeitoà inicializaçãodaspropriedadesdaclasse.Vamos criar ométodo_init()paraentãomovermosocódigoquebuscatodasasnegociaçõesparadentrodele:

classNegociacaoController{

constructor(){

//códigoanterioromitido

//chamaométododeinicializaçãothis._init();

}

_init(){

DaoFactory.getNegociacaoDao().then(dao=>dao.listaTodos()).then(negociacoes=>

negociacoes.forEach(negociacao=>this._negociacoes.adiciona(negociacao)))

.catch(err=>this._mensagem.texto=err);}

//códigoposterioromitido

16.7EXIBINDOTODASASNEGOCIAÇÕES 359

Page 383: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

}

Ainda faltaumaoperaçãodepersistência,aquelaquepermiteexcluirnegociações.

Nosso NegociacaoDao ainda não possui um método queapaguetodasasnegociaçõescadastradas,echegouahoradecriá-lo. Ele será praticamente idêntico ao método adiciona , adiferença é que não receberá nenhum parâmetro e quechamaremosafunçãoclear()emvezdeadd().Adicionandoo novométodoapagaTodos():

//client/app/domain/negociacao/NegociacaoDao.js//códigoanterioromitido

apagaTodos(){

returnnewPromise((resolve,reject)=>{

constrequest=this._connection.transaction([this._store],'readwrite').objectStore(this._store).clear();

request.onsuccess=e=>resolve();

request.onerror=e=>{console.log(e.target.error);reject('Nãofoipossívelapagarasnegociações');

};

});}//códigoposterioromitido

Por fim, precisamos alterar o método apaga() de

16.8REMOVENDOTODASASNEGOCIAÇÕES

360 16.8REMOVENDOTODASASNEGOCIAÇÕES

Page 384: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

NegociacaoController para que apague os dados do banco.Aliás, a tabela só seráesvaziada seaoperaçãodepersistênciadercerto:

//client/app/controllers/NegociacaoController.js//códigoanterioromitido

apaga(){

DaoFactory.getNegociacaoDao().then(dao=>dao.apagaTodos()).then(()=>{

this._negociacoes.esvazia();this._mensagem.texto='Negociaçõesapagadascomsuce

sso';}).catch(err=>this._mensagem.texto=err);

}//códigoposterioromitido

Já podemos recarregar nossa página e testarmos a novafuncionalidadeclicandonobotão"Apagar".Oresultandoseráaexclusão total das negociações do nosso banco e a limpeza databeladenegociações:

16.8REMOVENDOTODASASNEGOCIAÇÕES 361

Page 385: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Figura16.6:Negociaçõesexibidascomsucesso

Agora,quandorecarregarmosapágina,atabeladenegociaçõesestarávazia.

Figura16.7:Negociaçõestotalmenteapagadas

Ocódigoqueescrevemosfuncionacomoesperado,criamosa

16.9FACTORYFUNCTION

362 16.9FACTORYFUNCTION

Page 386: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

classe DaoFactory com o método estáticogetNegociacaoDao()queescondeacomplexidadedacriaçãodeinstâncias da classe NegociacaoDao. Porém, vale a pena umareflexão.

LinguagenspresasexclusivamenteaoparadigmadaOrientaçãoaObjetonãopermitemcriarmétodos semquepertençamaumaclasse.Nãosãorarasasclassescomapenasummétodo,namaioriadasvezesestáticopara implementaropadrãodeprojetoFactory.Noentanto,comoJavaScriptémultiparadigma,podemosescrevermenos se reimplementarmos o padrão Factory através doparadigmaFuncional.

Vamos alterar o arquivoclient/util/DaoFactory.js. EledeclararáapenasafunçãogetNegociacaoDao:

//client/app/util/DaoFactory.js

functiongetNegociacaoDao(){

returnConnectionFactory.getConnection().then(conn=>newNegociacaoDao(conn));

}

Não temosmais a declaração da classe e poupamos algumaslinhasdecódigo.

Agora, precisamos alterarclient/app/controllers/NegociacaoController.js para

usar a função getNegociacaoDao() no lugar deDaoFactory.getNegociacaoDao():

//client/app/controllers/NegociacaoController.js

classNegociacaoController{

16.9FACTORYFUNCTION 363

Page 387: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

constructor(){

//códigoomitido}

_init(){

//MUDANÇAAQUI

getNegociacaoDao().then(dao=>dao.listaTodos()).then(negociacoes=>

negociacoes.forEach(negociacao=>this._negociacoes.adiciona(negociacao)))

.catch(err=>this._mensagem.texto=err);}

adiciona(event){

event.preventDefault();

try{

constnegociacao=this._criaNegociacao();

//MUDANÇAAQUI

getNegociacaoDao().then(dao=>dao.adiciona(negociacao)).then(()=>{

this._negociacoes.adiciona(negociacao);this._mensagem.texto='Negociaçãoadicionadacom

sucesso';this._limpaFormulario();

}).catch(err=>this._mensagem.texto=err);

}catch(err){

//códigoomitido}

}

//códigoomitido

364 16.9FACTORYFUNCTION

Page 388: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

apaga(){

//MUDANÇAAQUI

getNegociacaoDao().then(dao=>dao.apagaTodos()).then(()=>{

this._negociacoes.esvazia();this._mensagem.texto='Negociaçõesapagadascomsuce

sso';}).catch(err=>this._mensagem.texto=err);

}}

Simplificamos ainda mais nosso código. Em suma, quandoprecisamos apenas de um método para realizar nosso trabalho,podemos evitar a declaração de classes e utilizar em seu lugar adeclaração de uma função. A aplicação prática de uma ou outraformadependerátambémdainclinaçãododesenvolvedorparaumououtroparadigma.

Excelente, terminamos nossa aplicação! Mas será que elafuncionaránosmaisdiversosnavegadoresdomercado?

O código completo deste capítulo você encontra emhttps://github.com/flaviohenriquealmeida/cangaceiro-javascript/tree/master/16

16.9FACTORYFUNCTION 365

Page 389: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

CAPÍTULO17

"Sertão:édentrodagente."-GrandeSertão:Veredas

Vimos que o JavaScript possui duas características queassombramdesenvolvedoresnessalinguagem:oescopoglobaleaordemdecarregamentodescripts.Oprimeiroproblematentamosminimizar através do module pattern, mas ele ainda continuaexistindo. Já com o segundo, nada pôde ser feito, jogando aresponsabilidade de importação nas costas do desenvolvedor.Contudo,apartirdoES2015(ES6),apróprialinguagemJavaScriptatacouessesproblemasatravésdeumsistemanativodemódulos.

O sistema demódulos do ES2015 (ES6) baseia-se em quatropilares:

1. Cada script é um módulo por padrão, que esconde suasvariáveisefunçõesdomundoexterno.

DIVIDIRPARACONQUISTAR

17.1MÓDULOSDOES2015(ES6)

366 17DIVIDIRPARACONQUISTAR

Page 390: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

2. A instrução export permite que ummódulo exporte osartefatosquedesejacompartilhar.

3. A instrução import permite que um módulo importeartefatos de outros módulos. Apenas artefatos exportadospodemserimportados.

4. O uso de um loader (carregador), aquele que será oresponsávelemcarregaroprimeiromóduloeresolvertodasassuasdependênciasparanós,semtermosdenospreocuparemimportarcadascriptemumaordemespecífica.

Um ponto a destacar dos quatro pilares que vimos é o doloader.MesmonaversãoES2017doJavaScriptnãoháconsensonaespecificaçãodoloader,porissoprecisamosapelarparabibliotecasde terceiros, capazes de resolver o carregamento de módulos. AbibliotecaqueusaremosseráaSystem.js.

AbibliotecaSystem.js(https://github.com/systemjs/systemjs)éum loaderuniversalcomsuporteaos formatosdemóduloAMD,CommonJS, inclusive do ES2015 (ES6), aquele que temosinteresse. Como temos o Node.js instalado como requisito deinfraestrutura, podemosbaixar a biblioteca atravésdonpm, seugerenciadordemódulos.

Precisamos preparar o terreno antes de instalarmos oSystem.js.Oprimeiropassoéabrirmosnossoterminalfavoritoe,com a certeza de estarmos dentro da pastajscangaceiro/client,executarocomando:

17.2INSTALANDOOLOADER

17.2INSTALANDOOLOADER 367

Page 391: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

npminit

PodemosteclarEnterparatodasasperguntassemproblemaalgum e, no final, será gerado o arquivo package.json . Empoucaspalavras,estearquivoguardaráonomeeaversãodetodasasdependênciasquebaixarmospelonpm.

O arquivo jscangaceiro/client/package.json criadopossuiaseguinteestrutura:

{"name":"client","version":"1.0.0","description":"","main":"index.js","scripts":{"test":"echo\"Error:notestspecified\"&&exit1"},"author":"","license":"ISC"}

Agora,aindadentrodapastajscangaceiro/client, vamosinstalaroSystem.jsatravésdocomando:

[email protected]

Oparâmetro--save registra omódulobaixadono arquivopackage.json.Porfim,apósocomandoterminardeexecutar,oSystem.js e todas as suas dependências estarão dentro da pastajscangaceiro/client/node_modules criada automaticamente

pelonpm.

Agora, sem nos assustarmos, removeremos todos os scriptsimportados em client/index.html . O System.js será oresponsável pelo carregamento dos nossos scripts que serãoconvertidosemmódulos.

368 17.2INSTALANDOOLOADER

Page 392: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Vamosimportarabibliotecaeindicarqueoprimeiromóduloasercarregadoseráoclient/app/app.js:

<!--client/index.html--><!--códigoanterioromitido-->

<divid="negociacoes"></div>

<!--importouabibliotecadoSystem.js--><scriptsrc="node_modules/systemjs/dist/system.js"></script>

<!--indicandoqualseráoprimeiromóduloasercarregado-->

<script>System

.import('app/app.js') catch(err console error(err))

</script></body></html>

É muito importante que estejamos acessando nosso projetoatravés do servidor disponibilizado com o projeto, porque oSystem.jsbaixaosmódulosatravésderequisiçõesassíncronascomXMLHttpRequest.

Acessandonossa aplicação pormeio delocalhost:3000 everificando o console do navegador, recebemos a seguintemensagemdeerro:

17.2INSTALANDOOLOADER 369

Page 393: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Figura17.1:Problemanocarregamentodomódulo

Oerroeratotalmenteesperado,poisaindaprecisamosajustarcadascriptquecriamostornando-osmódulosdoES2015(ES6).

Quando adotamos o sistema de módulos do ES2015 (ES6),cada scriptporpadrão seráconsideradoummóduloqueconfinatudo o que declara. Precisaremos configurar cada móduloindicandooqueeleimportaeexporta.

Uma técnica que pode ajudar no processo de ajuste éperguntarmosparaomóduloquais são suasdependências.Essasdependências precisam ser importadas através da instruçãoimport.Depois,precisamosnosperguntaroqueomódulodeve

17.3 TRANSFORMANDO SCRIPTS EMMÓDULOS

370 17.3TRANSFORMANDOSCRIPTSEMMÓDULOS

Page 394: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

exportarparaaquelesquedesejamutilizá-lo.Fazemosissoatravésdainstruçãoexport.

Analisando omódulo client/app/app.js, vemos que elepossuiapenasumadependência,aclasseNegociacaoControllerdefinida no módulo NegociacaoController.js . Alteraremosapp.jseimportaremossuadependência:

//client/app/app.js

import{NegociacaoController}from'./controllers/NegociacaoController.js';

constcontroller=newNegociacaoController();const$=document.querySelector.bind(document);

//códigoposterioromitido

Nesse código, estamos importando a classeNegociacaoController do módulo

./controller/NegociacaoController.js.Contudo,issoaindanão funcionará,poisNegociacaoController.js não exporta aclasseNegociacaoController.Precisamosalterá-lo:

exportclassNegociacaoController{

//códigoomitido}

A questão agora é que nosso NegociacaoController

dependedeoutrasclasses.Vamosimportartodaselas:

import{Negociacoes}from'../domain/negociacao/Negociacoes.js';import{NegociacoesView}from'../ui/views/NegociacoesView.js';import{Mensagem}from'../ui/models/Mensagem.js';import{MensagemView}from'../ui/views/MensagemView.js';import{NegociacaoService}from'../domain/negociacao/NegociacaoService.js';

17.3TRANSFORMANDOSCRIPTSEMMÓDULOS 371

Page 395: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

import{getNegociacaoDao}from'../util/DaoFactory.js';import{DataInvalidaException}from'../ui/converters/DataInvalidaException.js';import{Negociacao}from'../domain/negociacao/Negociacao.js';import{Bind}from'../util/Bind.js';import{DateConverter}from'../ui/converters/DateConverter.js';

exportclassNegociacaoController{/*códigoomitido*/}

Nãopodemosdeixardeimportarnenhumadependência;casocontrário, ela não será visível para nosso módulo. Agora,precisamosajustarcadaumdosmódulosimportados:

Negociacoes.js:

exportclassNegociacoes{/*códigoomitido*/

}

NegociacoesView.js:

import{View}from'./View.js';import{DateConverter}from'../converters/DateConverter.js';

exportclassNegociacoesViewextendsView{/*códigoomitido*/

}

A classe NegociacoesView precisou importar View eDateConverter , pois são dependências da classe. Vamos

continuar:

Mensagem.js:

exportclassMensagem{/*códigoomitido*/

}

372 17.3TRANSFORMANDOSCRIPTSEMMÓDULOS

Page 396: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

MensagemView.js:

import{View}from'./View.js';

exportclassMensagemViewextendsView{/*códigoomitido*/

}

NegociacaoService.js:

import{HttpService}from'../../util/HttpService.js';import{Negociacao}from'./Negociacao.js';

exportclassNegociacaoService{/*códigoomitido*/

}

DaoFactory.js:

import{ConnectionFactory}from'./ConnectionFactory.js';import{NegociacaoDao}from'../domain/negociacao/NegociacaoDao.js';

exportfunctiongetNegociacaoDao{/*códigoomitido*/

}

DataInvalidaException.js:

import{ApplicationException}from'../../util/ApplicationException.js';

exportclassDataInvalidaExceptionextendsApplicationException{

/*códigoomitido*/}

Negociacao.js:

exportclassNegociacao{/*códigoomitido*/

17.3TRANSFORMANDOSCRIPTSEMMÓDULOS 373

Page 397: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

}

NegociacaoDao.js:

import{Negociacao}from'./Negociacao.js';

exportclassNegociacaoDao{/*códigoomitido*/

}

DateConverter.js:

import{DataInvalidaException}from'./DataInvalidaException.js';

exportclassDateConverter{/*códigoomitido*/

}

View.js:

exportclassView{/*códigoomitido*/

}

Bind.js:

import{ProxyFactory}from'./ProxyFactory.js';

exportclassBind{/*códigoomitido*/

}

ParaaclasseConnectionFactory,podemosremoveraIIFE,poiscomoestamosusandoosistemademódulonativodoES2015(ES6),variáveise funçõesdomóduloautomaticamentenãoserãoacessíveis fora dele. No caso, basta exportarmos a classeConnectionFactoryparaqueelasejaacessível.

ConnectionFactory.js:

374 17.3TRANSFORMANDOSCRIPTSEMMÓDULOS

Page 398: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

conststores=['negociacoes'];letconnection=null;letclose=null;

exportclassConnectionFactory{/*códigoomitido*/

}

ApplicationException.js:

exportclassApplicationExceptionextendsError{/*códigoomitido*/

}

HttpService.js:

exportclassHttpService{/*códigoomitido*/

}

ProxyFactory.js:

exportclassProxyFactory{/*códigoomitido*/

}

Todos os módulos foram configurados. Entretanto, serecarregarmos a aplicação e olharmos no console do navegador,veremosaseguintemensagemdeerro:

Figura17.2:Sucessonagravação

O System.js não conseguiu importar nossosmódulos, apesar

17.3TRANSFORMANDOSCRIPTSEMMÓDULOS 375

Page 399: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

deles estarem no formato ES2015 (ES6). É necessária umaconfiguraçãoextraparaque sejam importadospelonosso loader.Essa configuração extra pode ser realizada através de umtranspiler,comoindicadonamensagemdeerro.Masoqueéumtranscompilador?

Umtranscompilador(transpiler)éumcompiladorquepermiterealizar transformações em nosso código, adicionando códigoextraouatémesmotraduzindoocódigo-fontedeumalinguagemparaoutra.

Esseprocessopodeserfeitodiretamentenonavegador,maséaltamentedesencorajadoemprodução,devidoàquantidadeextradescriptsqueserãocarregadosduranteoprocesso.Assim,oneraotempodecarregamentodapáginae,porconseguinte, impactanoranking de pesquisa orgânica do Google. Páginas que sãocarregadasmaisrapidamentesãofavorecidasnesseranking.

Precisamos de um transcompilador que rode localmente, emtempo de desenvolvimento, e que gere os arquivos modificadosque serão carregados pelo navegador. Sem dúvidas, o Babel(https://babeljs.io) é o transcompilador mais utilizado pelacomunidadeopensource.

Sua estrutura baseada em plugins possibilitou a criação dediversas funcionalidades já prontas para uso. É ele queutilizaremos. Porém, antes de instalarmos e configurarmos oBabel,precisamosrealizarumpequenoajusteemnossoprojeto.

Vamos renomear a pastaclient/app para client/app-

17.4OPAPELDEUMTRANSCOMPILADOR

376 17.4OPAPELDEUMTRANSCOMPILADOR

Page 400: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

src.Osufixosrc indicaque essapasta armazenaos arquivosoriginaisdoprojeto.

Com a pasta renomeada, podemos passar para a próximaseção,parainstalaroBabel.

QuandousamosBabel,estamosadicionandoemnossoprojetoum build step, ou seja, um passo de construção em nossaaplicação. Isso significa que ela não pode ser consumidadiretamente antes de passar por esse processo. Sendo assim, oprimeiro passo é instalar o Babel em nosso projeto, para entãoconfigurá-lo.

Dentro da pasta jscangaceiro/client , instalaremos obabel-cliatravésdonpm:

[email protected]

Alémdobabel-cli, precisamos instalar o plugin babel-plugin-transform-es2015-modules-systemjsqueadequaráosmódulosdoES2015aosistemadecarregamentosdoSystem.js:

npminstallbabel-plugin-transform-es2015-modules-systemjs@6.24.1--save-dev

Agora,precisamosexplicitarqueopluginquebaixamosdeveserusadopeloBabel.Paraisso,vamoscriaroarquivo.babelrcdentrodejscangaceiro/client,comaseguinteconfiguração:

{"plugins":["transform-es2015-modules-systemjs"]

}

17.5BABEL,INSTALAÇÃOEBUILD-STEP

17.5BABEL,INSTALAÇÃOEBUILD-STEP 377

Page 401: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Com tudo instalado, vamos adicionar um script no arquivopackage.jsonqueexecutaráoBabelparanós.Onomedoscriptserábuild:

{"name":"client","version":"1.0.0","description":"","main":"index.js","scripts":{"test":"echo\"Error:notestspecified\"&&exit1",

"build":"babelapp-src-dapp--source-maps"},"author":"","license":"ISC","dependencies":{"systemjs":"^0.20.12"

},"devDependencies":{"babel-cli":"^6.24.1","babel-plugin-transform-es2015-modules-systemjs":"^6.24.1"

}}

Vamosanalisaroscriptqueadicionamos:

"build":"babelapp-src-dapp--source-maps"

Já estamos preparados para testar. Tenha certeza de terrenomeadoapastaclient/app paraclient/app-src, comofoipedidoantes.

Vamostestarocomandonpmrunbuild:

npmrunbuild

Após o comando ter sido processado, será criadaautomaticamenteapastaclient/appcomamesmaestruturadepastasearquivosdeclient/app-src.Adiferençaéqueocódigoestátranscompilado.

378 17.5BABEL,INSTALAÇÃOEBUILD-STEP

Page 402: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Vamos abrir o arquivoclient/app/domain/negociacao/Negociacao.js e ver seu

conteúdo:

System.register([],function(_export,_context){"usestrict";

return{setters:[],execute:function(){

classNegociacao{

/*códigoanterioromitido*/}

_export("Negociacao",Negociacao);}

};});

É uma sintaxe bem diferente do arquivo original, massuficiente para que o System.js seja capaz de carregar nossasdependências. Porém, se um erro acontecer em nosso arquivo,veremos a indicação da posição do erro pelo debugger donavegadornoarquivotranscompilado.Precisamossaberaposiçãonoarquivooriginal,poisénelequeprecisamosdarmanutenção.

Aboanotíciaéque,aogerarmosnossosarquivos,oBabelgeratambémumarquivo.map(sourcemap)paracadaumdeles.

Um arquivo sourcemap serve para ajudar no processo dedepuraçãonoprópriobrowser, indicandoo errona estruturadoarquivo original em vez do arquivo transcompilado. Faz todosentido,poisénooriginalquedaremosmanutenção.

17.6SOURCEMAP

17.6SOURCEMAP 379

Page 403: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

O carregamento de arquivos .map pode variar entrenavegadores. No Chrome, por exemplo, eles serão carregadosapenas quando o console do navegador for acessado. Já outrosnavegadores necessitam que o carregamento seja explicitadoatravés de alguma opção de desenvolvedor. Agora nada nosimpedederealizarmosumteste.

Acessando mais uma vez http://localhost:3000 erecarregandonossapágina,elacontinuafuncionandocomoantes.Vejaqueagorasóprecisamosindicarqualseráoprimeiromóduloasercarregadopelonossoloaderqueeleseencarregaráderesolvertodas as suas dependências, removendo assim essaresponsabilidadedodesenvolvedor.

Além do que vimos, não conseguimos mais acessar asdefiniçõesdasclassesqueantesviviamnoescopoglobalatravésdoconsole. Ou seja, não há mais nenhuma informação da nossaaplicaçãonoescopoglobal.

Casoalgumerro tenhaacontecido, é importanteverificarmosse exportamos e importamos corretamente todos os artefatos daaplicação,paraemseguidaexecutarmosmaisumavezocomandonpmrunbuild.Aliás, terdeexecutartodavezestecomandoa

cada modificação é jogar muita responsabilidade no console dodesenvolvedor. Na próxima seção, aprenderemos a automatizaresseprocesso.

Podemosmelhoraraexperiênciadodesenvolvedorrealizando

17.7 COMPILANDO ARQUIVOS EM TEMPOREAL

380 17.7COMPILANDOARQUIVOSEMTEMPOREAL

Page 404: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

o processodetranscompilaçãoautomaticamentetodavezqueumarquivoforalteradonoprojeto.Aaçãodeveserrealizadaquandofazemosodeploy,paragarantirqueestátudocompilado.

Mas será que o desenvolvedor vai querer ter estaresponsabilidade a todomomento? Por isso, o Babel vem comowatcher, umobservadorde arquivosque automaticamente faráoprocessodetranscompilaçãoquandoumarquivoforalterado.Parahabilitá-lo, vamos no arquivopackge.json e adicionaremos owatch:

{"name":"client","version":"1.0.0","description":"","main":"index.js","scripts":{"test":"echo\"Error:notestspecified\"&&exit1","build":"babelapp-src-dapp--source-maps","watch":"babelapp-src-dapp--source-maps--watch"

},"author":"","license":"ISC","devDependencies":{"babel-cli":"^6.24.1",

"babel-plugin-transform-es2015-modules-systemjs":"^6.24.1"},"dependencies":{"systemjs":"^0.20.12"

}}

Noterminal,vamosexecutarowatch:

npmrunwatch

Elevaicompilarosarquivoseoterminalficarámonitorandoamodificaçãodetodoseles.Oprocessodecompilaçãocorrerábeme, ao recarregarmos a página de cadastro, tudo funcionará

17.7COMPILANDOARQUIVOSEMTEMPOREAL 381

Page 405: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

corretamente.

Demosumpassoextra introduzindoumtranscompiladoremnossoprojeto,masseráquepodemosiralém?Comcerteza!

AprendemosautilizarosistemademódulosdoES2015(ES6),evitando assim o escopo global e a responsabilidade decarregarmos scripts em uma ordem correta. Todavia, podemosorganizaraindamelhornossasimportações.

Vejamos primeiro como fizemos as importações emNegociacaoController:

//client/app-src/controllers/NegociacaoController.js

//APENASREVISÃODECÓDIGO

import{Negociacoes}from'../domain/negociacao/Negociacoes.js';import{NegociacoesView}from'../ui/views/NegociacoesView.js';import{Mensagem}from'../ui/models/Mensagem.js';import{MensagemView}from'../ui/views/MensagemView.js';import{NegociacaoService}from'../domain/negociacao/NegociacaoService.js';import{getNegociacaoDao}from'../util/DaoFactory.js';import{DataInvalidaException}from'../ui/converters/DataInvalidaException.js';import{Negociacao}from'../domain/negociacao/Negociacao.js';import{Bind}from'../util/Bind.js';import{DateConverter}from'../ui/converters/DateConverter.js';

exportclassNegociacaoController{

//códigoposterioromitido

17.8 BARREL, SIMPLIFICANDO AIMPORTAÇÃODEMÓDULOS

382 17.8BARREL,SIMPLIFICANDOAIMPORTAÇÃODEMÓDULOS

Page 406: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Reparequeimportamosváriosartefatosdeumamesmapasta,aliás, repetindo a instrução import diversas vezes. Podemossimplificarbastanteessaimportaçãoatravésdebarrels.Umbarrelnadamaisédoqueummóduloqueimportaeexportaosmódulosque importou. Com isso, podemos importar em uma únicainstruçãováriosartefatosexportadospelobarrel.Umexemplo:

//EXEMPLO,AINDANÃOENTRANAAPLICAÇÃO

import{Negociacoes,NegociacaoService,Negociacao}from'../domain/index';

Vamos começar criando o barrel para a pasta app-

src/models,atravésdoarquivoapp-src/models/index.ts:

//client/app-src/domain/index.ts

export*from'./negociacao/Negociacao.js';export*from'./negociacao/NegociacaoDao.js';export*from'./negociacao/NegociacaoService.js';export*from'./negociacao/Negociacoes.js';

Agora,faremosamesmacoisaparaclient/app-src/ui:

//client/app-src/ui/index.js

export*from'./views/MensagemView.js';export*from'./views/NegociacoesView.js';export*from'./views/View.js';export*from'./models/Mensagem.js';export*from'./converters/DataInvalidaException.js';export*from'./converters/DateConverter.js';

Porfim,faremosemclient/app-src/utils:

//client/app-src/util/index.js

export*from'./Bind.js';export*from'./ConnectionFactory.js';export*from'./DaoFactory.js';export*from'./ApplicationException.js';

17.8BARREL,SIMPLIFICANDOAIMPORTAÇÃODEMÓDULOS 383

Page 407: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

export*from'./HttpService.js';export*from'./ProxyFactory.js';

Eentão,alteraremosNegociacaoController:

import{Negociacoes,NegociacaoService,Negociacao}from'../domain/index.js';import{NegociacoesView,MensagemView,Mensagem,DataInvalidaException,DateConverter}from'../ui/index.js';import{getNegociacaoDao,Bind}from'../util/index.js';

exportclassNegociacaoController{

//códigoposterioromitido

Muitomenoslinhasdecódigo!

O código completo deste capítulo você encontra emhttps://github.com/flaviohenriquealmeida/cangaceiro-javascript/tree/master/17

384 17.8BARREL,SIMPLIFICANDOAIMPORTAÇÃODEMÓDULOS

Page 408: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

CAPÍTULO18

"Sertãoéisto:osenhorempurraparatrás,masderepenteelevolta a rodear o senhor dos lados. Sertão é quandomenos seespera."-GrandeSertão:Veredas

Nocapítuloanterior,comautilizaçãodemódulosdoES2015(ES6), resolvemos tanto o problema do escopo global quanto daordem de importação de scripts. Ainda há uma melhoria quepodemos realizar em nosso código, mas primeiro, vamosrelembrardaimportânciadasPromisesnele.

Utilizamos o padrão de projeto Promise para evitarmos ocallbackHELL, além de centralizarmos em um único lugar o

tratamentodeerrodasPromisesenvolvidasnaoperação.Vejamoso código que implementamos no método _init() deNegociacaoController:

//REVISANDOAPENAS!

//client/app-src/controllers/NegociacaoController.js//códigoanterioromitido

_init(){

INDOALÉM

18INDOALÉM 385

Page 409: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

.getNegociacaoDao().then(dao=>dao.listaTodos()).then(negociacoes=>

negociacoes.forEach(negociacao=>this._negociacoes.adiciona(negociacao)))

.catch(err=>this._mensagem.texto=err);//TRATAOERROEMUMLUGARAPENAS}

//códigoposterioromitido

Contudo, mesmo a solução com Promises não torna nossocódigo tão legível quanto a escrita de um código síncrono ebloqueante.Vejamosumcódigohipotético:

//EXEMPLODECOMOSERIASEOCÓDIGO//FOSSEESCRITODEMANEIRASÍNCRONAEBLOQUEANTE

_init(){

try{

//bloquearáaexecuçãodaaplicaçãoenquantonãoterminarconstdao=getNegociacaoDao();

//bloquearáaexecuçãodaaplicaçãoenquantonãoterminarconstnegociacoes=dao.listaTodos();

negociacoes.forEach(negociacao=>this._negociacoes.adiciona(negociacao)))}catch(err){

this._mensagem.texto=err;}}

Neste exemplo, a funçãogetNegociacaoDao() e ométododao.listaTodos() são síncronos e bloqueantes. Por serem

síncronos,podemoscapturarexceçõesatravésdascláusulastryecatch, pois o erro acontece na mesma pilha de execução do

nossoprograma.

386 18INDOALÉM

Page 410: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Porém,paraqueocódigoanteriornãoafeteaperformancedaaplicação,cadachamadadométodo_init()deveserexecutadaemumathread em separado, paranãobloquear o restantedaaplicação. Linguagens como Java e C# dão suporte amultithreading,resolvendooproblemadobloqueiodaaplicação.

O JavaScript é single thread (apesar de hoje podermos criarthreads levescomWebWorkers,quenãosão tão flexíveisquantothreads em outras linguagens), por isso não podemos executaroperaçõesdeI/Odemaneirabloqueante.Issoporque,seotempodeexecuçãodométodolistaTodos()demorassetrêssegundos,a aplicação ficaria congelada durante esse tempo até voltar arespondereprocessaroutrasoperaçõesexecutadaspelousuário.

Contudo, pormais estranho que isso possa soar, a partir doES2017 (ES8) já podemos escrever um código assíncrono nãobloqueante, muito parecido com a maneira que escrevemos umcódigo síncrono bloqueante, impactando diretamente nalegibilidade do código da nossa aplicação. Vejamos como napróximaseção.

A versão ECMAScript 2017 introduziu a sintaxeasync/await que permite escrevermos códigos assíncronos

envolvendoPromisesdemaneiraelegante.Vejamosmaisumavezo método_init(),masdessavezescritocomanovasintaxe:

//APENASUMEXEMPLO

async_init(){

18.1 ES2017 (ES8) E O AÇÚCAR SINTÁTICOASYNC/AWAIT

18.1ES2017(ES8)EOAÇÚCARSINTÁTICOASYNC/AWAIT 387

Page 411: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

try{constdao=awaitgetNegociacaoDao();constnegociacoes=awaitdao.listaTodos();negociacoes.forEach(negociacao=>this._negociacoes.adiciona(

negociacao)))}catch(err){this._mensagem.texto=err;

}}

Vejam que a estrutura do nosso código é extremamenteparecida com a estrutura hipotética síncrona que vimos. Adiferença está emdois pontos.Oprimeiro é a instruçãoasyncusadaantesdonomedométodo_init().Essainstruçãoindicaque a função estápreparadapara executar operações assíncronasquepodemseresperadas.

Osegundopontoéapresençadainstruçãoawaitantesdaschamadas de métodos que devolvem Promises . QuandogetNegociacaoDao() for chamado, o bloco do método

_init()serásuspenso,saindodapilhadeexecuçãoprincipaldaaplicação.

Seonossométodonãofazmaispartedapilhadeexecuçãodathread principal, o JavaScript continuará a executar outrasoperações.Maseagora?Nossométodo_init()caiunolimbo?Sim,masatéqueaPromiseretornadaporgetNegociacaoDao()sejaresolvida.

QuandoaPromise,quejáéumaoperaçãoassíncronaenãobloqueante, for resolvida, ela internamente fará um resume donosso método _init() . Isso o fará voltar para a pilha deexecução principal e ficará disponível para ser processado peloEventLoopdoJavaScript.

388 18.1ES2017(ES8)EOAÇÚCARSINTÁTICOASYNC/AWAIT

Page 412: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Quando nosso método _init() volta a ser executado,recebemos como retorno o valor já resolvido da Promise dainstruçãogetNegociacaoDao().Queloucura!

Esseprocesso se repetiránapróxima instruçãocoma sintaxeawait antesda chamadadométododao.listaTodos(), que

tambémdevolveumaPromise.Porfim,podemostratarqualquerrejeiçãodasPromisesdentrodoblocotry ecatch, algoquenãoerapossívelantes.Issoépossívelporque,quandohouverumarejeição de uma Promise, também haverá o resume do método_init() e a causada rejeição será lançada comoumaexceção,

permitindo identificar com clareza o ponto em que o erroaconteceuemnossocódigo.

Asintaxeasync/awaitnaverdadeéumaçúcarsintáticoparao uso concomitante de Promises com funções geradoras(generators).

Generatorssãofunçõesquepermitemsuspendereresumirsuaexecuçãoemqualquerpontodoseubloco,mantendoseucontextooriginal. Outra característica é que podem retornar um valormesmo sem o uso da instrução return. Vamos construir umsimplesexemplo:

//EXEMPLODEFUNÇÃOGERADORA

function*minhaFuncaoGeradora(){

}

//itéumITERATORletit=minhaFuncaoGeradora();

18.2PARASABERMAIS:GENERATORS

18.2PARASABERMAIS:GENERATORS 389

Page 413: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Generators possuem uma declaração especial, e possuem apalavra-chavefunction seguidadeum* (asterisco).QuandoexecutamosminhaFuncaoGeradora(), recebemos como retornonãooseuresultado,masumIterator,umobjetoespecialquenospermiteresumirogeneratore,eventualmente,extrairvalores.

A instrução yield é aquela responsável em suspender aexecução do bloco do gerador e também, quando especificado,retornar um valor que pode ser acessado através do Iterator. Énecessárioqueovalorvenhaàdireitadainstrução:

//EXEMPLODEFUNÇÃOGERADORA

function*minhaFuncaoGeradora(){

for(leti=0;i<3;i++){

//INSTRUÇÃOYIELD!//SUSPENDEAEXECUÇÃODOBLOCODAFUNÇÃO//ERETORNAOVALORDEI

yieldi;}}

letit=minhaFuncaoGeradora();

Quando executarmos uma função geradora, seu bloco aindanão será executado. É por isso que obrigatoriamente precisamoschamar pelomenos uma vez ométodoit.next() do Iteratorretornado. Com a chamada it.next() , o bloco da funçãogeradoraseráexecutadoatéquesedeparecomainstruçãoyieldquevaisuspendê-lo.

//EXEMPLODEFUNÇÃOGERADORA

function*minhaFuncaoGeradora(){

390 18.2PARASABERMAIS:GENERATORS

Page 414: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

for(leti=0;i<3;i++){

yieldi;}}

letit=minhaFuncaoGeradora();

//EXECUTAOBLOCODOGENERATOR,QUESERÁSUSPENSO//ASSIMQUEENCONTRARAINSTRUÇÃOYIELD

letobjeto=it.next();

A funçãoit.next() nos devolve um objeto que possui aspropriedades value e done . O primeiro nos dá acesso aqualquer valor retornado pela instrução yield, já o segundoindica através de true ou false se o bloco da função dogeneratorchegouaofim.

//EXEMPLODEFUNÇÃOGERADORA

function*minhaFuncaoGeradora(){

for(leti=0;i<3;i++){

//aosuspender,forneceaoiteratorovalordeiyieldi;

}}

letit=minhaFuncaoGeradora();letobjeto=it.next();console.log(objeto.value)//0éovalordeiconsole.log(objeto.done)//false,aindanãoterminou

Podemos chamar várias vezesit.next() para resumirmosnossafunção:

//EXEMPLODEFUNÇÃOGERADORA

function*minhaFuncaoGeradora(){

18.2PARASABERMAIS:GENERATORS 391

Page 415: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

for(leti=0;i<3;i++){

yieldi;}}

letit=minhaFuncaoGeradora();

//EXECUTAOBLOCODOGENERATOR,QUESERÁSUSPENSO//ASSIMQUEENCONTRARAINSTRUÇÃOYIELD

letobjeto=it.next();console.log(objeto.value)//0console.log(objeto.done)//falseobjeto=it.next();console.log(objeto.value)//1console.log(objeto.done)//falseobjeto=it.next();console.log(objeto.value)//2console.log(objeto.done)//falseobjeto=it.next();console.log(objeto.value)//undefinedconsole.log(objeto.done)//true

Porfim,podemossimplificarnossocódigodestaforma:

//EXEMPLODEFUNÇÃOGERADORA

function*minhaFuncaoGeradora(){

for(leti=0;i<3;i++){

yieldi;}}

letit=minhaFuncaoGeradora();

letobjeto=null;

while(!(objeto=it.next()).done){console.log(objeto.value);

}

392 18.2PARASABERMAIS:GENERATORS

Page 416: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

O mais importante para nós aqui é entendermos o papelsimplificadordeasync/awaitnousodeGeneratorsePromises.

Chegou a hora de usarmos async/wait em nosso projeto,poisjávimoscomoelefuncionaesuasvantagens.Vamoscomeçarpelométodoqueusamoscomoexemplohipotético,masqueagoramodificaremosefetivamente.

Vamos modificar o método _init() deNegociacaoController para que faça uso da instrução

async/await:

//client/app-src/controllers/NegociacaoController.js//códigoanterioromitido

async_init(){

try{constdao=awaitgetNegociacaoDao();constnegociacoes=awaitdao.listaTodos();negociacoes.forEach(negociacao=>this._negociacoes.adici

ona(negociacao));}catch(err){

//err.messageextraiapenasamensagemdeerrodaexceçã

this._mensagem.texto=err.message;}

}

//códigoposterioromitido

Lembre-sedequesópodemosutilizara sintaxeawait parauma Promise dentro de uma função async ; caso contrário,

18.3 REFATORANDO O PROJETO COMASYNC/WAIT

18.3REFATORANDOOPROJETOCOMASYNC/WAIT 393

Page 417: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

receberemosamensagemdeerro"awaitisareservedword".

Agora,vamosalterarométodoadiciona():

//client/app-src/controllers/NegociacaoController.js//códigoanterioromitido

asyncadiciona(event){

try{

event.preventDefault();constnegociacao=this._criaNegociacao();

constdao=awaitgetNegociacaoDao();awaitdao.adiciona(negociacao);this._negociacoes.adiciona(negociacao);this._mensagem.texto='Negociaçãoadicionadacomsucesso

;this._limpaFormulario();

}catch(err){

this._mensagem.texto=err.message;}

}

//códigoposterioromitido

Ométodoapaga()também:

//client/app-src/controllers/NegociacaoController.js//códigoanterioromitido

asyncapaga(){

try{constdao=awaitgetNegociacaoDao();awaitdao.apagaTodos();this._negociacoes.esvazia();this._mensagem.texto='Negociaçõesapagadascomsucesso'

;

}catch(err){

394 18.3REFATORANDOOPROJETOCOMASYNC/WAIT

Page 418: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

this._mensagem.texto=err.message;}

}

//códigoposterioromitido

Antes de alterarmos o método importaNegociacoes() ,alteraremos obtemNegociacoesDoPeriodo deNegociacaoService para que faça uso do async/await. O

métodoficaráassim:

//client/app-src/domain/negociacao/NegociacaoService.js

//códigoanterioromitido

asyncobtemNegociacoesDoPeriodo(){

try{letperiodo=awaitPromise.all([

this.obtemNegociacoesDaSemana(),this.obtemNegociacoesDaSemanaAnterior(),this.obtemNegociacoesDaSemanaRetrasada()

]);returnperiodo

.reduce((novoArray,item)=>novoArray.concat(item),[])

.sort((a,b)=>b.data.getTime()-a.data.getTime());

}catch(err){console.log(err);

thrownewError('Nãofoipossívelobterasnegociaçõesdoperíodo')

};}

Porfim,vamosvoltarparaaclasseNegociacaoControllereadequarométodoimportaNegociacoes():

//client/app-src/controllers/NegociacaoController.js//códigoanterioromitido

18.3REFATORANDOOPROJETOCOMASYNC/WAIT 395

Page 419: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

asyncimportaNegociacoes(){

try{constnegociacoes=awaitthis._service.obtemNegociacoesD

oPeriodo();console.log(negociacoes);negociacoes.filter(novaNegociacao=>

!this._negociacoes.paraArray().some(negociacaoExistente=>

novaNegociacao.equals(negociacaoExistente)))

.forEach(negociacao=>this._negociacoes.adiciona(negociacao));

this._mensagem.texto='Negociaçõesdoperíodoimportadascomsucesso';

}catch(err){

this._mensagem.texto=err.message;}

}

Eparacompletar,vamosalterarDaoFactory.js:

//client/app-src/util/DaoFactory.jsimport{ConnectionFactory}from'./ConnectionFactory.js';import{NegociacaoDao}from'../domain/negociacao/NegociacaoDao.js';

exportasyncfunctiongetNegociacaoDao(){

letconn=awaitConnectionFactory.getConnection();returnnewNegociacaoDao(conn);}

Casotenhamoscometidoalgumerro,oprópriocompiladordoBabelnosindicaráondeerramos.Bastaolharmosamensagemdeerroapresentadanoconsole.

A sintaxe async/await , como vimos, é um recurso doES2017 (ES8). É suportado a partir do Chrome 55, tanto para

396 18.3REFATORANDOOPROJETOCOMASYNC/WAIT

Page 420: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

desktop quanto para Android, do Opera 42, do Firefox 52 e doEdge15.Todavia,comoauxíliodoBabel,podemostranscompilarnosso código para ES2015 (ES6), garantindo assim uma maiorfolgaemtermosdecompatibilidadeentrenavegadores.

Para que possamos transcompilar nosso código de ES2017(ES8)paraES2015 (ES6),precisamos instalarumpreset doBabelcomestafinalidade.

Dentro da pasta jscangaceiro/client, vamos executar ocomando:

[email protected]

Estamos quase lá! Precisamos agora incluir o preset queacabamosdebaixarnalistadepresetsusadospeloBabelduranteoprocesso de transcompilação. Alterandojscangaceiro/.babelrc:

{"presets":["es2017"],"plugins":["transform-es2015-modules-systemjs"]

}

Quando executarmoso comandonpmrunwatch ou npmrunbuildnossopreset entraráemaçãoe realizaráa conversãodeasync/awaitparaousodePromise.

Por exemplo, se pegarmos uma parte do código deDaoFactory.js antes e depois da compilação, vemos uma

mudançaclaraemsuaestrutura:

18.4 GARANTINDO A COMPATIBILIDADECOMES2015

18.4GARANTINDOACOMPATIBILIDADECOMES2015 397

Page 421: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Antes:

//client/app/util/DaoFactory.js//códigoanterioromitido

asyncfunctiongetNegociacaoDao(){

letconn=awaitConnectionFactory.getConnection();returnnewNegociacaoDao(conn);

}

//códigoposterioromitido

Depois,comopreset:

//client/app/util/DaoFactory.js//códigoanterioromitido

functiongetNegociacaoDao(){return_asyncToGenerator(function*(){

letconn=yieldConnectionFactory.getConnection();returnnewNegociacaoDao(conn);})();

}

//códigoposterioromitido

É bom prevenir de todos os lados, como um verdadeirocangaceirofaz!

Agoraque temosuma formaunificadade lidarcomexceçõesatravésdetry/catch,podemosconvencionarque todaexceçãodo tipo ApplicationException é de negócio e será exibidadiretamente para o usuário. As demais exceções serão logadas,paraqueodesenvolvedor investiguesuacausa,eumamensagem

18.5LIDANDOMELHORCOMEXCEÇÕES

398 18.5LIDANDOMELHORCOMEXCEÇÕES

Page 422: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

genéricaseráexibidaemseulugar.

Vamos criarmos uma função que saiba identificar o tipo daexceção. Faremos isso no próprio móduloclient/app/util/ApplicationException.js:

//client/app-src/util/ApplicationException.js

exportclassApplicationExceptionextendsError{

constructor(msg=''){

super(msg);this.name=this.constructor.name;

}}

//hackdoSystem.jsparaqueafunçãotenhaacessoàdefiniçãodaclasse

constexception=ApplicationException;

exportfunctionisApplicationException(err){

returnerrinstanceofexception||Object.getPrototypeOf(err)instanceofexception

}

Object.getPrototypeOf() faz parte da especificaçãoECMAScriptqueveiosubstituirapropriedade__proto__.Elaretornaoprototypedeobjeto.

A função que acabamos de criar verifica se o tipo ou oprototypedaexceçãosãoinstânciasdeApplicationException.O teste do prototype é importante, pois, sem ele,DataInvalidaExceptionretornariafalse.

18.5LIDANDOMELHORCOMEXCEÇÕES 399

Page 423: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Agora,criaremosmaisumafunçãoqueretornaamensagemdaexceçãoouamensagemgenérica,casoaexceçãonãosejadotipoApplicationException:

//client/app-src/util/ApplicationException.js

//códigoanterioromitido

//novafunção

exportfunctiongetExceptionMessage(err){

if(isApplicationException(err)){returnerr.message;

}else{console.log(err);return'Nãofoipossívelrealizaraoperação.';

}}

Excelente. Agora, em client/app-

src/controllers/NegociacaoController.js, importaremos afunçãogetExceptionMessage:

//client/app-src/controllers/NegociacaoController.js

import{Negociacoes,NegociacaoService,Negociacao}from'../domain/index.js';

//removeuDataInvalidaException

import{NegociacoesView,MensagemView,Mensagem,DateConverter}from'../ui/index.js';

//importougetExceptionMessage

import{getNegociacaoDao,Bind,getExceptionMessage}from'../util/index.js';

Com a função importada, vamos alterar todos os blocoscatchpara:

400 18.5LIDANDOMELHORCOMEXCEÇÕES

Page 424: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

//todososblocoscatchdevemficarassim

}catch(err){

this._mensagem.texto=getExceptionMessage(err);}

Queremos que nossa classe NegociacaoService tambémlance exceções de negócio em vez de Error , para que amensagemdealtonívelsejaexibidaparaousuário.Então,vamosalterar client/app-

src/domain/negociacao/NegociacaoService.js . Primeiro,importaremosApplicationException:

//client/app-src/domain/negociacao/NegociacaoService.js

import{HttpService}from'../../util/HttpService.js';import{Negociacao}from'./Negociacao.js';import{ApplicationException}from'../../util/ApplicationException.js';

Agora, vamos substituir todas as instruções new Error(/*mensagem*/) pornew ApplicationException(/* mensagem*/).

Comasalteraçõesque fizemos,qualquerexceção lançadaporNegociacaoService será capturada no bloco catch emNegociacaoController , e sua mensagem de alto nível será

exibidaparaousuário.

NossaaplicaçãoacessaumaAPIexternaatravésderequisiçãoAjax,assíncronapornatureza.Contudo,éumaboapráticaevitar

18.6 DEBOUNCE PATTERN: CONTROLANDOAANSIEDADE

18.6DEBOUNCEPATTERN:CONTROLANDOAANSIEDADE 401

Page 425: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

que a API seja "metralhada" por uma sucessão de requisiçõesindevidas.Nuncasabemossenossosusuáriossãoansiosos,não?

Nocasodanossaaplicação,nadanosimpededeclicarmos100vezes no botão "Importar Negociações" que realizará 100requisições para a API, apesar de não permitirmos maisimportações duplicadas. Podemos solucionar o problema queacabamosdeverpostergandoaaçãoquedesejamosexecutar.

Se,duranteumajaneladetempo,amesmaaçãoforexecutada,aanteriorserácanceladaepassaráavaleraúltima.Dessaforma,seo usuário clicar 100 vezes no botão"Importar Negociações"em uma janela de tempo de 1 segundo, apenas o último cliqueefetivamenteexecutaráaação.

Asoluçãoqueacabamosdeveré tãocorriqueiranouniversoJavaScript que se tornou um padrão, inclusive ganhou o nomeDebounce.Vamosimplementá-la!

Criaremos omóduloclient/app-src/util/Debounce.ts.O módulo exportará a função debounce que receberá comoparâmetroafunção alvo e a janelade tempo emmilissegundos.Seu retorno será uma nova função, que encapsula a funçãooriginal que desejamos aplicar Debounce através de umtemporizador. É esta nova função que associaremos ao eventoclickdobotãoresponsávelpelaimportaçãodenegociações:

//client/app-src/util/Debounce.ts

exportfunctiondebounce(fn,milissegundos){

18.7 IMPLEMENTANDO O DEBOUNCEPATTERN

402 18.7IMPLEMENTANDOODEBOUNCEPATTERN

Page 426: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

return()=>{

//usaumtemporizadorparachamarfn()//depoisdetantosmilissegundossetTimeout(()=>fn(),milissegundos);

}}

Não podemos nos esquecer de exportarmos Debounce.jsatravésdobarrelclient/app-src/util/index.js.Alterandooarquivo:

//client/app-src/util/index.js

export*from'./Bind.js';export*from'./ConnectionFactory.js';export*from'./DaoFactory.js';export*from'./ApplicationException.js';export*from'./HttpService.js';export*from'./ProxyFactory.js';

//adicionouaexportação!export*from'./Debounce.js';

Sabemos que nossa função não está pronta, mas vamosimportá-la em client/app-src/app.ts e utilizá-la para oeventoclickdobotãoqueimportaasnegociações:

//client/app-src/app.ts

import{NegociacaoController}from'./controllers/NegociacaoController.js';

//importandoafunção

import{debounce}from'./util/index.js';

constcontroller=newNegociacaoController();

//códigoanterioromitido

18.7IMPLEMENTANDOODEBOUNCEPATTERN 403

Page 427: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

$('#botao-importa').addEventListener('click',debounce(()=>

controller.importaNegociacoes.bind(controller),1000));

Em vez de usarmoscontroller.importaNegociacoes.bind(controller) , vamos

passar uma função que chamarácontroller.importaNegociacoes().Issonospermitirácolocaralgumas informações no console para nos ajudar a entender ocomportamentodonossocódigo.

//client/app-src/app.ts

import{NegociacaoController}from'./controllers/NegociacaoController.js';import{debounce}from'./util/index.js';

constcontroller=newNegociacaoController();

//códigoanterioromitido

$('#botao-importa').addEventListener('click',debounce(()=>{

console.log('EXECUTOUAOPERAÇÃODODEBOUNCE');controller.importaNegociacoes();

},1000));

Do jeito que está, se clicarmos 100 vezes em menos de umsegundo, cada ação será executada após um segundo. Ocomportamentodesejadoéqueapenasumaaçãosejaexecutada.

Agora, sóprecisamos lidar como cancelamentodas açõesnajaneladeumsegundo.Todosasaçõesqueforemexecutadascomoclique do botão precisam referenciar o mesmo timer para quepossampararoanteriorecriarumnovo.Paraisso,vamosdeclararavariáveltimernoescopodedebounce:

//client/app-src/util/Debounce.ts

404 18.7IMPLEMENTANDOODEBOUNCEPATTERN

Page 428: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

exportfunctiondebounce(fn,milissegundos){

//guardaoIDdeumtimer,0indicaquenãohánenhumlettimer=0;

return()=>{setTimeout(()=>fn(),milissegundos);

}}

Afunçãoretornadapordebounceéaquelaqueserádisparadapeloeventoclickdobotão.Sendoassim,aprimeiracoisaqueprecisamos fazer é cancelar qualquer timer que já esteja emexecução,paraemseguidadefinirmosumnovotimer:

//client/app-src/util/Debounce.ts

exportfunctiondebounce(fn,milissegundos){

lettimer=0;return()=>{

//paraoúltimotimerdefinidoclearTimeout(timer);

//avariáveltimerganhaoIDdeumnovotemporizador//afetaavariávelnoescopodafunçãodebouncetimer=setTimeout(()=>fn(),milissegundos);

}}

Vamos recapitular. Quando debounce for invocado,retornaráumanovafunção.Éessanovafunçãoqueseráassociadaao evento "click" do botão. Quando o evento for disparado, afunção parará qualquer timer que esteja em andamento e criaráumnovotimer.

Senenhumaoutraaçãoforempreendidanajaneladetempodeum segundo, a função passada como primeiro parâmetro de

18.7IMPLEMENTANDOODEBOUNCEPATTERN 405

Page 429: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

debounce será executada. Porém, se outro clique for realizadoantesda janeladeumsegundo terminar,o temporizadorvigenteserácanceladoeumnovotomaráoseulugar.

Porfim,podemosutilizarafunçãodebounce paraqualquereventoemnossaaplicaçãoquepreciseexecutarapenasumaaçãodentrodeumajaneladetempo.

O código completo deste capítulo você encontra emhttps://github.com/flaviohenriquealmeida/cangaceiro-javascript/tree/master/18

406 18.7IMPLEMENTANDOODEBOUNCEPATTERN

Page 430: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

CAPÍTULO19

"Ser ninja não é mais suficiente, é preciso ser cangaceiro" -FlávioAlmeida

Nocapítuloanterior,implementamosopadrãoDebounceparagarantir que a chamada do método importaNegociacoes deNegociacaoController fosse feita apenas uma vez dentro da

janeladetempodeumsegundo:

//app-src/app.js//códigoanterioromitido

//RELEMBRANDOAPENAS!

$('#botao-importa').addEventListener('click',debounce(()=>{

console.log('EXECUTOUAOPERAÇÃODODEBOUNCE');controller.importaNegociacoes();

},1000));

IsolamoselegantementealógicadopatternDebounceemumafunçãoparaquefossepossívelreaproveitá-laemoutroseventosdanossa aplicação. Contudo, se quisermos utilizar nossoNegociacaoControlleremoutrosprojetos,teremosdelembrarde realizar o debounce para qualquer evento que chame o

CHEGANDOAOLIMITE

19CHEGANDOAOLIMITE 407

Page 431: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

método.

Umapossívelsoluçãoseriaadicionarachamadadedebouncediretamente no método importaNegociacoes , para termoscertezadequeelasejaaplicadaindependentedoeventonoqualométodo é chamado. Entretanto, isso poluiria ométodo com umcódigo que não lhe diz respeito, dificultando sua legibilidade.Apesardenãotermosumasoluçãoaparente,podemosconsegui-laatravésdopadrãodeprojetoDecorator.

Decorator é um padrão de projeto de software que permiteadicionarumcomportamentoaumobjeto jáexistenteemtempode execução, ou seja, agrega dinamicamente responsabilidadesadicionaisaumobjeto.

Indo direto ao ponto, queremos o mesmo resultado que játemoscomapenasestaalteraçãoemnossaaplicação:

//CÓDIGONÃOCOMPILA,APENASEXEMPLO

//app-src/controllers/NegociacaoController.js

//códigoanterioromitido

//DECORANDOOMÉTODO

@debounce()asyncimportaNegociacoes(){

//códigoomitido

}//códigoposterioromitido

Comasintaxequeacabamosdever,aaplicaçãododebounce

19.1OPADRÃODEPROJETODECORATOR

408 19.1OPADRÃODEPROJETODECORATOR

Page 432: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

através de um Decorator é muito menos invasiva, pois nãoprecisamosmisturara lógicadesuachamadadentrodoblocodométodo. Além disso, independente do lugar que o métodoimportaNegociacoesforchamado,oDecoratorseráaplicado.

Apesar de muito elegante, não há suporte oficial para esterecurso no ECMAScript. Há uma proposta em andamento paraque Decorators se tornem padrão na linguagem(https://github.com/tc39/proposal-decorators). Vamos desistir deusá-lo?Comcerteza,não.

Uma das grandes vantagens de utilizarmos umtranscompiladoréacapacidadedeusarmosrecursosmodernosdalinguagemantesmesmoquesetornemoficiais,ouantesmesmodesua implementação nos navegadores. Como já estamos usandoBabel, basta adicionarmos umplugin para termos suporte a essefantásticorecurso.

Nãofiquecomreceiodeutilizaresterecursoexperimental.Porexemplo,alinguagemTypeScript,umsupersetdoECMAScript2015 criado pelaMicrosoft, também faz uso experimental deDecorators, que são suportados através de um processo detranscompilação fornecido pelo próprio compilador dalinguagem.

Semdelongas, vamos ativar essapoderosa funcionalidade emnossoprojeto.

19.1OPADRÃODEPROJETODECORATOR 409

Page 433: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Noterminal,dentrodapastaclient,vamosinstalaropluginbabel-plugin-transform-decorators-legacy.

npminstallbabel-plugin-transform-decorators-legacy@1.3.4--save-dev

Emseguida,noarquivoclient/.babelrc,adicionaremosopluginnalistadeplugins:

{"presets":["es2017"],

"plugins":["transform-es2015-modules-systemjs","transform-decorators-legacy"]}

Vamosalterarapp-src/app.jspararemovermosachamadadanossafunçãodebounce,quesetornaráumDecorator.Nossocódigovoltaráparaoestágioanterior,antesdousodafunção:

//app-src/app.js

import{NegociacaoController}from'./controllers/NegociacaoController.js';

//NÃOIMPORTAMAISDEBOUNCE

//códigoanterioromitido

//voltouparaaformaanterior$('#botao-importa').addEventListener('click',controller.importaNegociacoes.bind(controller));

Antes de continuarmos, há uma pegadinha noVisual StudioCode,casovocêoestejautilizando.Quandoeleencontrarasintaxe@NomeDoDeCorator,exibiráaseguintemensagemdeerro:

19.2 SUPORTANDO DECORATOR ATRAVÉSDOBABEL

410 19.2SUPORTANDODECORATORATRAVÉSDOBABEL

Page 434: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

[js]Experimentalsupportfordecoratorsisafeaturethatissubjecttochangeinafuturerelease.Setthe'experimentalDecorators'optiontoremovethiswarning.

Para silenciarmos o Visual Studio Code, basta criarmos oarquivoclient/jsconfig.jsoncomoseguinteconteúdo:

{"compilerOptions":{

"experimentalDecorators":true}

}

ÉmuitoimportantequeapastaclientsejaabertanoVisualStudioCodeparaqueeleencontreoarquivojsconfig.json.Seabrirmos diretamente client/app-src , o arquivo não seráencontradoeamensagemdeerrocontinuaráaserexibida.

Agoraquejátemostudonolugar,daremosinícioàcriaçãodonosso Decorator. Aliás, podemos apagar o arquivo app-

src/util/Debounce.js , inclusive remover sua exportação deapp-src/util/index.js,quenofinalficaráassim:

//app-src/util/index.js

export*from'./Bind.js';export*from'./ConnectionFactory.js';export*from'./DaoFactory.js';export*from'./ApplicationException.js';export*from'./HttpService.js';export*from'./ProxyFactory.js';

//REMOVEUOEXPORTDOARQUIVOQUEACABAMOSDEAPAGAR

Vamos criar o arquivo app-

src/util/decorators/Debounce.js.Seuesqueletoseráassim:

//app-src/util/decorators/Debounce.js

exportfunctiondebounce(milissegundos=500){

19.2SUPORTANDODECORATORATRAVÉSDOBABEL 411

Page 435: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

returnfunction(target,key,descriptor){

returndescriptor;}

}

Temosafunçãodebouncequerecebecomoparâmetroumaquantidade em milissegundos, o tempo que levará emconsideração.Senãorecebervaloralgum,seuvalorpadrãoserádemeiosegundograçasaodefaultparametersuportadopeloES2015(ES6).

TodafunçãoDecoratordeveretornaroutrafunçãoquerecebetrês parâmetros. O primeiro é o target , o alvo do nossoDecorator; e o segundo é o nome da propriedade na qual oDecoratorfoiutilizado.Emnossocaso,comovamosusá-loparaométodoimportaNegociacoes, o parâmetro key guardará onomedométodo.Porfim,temosodescriptor,quemereceumdestaqueespecial.

O descriptor é um objeto especial que nos dá acesso àimplementação original do método ou função, por meio dedescriptor.value. É importante queo retornoda funçãoque

recebe os três parâmetros que vimos sempre devolva odescriptor,modificadoounão.

Oprimeiropassoseráguardarumareferênciaparaométodooriginal,antesdeelesermodificado:

exportfunctiondebounce(milissegundos=500){

returnfunction(target,key,descriptor){

constmetodoOriginal=descriptor.value;

412 19.2SUPORTANDODECORATORATRAVÉSDOBABEL

Page 436: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

returndescriptor;}

}

É importante guardar o método original, porque, aomodificarmos descriptor.value para aplicar o debounce ,precisaremosqueométodooriginalsejachamado.

Agora, vamos sobrescrever odescriptor.value com umanovafunção.Comonãosabemosseométododecoradopossuiounãoparâmetros,usaremosoRESToperatorparaquenossonovométodo esteja preparado para receber nenhum ou maisparâmetros:

//app-src/util/decorators/Debounce.js

exportfunctiondebounce(milissegundos=500){

returnfunction(target,key,descriptor){

constmetodoOriginal=descriptor.value;

lettimer=0;descriptor.value=function(...args){

clearTimeout(timer);//aquientraaimplementaçãodonossométodo//quesubstituiráooriginal

}

returndescriptor;}

}

Através de metodoOriginal.apply(this, args) ,chamamos o método original passando seu contexto e aquantidadedeargumentosrecebidapelométodoqueosubstituiu:

//app-src/util/decorators/Debounce.js

19.2SUPORTANDODECORATORATRAVÉSDOBABEL 413

Page 437: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

exportfunctiondebounce(milissegundos=500){

returnfunction(target,key,descriptor){

constmetodoOriginal=descriptor.value;

lettimer=0;

descriptor.value=function(...args){clearInterval(timer);

//chamametodoOriginaldepoisdeXmilissegundos!timer=setTimeout(()=>metodoOriginal.apply(this,a

rgs),milissegundos);}

returndescriptor;}

}

Excelente, isso já é suficiente para que nosso Decoratorfuncione.Vamosexportá-lonobarrelapp-src/util/index.js,para em seguida importá-lo e utilizá-lo emNegociacaoController.

//app-src/util/index.js

export*from'./Bind.js';export*from'./ConnectionFactory.js';export*from'./DaoFactory.js';export*from'./ApplicationException.js';export*from'./HttpService.js';export*from'./ProxyFactory.js';export*from'./decorators/Debounce.js';

Agora,alterandoNegociacaoController:

import{Negociacoes,NegociacaoService,Negociacao}from'../domain/index.js';import{NegociacoesView,MensagemView,Mensagem,DateConverter}from'../ui/index.js';

414 19.2SUPORTANDODECORATORATRAVÉSDOBABEL

Page 438: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

/importouodecoratorimport{getNegociacaoDao,Bind,getMessageException,debounce}from'../util/index.js';

exportclassNegociacaoController{

//códigoanterioromitido

@debounce()asyncimportaNegociacoes(){

//códigoomitido}

}

Podemos testar a aplicação. Inclusive podemos passar aquantidadeemmilissegundosquedesejamosparao@debounce,queelaserárespeitada:

//EXEMPLO:esperaumsegundoemeio

@debounce(1500)asyncimportaNegociacoes(){

//códigoomitido}

}

Que tal aplicarmos a solução também para o métodoadiciona() deNegociacaoController? Tudo bemque não

temosumasituaçãonaqualodebouncefaçamuitosentido,masé uma maneira de podermos testar a reutilização do nossoDecorator:

//app-src/controllers/NegociacaoController.js//códigoanterioromitido

19.3 UM PROBLEMA NÃO ESPERADO COMNOSSODECORATOR

19.3UMPROBLEMANÃOESPERADOCOMNOSSODECORATOR 415

Page 439: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

@debounce()asyncadiciona(event){

//códigoomitido}

//códigoposterioromitido

Assim que recarregamos nossa página com os arquivosatualizados durante o processo de transcompilação do Babel,quando tentamos adicionar novas negociações, a página érecarregada indevidamente. É como se oevent.preventDefault() dométodoadiciona() não fosse

chamado.Oqueestáacontecendo?

Comoométodooriginalésubstituídoporumnovo,estenovométodo não realiza o event.preventDefault() . Como ométododecorado só será chamadodepois deXmilissegundos, oeventosubmitnãoentendequedevecancelarseueventopadrão,o da submissão do formulário, então a página recarregará.Podemos resolver isso facilmente alterando nosso código daseguinteforma:

exportfunctiondebounce(milissegundos=500){

returnfunction(target,key,descriptor){

constmetodoOriginal=descriptor.value;

lettimer=0;

descriptor.value=function(...args){

//MUDANÇA!if(event)event.preventDefault();

clearInterval(timer);timer=setTimeout(()=>metodoOriginal.apply(this,a

rgs),milissegundos);

416 19.3UMPROBLEMANÃOESPERADOCOMNOSSODECORATOR

Page 440: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

}

returndescriptor;}

}

Podemostestarseavariável implícitaevent estádisponíveldurante a chamada do método. Se estiver, chamamosevent.preventDefault().

Umnovo teste indicaquenossa aplicaçãovoltoua funcionarcomo esperado. Mas será que podemos abusar mais umpouquinhodesterecurso?Éoqueveremosnapróximaseção.

Emnossa classeNegociacaoController, buscamos as tagsinput do formulário através da funçãoquerySelector, que

recebeoselectorCSSdoselementos.Veja:

//REVISANDOAPENASOCÓDIGO

//app-src/controllers/NegociacaoController.js

//códigoanterioromitido

exportclassNegociacaoController{

constructor(){

const$=document.querySelector.bind(document);this._inputData=$('#data');this._inputQuantidade=$('#quantidade');this._inputValor=$('#valor');

//códigoposterioromitido

Mas que tal criarmos um Decorator que saiba injetar as

19.4ELABORANDOUMDOMINJECTOR

19.4ELABORANDOUMDOMINJECTOR 417

Page 441: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

dependênciasdoselementosdoDOMnainstânciadenossaclasse?Queremosalgocomo:

//EXEMPLOAPENAS

@controller('#data','#quantidade','#valor')exportclassNegociacaoController{

constructor(inputData,inputQuantidade,inputValor){

this._inputData=inputData;this._inputQuantidade=inputQuantidade;this._inputValor=inputValor;

Neste exemplo, o Decorator @controller recebe comoparâmetro os seletores CSS dos elementos que desejamos passarparaonovoconstructordaclasse,namesmaordem.

Agora que já aprendemos a criar Decorators de métodos,chegouahoradeaprendermosacriarDecoratorsdeclasses.

Vamos criar nosso Decorator de classe em app-

src/util/decorators/Controller.js , com a seguinteestrutura:

//app-src/util/decorators/Controller.js

exportfunctioncontroller(...seletores){

returnfunction(constructor){

}}

Nosso Decorator recebe como parâmetro uma quantidade

19.5DECORATORDECLASSE

418 19.5DECORATORDECLASSE

Page 442: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

indeterminadadeparâmetros,por issousamosoRESTOperator.Esses parâmetros serão os seletores CSS dos elementos quedesejamosbuscar.

Assim como um Decorator de método, ele devolve umafunção. Mas desta vez, ao ser chamada, ela nos dá acesso aoconstructordaclassedecorada.Épormeiodessareferênciadoconstructor da classequepodemos criar instânciasdentrodo

nosso Decorator, inclusive passar para essas instâncias osparâmetrosquedesejarmos.

Precisamos converter a lista de seletores em uma lista deelementosdoDOM.Paraisso,usaremosafunçãomapquetodoarraypossui:

//app-src/util/decorators/Controller.js

exportfunctioncontroller(...seletores){

//listacomoselementosdoDOM

constelements=seletores.map(seletor=>document.querySelector(seletor));

returnfunction(constructor){

}}

Já temos um array com todos os elementos do DOM queprecisamos passar para o constructor da classe decorada.Agora, vamos guardar uma referência para o constructororiginal da classe, para então começarmos a esboçar um novoconstructor:

//app-src/util/decorators/Controller.js

19.5DECORATORDECLASSE 419

Page 443: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

exportfunctioncontroller(...seletores){

constelements=seletores.map(seletor=>document.querySelector(seletor));

returnfunction(constructor){

constconstructorOriginal=constructor;

constconstructorNovo=function(){

}

}}

OconstructorNovodeveráchamarconstructorOriginalpassando todos os parâmetros que ele necessita. Um ponto adestacar é que usamos function e não arrow function paradefinironovoconstructor.Issonãofoiporacaso,poisothisdoconstructordeveserdinâmicoenãoestático.Setivéssemosusadoarrowfunctionlánafrente,receberíamosumerro.

Vamos continuar a implementar o novo constructor. AinstânciacriadapelachamadadeconstructorOriginaldeveserretornada:

//app-src/util/decorators/Controller.js

exportfunctioncontroller(...seletores){

constelements=seletores.map(seletor=>document.querySelector(seletor));

returnfunction(constructor){

constconstructorOriginal=constructor;

constconstructorNovo=function(){

returnnewconstructorOriginal(...elements);

420 19.5DECORATORDECLASSE

Page 444: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

}}

}

Isso aindanão é suficiente.OconstructorNovo deve ter omesmoprototypedeconstructorOriginal. Isso é importante,poisaclassequedecoramospodeterherdadodeoutraclasse:

//app-src/util/decorators/Controller.js

exportfunctioncontroller(...seletores){

constelements=seletores.map(seletor=>document.querySelector(seletor));

returnfunction(constructor){

constconstructorOriginal=constructor;

constconstructorNovo=function(){

returnnewconstructorOriginal(...elements);}

//ajustandooprototypeconstructorNovo.prototype=constructorOriginal.prototype

;

//retornandoonovoconstructorreturnconstructorNovo;

}}

Terminamos nosso Decorator. Só precisamos exportá-loatravésdobarrildapastaapp-src/util,paraentãoimportá-loeutilizá-loemNegociacaoController.

Vamosalterarobarril:

//app-src/util/index.js

export*from'./Bind.js';

19.5DECORATORDECLASSE 421

Page 445: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

export*from'./ConnectionFactory.js';export*from'./DaoFactory.js';export*from'./ApplicationException.js';export*from'./HttpService.js';export*from'./ProxyFactory.js';export*from'./decorators/Debounce.js';

//exportou!export*from'./decorators/Controller.js';

Agoraocontroller:

//app-src/controller/NegociacaoController.js

//códigoanterioromitido

//IMPORTOUODECORATOR

import{getNegociacaoDao,Bind,getMessageException,debounce,controller}from'../util/index.js';

//UTILIZANDOODECORATOR

@controller('#data','#quantidade','#valor')exportclassNegociacaoController{

//MÉTODOAGORARECEBETRÊSPARÂMETROSconstructor(inputData,inputQuantidade,inputValor){

this._inputData=inputData;this._inputQuantidade=inputQuantidade;this._inputValor=inputValor;

//códigoposterioromitido}

//códigoposterioromitido

Basta recarregarmos nossa página para vermos nossoDecoratoremação.NossocódigoéapenasumaamostradoqueoDecorator de classes pode fazer. Todavia, um olhar atentoperceberáquepodemossimplificaraindamaisnossocódigo.

422 19.5DECORATORDECLASSE

Page 446: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Como o constructor de NegociacaoController agorarecebe três parâmetros, podemos usar a mesma estratégia comObject.assign que utilizamos na classe Negociacao para

reduziraquantidadedeinstruções:

//app-src/controller/NegociacaoController.js

//códigoanterioromitido

import{getNegociacaoDao,Bind,getMessageException,debounce,controller}from'../util/index.js';

@controller('#data','#quantidade','#valor')exportclassNegociacaoController{

//OSPARÂMETROSAGORACOMEÇAMCOMUNDERLINE

constructor(_inputData,_inputQuantidade,_inputValor){

//UMAÚNICAINSTRUÇÃO

Object.assign(this,{_inputData,_inputQuantidade,_inputValor})

this._negociacoes=newBind(newNegociacoes(),newNegociacoesView('#negociacoes'),'adiciona','esvazia'

);

this._mensagem=newBind(newMensagem(),newMensagemView('#mensagemView'),'texto'

);

this._service=newNegociacaoService();

this._init();}

//códigoposterioromitido

19.5DECORATORDECLASSE 423

Page 447: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Angular (https://angular.io/) é um framework que faz usointenso de decorators, inclusive possui um sistema bemavançado de injeção de dependências. O código queescrevemoséumacaricaturadoqueestepoderosoframeworkpodenosoferecer.

Excelente.Commaisestaetapaconcluída,podemoscontinuarcomnossassimplificaçõesnapróximaseção.

Estamos a um passo de concluirmos nossa aplicação, maspodemos realizar mais uma melhoria que facilitará em muito avidadodesenvolvedorquandooassuntoérequisiçõesassíncronas.Primeiro, vamos rever a classeHttpService que criamos paraisolarmosoobjetoXmlHttpRequest:

//app-src/util/HttpService.js

exportclassHttpService{

get(url){

returnnewPromise((resolve,reject)=>{

constxhr=newXMLHttpRequest();

xhr.open('GET',url);

xhr.onreadystatechange=()=>{

if(xhr.readyState==4){

19.6 SIMPLIFICANDO REQUISIÇÕES AJAXCOMAAPIFETCH

424 19.6SIMPLIFICANDOREQUISIÇÕESAJAXCOMAAPIFETCH

Page 448: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

if(xhr.status==200){

resolve(JSON.parse(xhr.responseText));}else{

console.log(xhr.responseText);reject(xhr.responseText);

}}

};

xhr.send();

});}

}

UtilizamosopadrãodeprojetoPromiseparafacilitarousodométodoget()emtodososlugaresqueessetipoderequisiçãoénecessário. Contudo, podemos usar um recurso do próprioJavaScript para realizar requisições Ajax que já suporta Promiseporpadrão,aAPIFetch.

AAPIfetchéexperimental,massuportadaapartirdoChrome42,Firefox39,InternetExplorer11,Edge38,Opera29eSafari10.Elasimplificadrasticamenteocódigopararealizarmosrequisiçõesassíncronas,comumentechamadasderequisiçõesAjaxnomundoJavaScript.

AAPI Fetch émais um recurso experimental bastante usadono mundo JavaScript. Não se preocupe por enquanto comquestões de compatibilidade, mas tenha certeza de estarusandoumnavegadorqueasuportenestemomento.

19.6SIMPLIFICANDOREQUISIÇÕESAJAXCOMAAPIFETCH 425

Page 449: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Parapodervermosnapráticasuasimplicidade,vamosapagaroconteúdodométodoget()deHttpServicepara reescrevê-lonovamentecomaAPIFetch.

AcessamosaAPIatravésdafunçãoglobalfetchquerecebecomoparâmetroaURLquedesejamosrealizarumarequisição:

//app-src/util/HttpService.js

exportclassHttpService{

get(url){

returnfetch(url).then(res=>console.log(res));

}}

A função fetch retorna uma Promise, e é por isso queobtemos seu resultado através da chamada encadeada à funçãothen.Contudo,arespostanãoéparseadaautomaticamenteparanós,mas tambémnão será necessário usarmosJSON.parse(),comofizemoscomarespostadeXMLHttpRequest.

Noexemploanterior,resnadamaisédoqueumobjetoqueencapsula a resposta.Atravésdosmétodostext() ejson(),lidamos com a resposta no formato texto ou como objetos noformatoJSON,respectivamente.

Nocaso, temos interesseem lidarcoma respostano formatoJSON:

//app-src/util/HttpService.js

exportclassHttpService{

//quemchamaométodoobtémosdadosatravésde.then()get(url){

426 19.6SIMPLIFICANDOREQUISIÇÕESAJAXCOMAAPIFETCH

Page 450: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

returnfetch(url).then(res=>res.json());

}}

Comoestamosusandoarrowfunctionsembloco,oresultadode res.json() é retornado automaticamente para a próximachamada,encadeadaàfunçãothen().

Apesar de muito mais simples do que a versão comPromise/XMLHttpRequest, ainda precisamos lidar com possíveiserros durante a operação. Isso pode ser feito por quem chama ométodo get() bastando encadear uma chamada à funçãocatch().Vejamosumexemplodeuso:

//EXEMPLODEUMAPENAS

letservice=newHttpService();service

.get('http://endereco-de-uma-api').then(dados=>console.log(dados)).catch(err=>console.log(err));

Porém,háumapegadinhacomaAPIFetch.Afunçãocatchsó será chamada apenas se rejeitarmos a Promise. Para quepossamosfazerisso,precisamossabersearequisiçãofoirealizadacomsucessoounão,combaseemres.ok.

AlterandoocódigodeHttpService:

exportclassHttpService{

_handleErrors(res){

//senãoestiverok,lançaaexceçãoif(!res.ok)thrownewError(res.statusText);

//senenhumexceçãofoilançada,retornaaprópriarespo

19.6SIMPLIFICANDOREQUISIÇÕESAJAXCOMAAPIFETCH 427

Page 451: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

stareturnres;

}

get(url){

returnfetch(url).then(res=>this._handleErrors(res)).then(res=>res.json());

}}

Para manter a organização do código, criamos o métodoprivado_handleErrors().O.then nofetch devolverá aprópria requisição this._handleErrors que será acessível nopróximo .then e será convertido para json. Com o if ,identificamos se tudo funcionou bem com o res.ok ; casocontrário,cairemosnoelseelançaremosumaexceção.Quandoa exceção for lançada, a Promise não vai para o .then doget(),masseguiráparaocatch.

Se atualizarmos a página no navegador, em alguns instantesreceberemosamensagemdequeasnegociaçõesdoperíodoforamimportadascorretamente.

Por padrão, fetch executa uma requisição com o métodoGET.EsequisermosenviarumJSONatravésdométodoPOST

paraumaAPI?Nessecaso,precisaremosconfigurararequisição.

Aboanotíciaéquenossoservidorestápreparadoparareceberuma requisição POST que envia um JSON para o endereço/negociacoes. O servidor apenas exibirá no console os dados

19.7 CONFIGURANDO UMA REQUISIÇÃOCOMAPIFETCH

428 19.7CONFIGURANDOUMAREQUISIÇÃOCOMAPIFETCH

Page 452: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

recebidos.

O código de teste que escreveremos ficará no arquivoclient/app-src/app.js . Assim que nossa aplicação for

carregada, uma requisição será feita para o servidor. Poderíamostercriadooutroarquivo,masissonospouparátempo.

O primeiro passo será importar Negociacao de./domain/index.js , e preparar o cabeçalho da requisição,

indicandoqueoContent-Typeseráapplication/json:

//client/app-src/app.js

import{NegociacaoController}from'./controllers/NegociacaoController.js';

//importouNegociacao!import{Negociacao}from'./domain/index.js';

//códigoanterioromitido

constnegociacao=newNegociacao(newDate(),1,200);constcabecalhos=newHeaders();cabecalhos.set('Content-Type','application/json');

ÉpormeiodeumainstânciadeHeadersqueconfiguramosnossocabeçalho.Oobjetopossuiométodoset. É atravésdelequeindicamosoContent-Typequeutilizaremos.

Opróximopassoserácriarmosumobjetoquechamaremosdeconfig.Elepossuiaspropriedadesmethod,headersebodyque recebem respectivamenteos valoresPOST,cabecalhos eJSON.stringify(negociacao).

//client/app-src/app.js

import{NegociacaoController}from'./controllers/NegociacaoController.js';

19.7CONFIGURANDOUMAREQUISIÇÃOCOMAPIFETCH 429

Page 453: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

//importouNegociacao!import{Negociacao}from'./domain/index.js';

//códigoanterioromitido

constnegociacao=newNegociacao(newDate(),1,200);constcabecalhos=newHeaders();cabecalhos.set('Content-Type','application/json');

constconfig={method:'POST',headers:cabecalhos,body:JSON.stringify(negociacao)

};

Com a configuração necessária, executamos nossa requisiçãopormeiodefetch:

//client/app-src/app.js

import{NegociacaoController}from'./controllers/NegociacaoController.js';

//importouNegociacao!import{Negociacao}from'./domain/index.js';

//códigoanterioromitido

constnegociacao=newNegociacao(newDate(),1,200);constcabecalhos=newHeaders();cabecalhos.set('Content-Type','application/json');

constconfig={method:'POST',headers:cabecalhos,body:JSON.stringify(negociacao)

};

fetch('/negociacoes',config).then(()=>console.log('Dadoenviadocomsucesso'));

Reparem que fetch recebe como segundo parâmetro o

430 19.7CONFIGURANDOUMAREQUISIÇÃOCOMAPIFETCH

Page 454: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

objeto com todas as configurações que fizemos. Recarregandonossa página, ao olharmos para o terminal que roda nossoservidor,vemososdadosdanegociaçãoqueenviamos.

Servidorescutandonaporta:3000DadorecebidoviaPOST:{_data:2017-06-06T20:13:02.799Z,_quantidade:1,_valor:200}

Podemossimplificarumpoucomaisocódigoqueescrevemosutilizandooatalhoparadeclaraçãodeobjetosliteraisquejávimosnestelivro.

Vamos mudar o nome da constante cabecalhos paraheaders , e também vamos guardar o resultado de

JSON.stringify(negociacao)naconstantebodyeométodona constante method. Agora, a declaração do objeto configficarámaissimples:

//client/app-src/app.js

import{NegociacaoController}from'./controllers/NegociacaoController.js';import{Negociacao}from'./domain/index.js';

//códigoanterioromitido

constnegociacao=newNegociacao(newDate(),1,200);//mudouparaheaders!constheaders=newHeaders();headers.set('Content-Type','application/json');

//novaconstanteconstbody=JSON.stringify(negociacao);

//novaconstanteconstmethod='POST';

constconfig={method,headers,

19.7CONFIGURANDOUMAREQUISIÇÃOCOMAPIFETCH 431

Page 455: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

body};

fetch('/negociacoes',config).then(()=>console.log('Dadoenviadocomsucesso'));

Éumapequenamudança,mas que deixa nosso código aindamaisenxutoelegível.

Estamos prestes a terminar nossa aplicação, porém há maisumamelhoriaquepodemosrealizarantesde finalizá-la.VejamosnossaclasseNegociacaoumaúltimavez:

//app-src/domain/negociacao/Negociacao.js

exportclassNegociacao{

constructor(_data,_quantidade,_valor){

Object.assign(this,{_quantidade,_valor});this._data=newDate(_data.getTime());Object.freeze(this);

}

//códigoposterioromitido}

Sabemosqueosparâmetros recebidos emseuconstructorsão obrigatórios, mas em nenhum momento testamos se forampassados.

Umasoluçãopossíveléaplicarmosumasériedecondiçõesifpara cada parâmetro, e então lançarmos uma exceção para essascondições:

19.8 VALIDAÇÃO COM PARÂMETRODEFAULT

432 19.8VALIDAÇÃOCOMPARÂMETRODEFAULT

Page 456: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

//app-src/domain/negociacao/Negociacao.js

exportclassNegociacao{

constructor(_data,_quantidade,_valor){

if(!_data){thrownewError('dataéumparâmetroobrigatório');

}

if(!_quantidade){thrownewError('quantidadeéumparâmetroobrigatóri

o');}

if(!_valor){thrownewError('valoréumparâmetroobrigatório');

}

Object.assign(this,{_quantidade,_valor});this._data=newDate(_data.getTime());Object.freeze(this);

}

//códigoposterioromitido}

Asoluçãoquevimos,apesardefuncional,éumtantoverbosa.Umasoluçãomaiseleganteéisolarmosaexceçãolançadaemumafunção, para em seguida chamá-la como parâmetro default .Com essa estratégia, a função será chamada caso nenhumparâmetrosejachamado.

Vamoscriaroarquivoapp-src/util/Obrigatorio.ts queexportaráafunçãoobrigatorio:

//app-src/util/Obrigatorio.js

exportfunctionobrigatorio(parametro){

thrownewError(`${parametro}éumparâmetroobrigatório`);}

19.8VALIDAÇÃOCOMPARÂMETRODEFAULT 433

Page 457: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Nãopodemosnosesquecerdeadicioná-lonobarril:

//app-src/util/index.js

export*from'./Bind.js';export*from'./ConnectionFactory.js';export*from'./DaoFactory.js';export*from'./ApplicationException.js';export*from'./HttpService.js';export*from'./ProxyFactory.js';export*from'./decorators/Debounce.js';export*from'./decorators/Controller.js';

//exportando!export*from'./Obrigatorio.js';

Agora,naclasseNegociacao,faremosassim:

//app-src/controllers/NegociacaoController.js

import{obrigatorio}from'../../util/index.js';

exportclassNegociacao{

constructor(_data=obrigatorio('data'),_quantidade=obrigatorio('quantidade'),_valor=obrigatorio('valor')){

Object.assign(this,{_quantidade,_valor});this._data=newDate(_data.getTime());Object.freeze(this);

}

Experimentealterarométodo_criaNegociacaoomitindoapassagem de um parâmetro para o constructor deNegociacao.Aexeçãoserálançadaemensagemseráexibidaparao usuário.Umasoluçãoelegantepara trataraobrigatoriedadedeparâmetros.

434 19.8VALIDAÇÃOCOMPARÂMETRODEFAULT

Page 458: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Nossa aplicação funciona com esperado, mas o arquivoclient/app-src/app.js,queinicializanossocontroller,realiza

um POST através da API Fetch e também uma associação deeventos de elementos do DOM com os métodos de nossocontroller. Realizar esse tipo de associação é algo corriqueiro nomundoJavaScript.Seráquepodemosfacilitaresseprocesso?

Umapossívelsoluçãoéisolarmosalógicadeassociaçãoemumúnico lugar, para então aproveitá-la toda vez que houver anecessidade de associarmos um método de um controller a umevento.Nessesentido,podemoscriaroDecoratorbindEventquereceberácomoparâmetrooevento(click,submitetc.),oseletordoelementodoDOM(noqualnossométodoseráassociado)eumaflag para indicarmos se queremos realizar ou nãoevent.preventDefault().

Todavia,umDecoratordemétodoéaplicadoantesdaclasseserinstanciada.Comoseremoscapazesdeassociarummétodoaumeventosemtermosumainstância?

Podemos solucionar o problema anterior utilizando oDecoratorbindEvent apenasparaadicionar informações extras(metadados) sobre ométodo no qual foi associado. Em seguida,com auxílio de um Decorator de classe, procuraremos nosmétodos da instância da classe os metadados adicionados porbindEvent. As informações contidas nesses metadados serão

usadas para fazer a associação do evento com o método dainstância.

19.9REFLECT-METADATA:AVANÇANDONAMETAPROGRAMAÇÃO

19.9REFLECT-METADATA:AVANÇANDONAMETAPROGRAMAÇÃO 435

Page 459: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Contudo, o ECMAScript ainda não possui uma maneiraespecificada para inclusão de metadados na definição de nossasclasses. Há até uma proposta (https://rbuckton.github.io/reflect-metadata/) em discussão que poderá entrar em uma futuraatualização.

A boa notícia é que não precisamos esperar o futuro parautilizarmos o recurso que precisamos. Existe o projeto reflect-metadata (https://github.com/rbuckton/reflect-metadata) quesegue a especificação proposta para a linguagem. Inclusive,frameworks comoAngular e linguagens como TypeScript fazemusodestemesmoprojeto.

Já existe uma API de reflexão no ECMAScript 2015 (ES6)acessível através do objeto global Reflect. Todavia, o reflect-metadata adiciona novas funções que permitem trabalhar commetadados.

Vamosinstalaroreflect-metadatapeloterminal,dentrodapastaclient,comocomando:

[email protected]

Precisamos importá-lo como primeiro script emclient/index.htm:

<divid="negociacoes"></div>

<scriptsrc="node_modules/reflect-metadata/Reflect.js"></script>

<scriptsrc="node_modules/systemjs/dist/system.js"></script><script>

System.import('app/app.js')

.catch(err=>console.error(err));</script>

436 19.9REFLECT-METADATA:AVANÇANDONAMETAPROGRAMAÇÃO

Page 460: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

</body>

Excelente,agora jápodemosdar inícioàconstruçãodonossoDecoratorbindEvent.

Criaremos oDecorator que adicionará ometadado quemaistardeusaremosparacompletarnossasolução.

Vamos criar o arquivo client/app-

src/util/decorators/BindEvent.js . Ele terá o seguinteesqueleto:

//client/app-src/util/decorators/BindEvent.js

import{obrigatorio}from'../../util/index.js';

exportfunctionbindEvent(event=obrigatorio('event'),selector=obrigatorio('selector'),prevent=true){

returnfunction(target,propertyKey,descriptor){

//éaquiqueprecisamosadicionarosmetadados

returndescriptor;}

}

Atéagoranãohánenhumanovidade, inclusiveusamosnossafunção obrigatorio nos dois primeiros parâmetros doDecorator. O valor padrão do último parâmetro será true, poisadotaremos como padrão o cancelamento do evento com

19.10 ADICIONANDO METADADOS COMDECORATORDEMÉTODO

19.10ADICIONANDOMETADADOSCOMDECORATORDEMÉTODO 437

Page 461: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

event.preventDefault().

Agora, através de Reflect.defineMetadata() , vamosadicionarnossometadadoquechamaremosdebindEvent:

//client/app-src/util/decorators/BindEvent.js

import{obrigatorio}from'../../util/index.js';

exportfunctionbindEvent(event=obrigatorio('event'),selector=obrigatorio('selector'),prevent=true){

returnfunction(target,propertyKey,descriptor){

Reflect.defineMetadata('bindEvent',{event,selector,prevent,propertyKey},Object.getPrototypeOf(target),propertyKey);

returndescriptor;}

}

A função Reflect.defineMetadata recebe quatroparâmetros.Oprimeiroéonomequeidentificanossometadado.O segundo é um objeto JavaScript, nosso metadado, com aspropriedadesevent,selector, prevent e propertyKeyatravésdoatalhoparadeclaraçãodeobjetosquejávimos.

Emseguida,precisamospassaroprototypedainstânciaquedesejamos adicionar o metadado, por isso usamosObject.getPrototypeOf(target) e por fim o nome da

propriedade do objeto que receberá o metadado. Como vamosutilizar@bindEvent apenasemmétodos,propertyKey será onomedométodo.

438 19.10ADICIONANDOMETADADOSCOMDECORATORDEMÉTODO

Page 462: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Vamos adicionar nosso novo Decorator ao barrelclient/util/index.js:

//client/util/index.js

export*from'./Bind.js';export*from'./ConnectionFactory.js';export*from'./DaoFactory.js';export*from'./ApplicationException.js';export*from'./HttpService.js';export*from'./ProxyFactory.js';export*from'./decorators/Debounce.js';export*from'./decorators/Controller.js';export*from'./Obrigatorio.js';

//importou!export*from'./decorators/BindEvent.js';

Sem hiato, vamos importá-lo e adicioná-lo aos métodosadiciona() , importaNegociacoes() e apaga() deNegociacaoController:

//client/app-src/controller/NegociacaoController.js

//importaçõesanterioresomitidas

//importoubindEventimport{getNegociacaoDao,Bind,getMessageException,debounce,controller,bindEvent}from'../util/index.js';

//associaaoeventosubmitdoelementocomseletor.form@bindEvent('submit','.form')@debounce()asyncadiciona(event){//códigoomitido}

//códigoomitido

//associaaoeventoclickdoelementocomseletor#botao-importa@bindEvent('click','#botao-importa')@debounce()asyncimportaNegociacoes(){//códigoomitido}

//associaaoeventoclickdoelementocomseletor#botao-importa

19.10ADICIONANDOMETADADOSCOMDECORATORDEMÉTODO 439

Page 463: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

@bindEvent('click','#botao-apaga')asyncapaga(){//códigoomitido}

//códigposterioromitido

Duranteoprocessodetranscompilação,eleseráprocessadoe,duranteessaetapa,adicionaráosmetadadosnosmétodosemquefoiusado.Contudo,precisamosdeumDecoratorquesejacapazdeacessar a instância da classe, buscar os metadados e fazer asassociaçõesdeeventos.Játemosodeclassecontroller.Vamosmodificá-loparaatendernossanecessidade.

Nossoprimeiropassoseráacessara instânciacriadae,comoauxíliodeObject.getOwnPropertyNames(), percorrer todas aspropriedadesdaclasse.Paracadapropriedade,verificamosatravésdeReflect.hasMetadata() se o metadado bindEvent estápresente. Esta função recebe como parâmetro o identificador dometadado, a instância da classe e, por último, o nome dapropriedadequeestásendoverificada:

//client/app-src/util/decorators/Controller.js

exportfunctioncontroller(...seletores){

constelements=seletores.map(seletor=>document.querySelector(seletor));

returnfunction(constructor){

constconstructorOriginal=constructor;

constconstructorNovo=function(){

//guardandoumareferênciaparaainstância

19.11 EXTRAINDO METADADOS COM UMDECORATORDECLASSE

440 19.11EXTRAINDOMETADADOSCOMUMDECORATORDECLASSE

Page 464: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

constinstance=newconstructorOriginal(...elements);

//varrecadapropriedadedaclasseObject

.getOwnPropertyNames(constructorOriginal.prototype)

.forEach(property=>{

if(Reflect.hasMetadata('bindEvent',instance,property)){

//precisafazeraassociaçãodoeventocomométodo

}});

}

constructorNovo.prototype=constructorOriginal.prototype;

returnconstructorNovo;}

}

Estamosquaselá.Agora,dentrodacondiçãoif,precisamosextrair o objeto que adicionamos como metadado, para entãotermos os dados necessários para podermos realizar a associaçãodo evento. Vamos criar uma função auxiliar chamadaassociaEvento,quereceberáainstânciadaclasseeometadado.

É através de Reflect.getMetadata() que extraímos ainformação, passando como parâmetros o identificador dometadado, a instância da classe e o nome da propriedade quepossuiometadado:

//client/app-src/util/decorators/Controller.js

exportfunctioncontroller(...seletores){

constelements=seletores.map(seletor=>

19.11EXTRAINDOMETADADOSCOMUMDECORATORDECLASSE 441

Page 465: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

document.querySelector(seletor));

returnfunction(constructor){

constconstructorOriginal=constructor;

constconstructorNovo=function(){

constinstance=newconstructorOriginal(...elements);

Object.getOwnPropertyNames(constructorOriginal.prototyp

e).forEach(property=>{

if(Reflect.hasMetadata('bindEvent',instance,property)){

associaEvento(instance,Reflect.getMetadata('bindEvent',inst

ance,property));}

});}

constructorNovo.prototype=constructorOriginal.prototype;

returnconstructorNovo;}

}

functionassociaEvento(instance,metadado){

document.querySelector(metadado.selector).addEventListener(metadado.event,event=>{

if(metadado.prevent)event.preventDefault();instance[metadado.propertyKey](event);

});}

Reparem que não associamos diretamente o método dainstância ao evento em associaEvento . Adicionamos uma

442 19.11EXTRAINDOMETADADOSCOMUMDECORATORDECLASSE

Page 466: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

funçãointermediáriaqueentãochamaométododainstância.Issofoinecessárioparalivrarmosdodesenvolvedoraresponsabilidadederealizarevent.preventDefaultnosmétodosdocontroller.

Agora, só nos resta alterar o arquivo client/app-

src/app.js . Removeremos todas as instruções que associameventos, inclusive aquela que cria o alias $. Nosso arquivoficaráassim:

//client/app-src/app.js

import{NegociacaoController}from'./controllers/NegociacaoController.js';import{Negociacao}from'./domain/index.js';

constcontroller=newNegociacaoController();

//REMOVEUOALIAS$EASASSOCIAÇÕESDOSEVENTOS

constnegociacao=newNegociacao(newDate(),1,200);constheaders=newHeaders();headers.set('Content-Type','application/json');constbody=JSON.stringify(negociacao);constmethod='POST';

constconfig={method,headers,body

};

fetch('/negociacoes',config).then(()=>console.log('Dadoenviadocomsucesso'));

Missãocumprida!Simplificamosbastanteamaneirapelaqualrealizávamosaassociaçãodeumeventocomosmétodosdenossocontroller.

19.11EXTRAINDOMETADADOSCOMUMDECORATORDECLASSE 443

Page 467: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

O código completo deste capítulo você encontra emhttps://github.com/flaviohenriquealmeida/cangaceiro-javascript/tree/master/19

444 19.11EXTRAINDOMETADADOSCOMUMDECORATORDECLASSE

Page 468: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

CAPÍTULO20

“Como é que você chegou aqui com vida, cabra velho?" -Lampião

Temosnossaaplicaçãototalmentefuncional.Todavia,existempontosquedevemserlevadosemconsideraçãoantesdebatermoso marteloedizermosqueaaplicaçãoestáprontaparaprodução:

OSystem.jscarregaoprimeiromódulodaaplicaçãoe,a partir dele, identifica e busca suas dependências,realizando diversas requisições para o servidor.Minimizar o número de requisições para o servidormelhora a performance da aplicação. Temos comoevitaressasmúltiplasrequisições?

OCSSdoBootstrapjáexistianoprojetoquebaixamosno início do livro. Será que podemosusar o próprionpm para baixar e gerenciar as dependências do

front-end?

Não há distinção entre a versão do projeto deprodução para a de desenvolvimento. Como

ENFRENTAMENTOFINAL

20ENFRENTAMENTOFINAL 445

Page 469: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

concatenar eminificar os scripts parao ambientedeprodução?

Estamosusandoumserverdisponibilizadopeloautorpara tornar nosso projeto acessível pelo navegador.Como conseguir o resultado parecido comferramentasconceituadasdomercado?

Podemos responder a todas as perguntas com auxílio doWebpack.

Webpack (https://webpack.github.io/) é um module bundler,um agrupador de módulos usado por ferramentas famosas domercado,comoAngularCLI(https://cli.angular.io/),CreateReactApp (https://github.com/facebookincubator/create-react-app) eVue CLI (https://github.com/vuejs/vue-cli). Mas seu uso não éexclusivodessasferramentas.

OWebpackpermitetratardiversosrecursosdaaplicaçãocomoummódulo,inclusivearquivosCSS.Tudoissoéfeitoduranteumprocessodebuildque,nofinal,geraumúnicoarquivoJavaScript,geralmente chamado de bundle.js . Este possui todos osmódulos necessários para a aplicação.Neste sentido, oWebpackdispensaousodeumloader,comooSystem.jsqueusamosemnossoprojeto.

20.1WEBPACK,AGRUPADORDEMÓDULOS

20.2 PREPARANDO O TERRENO PARA OWEBPACK

446 20.1WEBPACK,AGRUPADORDEMÓDULOS

Page 470: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

VamosprepararoterrenoparautilizarmosWebpackemnossoprojeto. Primeiro, emclient/index.html, vamos substituir aimportação do script do System.js e sua configuração por umaúnica tag<script>, que importa o arquivodist/bundle.jsqueaindanãoexiste.

<!--client/index.html--><!--códigoanterioromitido-->

<divid="negociacoes"></div>

<scriptsrc="node_modules/reflect-metadata/Reflect.js"></script>

<scriptsrc="dist/bundle.js"></script>

</body>

</html>

Vamosaproveitareapagarapastaclient/app, aquela quecontém os arquivos transcompilados pelo Babel . Nossosarquivoscompiladosseguirãooutrotrâmite.

Nãoutilizaremosmaisbabel-cli,nemosystemjs.Vamosremovê-los do nosso projeto, inclusive do arquivoclient/package.json.Conseguimosissoatravésdoscomandos:

npmuninstallbabel-cli--save-devnpmuninstallsystemjs--save

Comovimos,nãousaremosmaisobabel-cli,masoBabelainda precisa estar presente, pois o Webpack o utilizará paratranscompilarnossosarquivos.Então,vamosinstalarobabel-core(https://www.npmjs.com/package/babel-core), uma versão doBabeldesprovidade linhadecomandopara serusadaporoutrasferramentas.Vamos aproveitar e também instalar oWebpack deumavez:

20.2PREPARANDOOTERRENOPARAOWEBPACK 447

Page 471: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

[email protected]@3.1.0--save-dev

Éimportanteutilizarasversõeshomologadasporesteautoraolongo do capítulo. Se mais tarde houver a necessidade deatualizarasbibliotecas, issopode ser feitono final,depoisdoleitorteracertezadequetudoestáfuncionando.

Agora que temos as dependências fundamentais baixadas,podemosdarinícioàconfiguraçãodoWebpack.

Webpack centraliza suas configurações no arquivowebpack.config.js . Uma das primeiras configurações que

precisamos fazer é indicarqual seráoprimeiromódulo (entry) aser carregado. O Webpack é inteligente para carregar suasdependênciasapartirdomódulo.Porfim,precisamosdizerolocalde saída (output) do bundle criado, o arquivo com todos osmódulosresolvidosdaaplicação.

Vamoscriaroarquivoclient/webpack.config.js,queleráo arquivo ./app-src/app.js . E o resultado com todos osmódulosresolvidosserásalvoemdist/bundle.js.

//client/webpack.config.js

constpath=require('path');

module.exports={entry:'./app-src/app.js',output:{

filename:'bundle.js',

20.3OTEMÍVELWEBPACK.CONFIG.JS

448 20.3OTEMÍVELWEBPACK.CONFIG.JS

Page 472: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

path:path.resolve(__dirname,'dist')}

}

Teremos um único arquivo para ser importado pelonavegador,minimizandoassimaquantidadederequisições feitaspara o servidor. Nosso próximo passo será adicionarmos emclient/package.jsonumnpmscriptquechameobináriodo

Webpack, indicandoparaeleoarquivodeconfiguraçãoquedevelevaremconsideração.

Aliás, vamos remover todos os scripts que configuramos atéagora,mantendoapenasotest,paraentãoadicionarmosonovoscript.Emclient/package.json,faremos:

/*códigoanterioromitido*/

"scripts":{"test":"echo\"Error:notestspecified\"&&exit1",

"build-dev":"webpack--configwebpack.config.js"},

/*códigoposterioromitido*/

Agora,atravésdoterminaledentrodapastaclient, vamosexecutarocomando:

npmrunbuild-dev

Durante o processo de build, receberemos a seguintemensagemdeerronoterminal:

ERRORin./app-src/controllers/NegociacaoController.jsModuleparsefailed:/Users/flavioalmeida/Desktop/20/client/app-src/controllers/NegociacaoController.jsUnexpectedcharacter'@'(5:0)Youmayneedanappropriateloadertohandlethisfiletype.|import{getNegociacaoDao,Bind,debounce,controller,bindEvent}from'../util/index.js';

20.3OTEMÍVELWEBPACK.CONFIG.JS 449

Page 473: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

||@controller('#data','#quantidade','#valor')|exportclassNegociacaoController{|@./app-src/app.js1:0-77

Isso acontece porque oWebpacknão reconhece a sintaxe doDecorator. Faz sentido, porque este recurso só está disponívelatravésdoprocessodecompilaçãodoBabel.

Para resolvermos este problema, precisamos solicitar aobabel-corequetranscompilenossosarquivosantesdoWebpackresolver os módulos. Mas como? É o que veremos na próximaseção.

A ponte de ligação entre oWebpack e o babel-core é obabel-loader (https://github.com/babel/babel-loader), umcarregadorexclusivovoltadoparaoBabel.Esseloaderleránossasconfiguraçõesemclient/.babelrcquandoforexecutado.

Vamosinstalaroloaderatravésdoterminal:

[email protected]

Isso ainda não é suficiente, precisamos fazer com queclient/webpack.config.js utilize o loader que acabamos de

baixar.Fazemosissoadicionandoaconfiguraçãomodule.Dentrodemodule,podemosterváriasrules (regras),ecadaregrapodeusarummóduloespecíficoquandoaplicada.

Alterandonossoarquivoclient/webpack.config.js:

20.4 BABEL-LOADER, A PONTE ENTRE OWEBPACKEOBABEL

450 20.4BABEL-LOADER,APONTEENTREOWEBPACKEOBABEL

Page 474: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

//client/webpack.config.js

constpath=require('path');module.exports={

entry:'./app-src/app.js',output:{

filename:'bundle.js',path:path.resolve(__dirname,'dist')

},module:{

rules:[{

test:/\.js$/,exclude:/node_modules/,use:{

loader:'babel-loader'}

}]

}}

Porenquanto,temosapenasumaregra.Apropriedadetestindica a condição na qual nosso loader será aplicado. Usamos aexpressãoregular/\.js$/paraconsiderartodososarquivosqueterminamcomaextensão.js.Duranteesteprocesso,excluímosapastanode_modules,poisnãofazsentidoprocessarosarquivosdela. Por fim, dentro de use , indicamos o loader que seráutilizado,emnossocasoobabel-loader.

Agora já podemos tentar executar mais uma vez nossoprocessodebuildatravésdoterminal:

npmrunbuild-dev

Emaisumavez,recebemosumavisonele:

Hash:b9e23a2ce60c22bf8d05Version:webpack3.0.0Time:286ms

AssetSizeChunks ChunkNames

20.4BABEL-LOADER,APONTEENTREOWEBPACKEOBABEL 451

Page 475: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

bundle.js3.87kB0[emitted]main[0]./app-src/app.js1.38kB{0}[built][1warning]

WARNINGin./app-src/app.jsSystem.registerisnotsupportedbywebpack.

Oqueseráquefoidessavez?

Quando usamos System.js , configuramos oclient/.babelrc para transcompilarnossosmódulos comum

padrão compatível com o loader. Contudo, esse formato não ésuportado pelo Webpack. A boa notícia é que, a partir da suaversão2.0,Webpackjásuportaporpadrãoosistemademódulosdo ES2015 (ES6).Dessa forma, não precisamosmais domódulo

babel-plugin-transform-es2015-modules-systemjs epodemos removê-lo. Além disso, também podemos atualizarclient/.babelrcparanãofazermaisusodoplugin.

Removendoomódulovianpm:

npmuninstallbabel-plugin-transform-es2015-modules-systemjs--save-dev

Agora,precisamosalterarclient/.babelrc.Emplugins,deixaremosapenasotransform-decorators-legacy:

//client/.babelrc{

"presets":["es2017"],"plugins":["transform-decorators-legacy"]

}

Agorapodemosgerarobuilddonossoprojetoqueelevaiatéofinal,semproblemaalgum:

npmrunbuild-dev

Dentro da pasta client/dist , foi gerado o arquivo

452 20.4BABEL-LOADER,APONTEENTREOWEBPACKEOBABEL

Page 476: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

bundle.js.Éumarquivoquecontémtodososmódulosusadospelaaplicaçãoconcatenados.

Mesmo ainda sem um servidor rodando, se abrirmosclient/index.htmldiretamentenonavegador,nossaaplicaçãocontinuaráfuncionando.

Aprendemos a realizar um build de desenvolvimento, masminificar scripts em ambiente de produção é uma boa prática.Aliás, ainda não temos uma separação clara entre esses doisambientes.Chegouahoradebotaracasaemordem.

Vamos criar um novo npm script chamado build-prod,praticamente idêntico ao build-dev , mas que receberá oparâmetro -p . Isso indicará para o Webpack que estamossolicitando um build de produção, fazendo-o minificar nossobundle.jsnofinal.

Alterandoclient/package.json:

//códigoanterioromitido

"scripts":{"test":"echo\"Error:notestspecified\"&&exit1","build-dev":"webpack--configwebpack.config.js","build-prod":"webpack-p--configwebpack.config.js"

},

//códigoposterioromitido

Porfim,atravésdoterminal,vamosexecutarocomando:

npmrunbuild-prod

20.5PREPARANDOOBUILDDEPRODUÇÃO

20.5PREPARANDOOBUILDDEPRODUÇÃO 453

Page 477: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Umnovoerroéexibidonoterminal:

ERRORinbundle.jsfromUglifyJsUnexpectedtoken:name(Negociacao)[bundle.js:75,4]

Eagora?Quandousamosoparâmetro-p,oWebpackusaráomódulo Uglify para minificar nossos arquivos. Todavia, estemódulo ainda não é compatível com a sintaxe do ES2015 (ES6),razãodoerroquerecebemos.

UmasoluçãoseriatranscompilarnossoprojetoparaES5,masisso tornaria o processo de build mais lento, pois teríamos detranscompilar de ES2017 para ES2016, para só depoistranscompilar para ES5. Vamos abdicar do parâmetro -p einstalar o plugin babili-webpack-plugin do Webpack, que sabeminificarcorretamentenossocódigo.

Atravésdoterminal,instalaremosoplugincomocomando:

[email protected]

Excelente, agora precisamos configurá-lo emclient/webpack.config.js. Porém, esse plugin só pode ser

ativadonobuilddeprodução,enãonodedesenvolvimento.Parafazermos essa distinção, consultaremos a variável de ambienteNODE_ENV através de process.env.NODE_ENV , dentro dewebpack.config.js.OpluginsóseráativadoquandoovalordavariáveldeambienteNODE_ENVforproduction:

//client/webpack.config.js

constpath=require('path');

//IMPORTANDOOPLUGINconstbabiliPlugin=require('babili-webpack-plugin');

//COMEÇACOMNENHUMPLUGIN

454 20.5PREPARANDOOBUILDDEPRODUÇÃO

Page 478: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

letplugins=[]

if(process.env.NODE_ENV=='production'){

//SEFORPRODUCTION,ADICIONAOPLUGINNALISTA

plugins.push(newbabiliPlugin());}

module.exports={entry:'./app-src/app.js',output:{

filename:'bundle.js',path:path.resolve(__dirname,'dist')

},module:{

rules:[{

test:/\.js$/,exclude:/node_modules/,use:{

loader:'babel-loader'}

}]

},//mesmacoisaqueplugins:pluginsplugins

}

Agora, precisamos alterar nosso arquivoclient/package.json,quedeveatribuiràvariáveldeambienteNODE_ENV o valorproduction toda vezqueo scriptbuild-prodforchamado:

//ATENÇÃO,PORENQUANTOINCOMPATÍVELCOMWINDOWS

//códigoanterioromitido

"scripts":{"test":"echo\"Error:notestspecified\"&&exit1","build-dev":"webpack--configwebpack.config.js","build-prod":"NODE_ENV=productionwebpack--configwebpack.c

20.5PREPARANDOOBUILDDEPRODUÇÃO 455

Page 479: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

onfig.js"},

//códigoposterioromitido

O problema é que esta solução só funciona para mudarvariáveisdeambientenoLinuxenoMAC,masnãofuncionaránoWindows.Veremosasoluçãoaseguir.

Para termos uma solução de atribuição de variáveis deambiente que funcione nas plataformas Linux,Mac eWindows,podemos usar o módulo cross-env(https://www.npmjs.com/package/cross-env).Vamosinstalá-lo:

[email protected]

Agora, vamos alterar o script build-prod emclient/package.jsonparafazerusodocross-env:

//client/package.json//códigoanterioromitido

"scripts":{"test":"echo\"Error:notestspecified\"&&exit1","build-dev":"webpack--configwebpack.config.js","build-prod":"cross-envNODE_ENV=productionwebpack--config

webpack.config.js"},

//códigoposterioromitido

Testandonossaalteração:

npmrunbuild-prod

20.6 MUDANDO O AMBIENTE COM CROSS-ENV

456 20.6MUDANDOOAMBIENTECOMCROSS-ENV

Page 480: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Agora, o script build-prod criará oclient/dis/bundle.js minificado, perfeito para ser utilizado

emprodução.

Estamos utilizando Webpack para agrupar nossos módulos,mas precisar executar o npm script build-dev toda vez quealterarmos um arquivo desanima qualquer cangaceiro. Podemoslançar mão do webpack-dev-server(https://www.npmjs.com/package/webpack-dev-server), umservidor para ambiente de desenvolvimento que se integra comWebpack, disparando o build do projeto sempre que algumarquivo for modificado, e ainda por cima suporta livereload!Porém, antes de instalá-lo, precisamos realizar uma pequenaalteraçãoemjscangaceiro/server/.

O servidordisponibilizadonodownloaddoprojetono iníciodolivro,alémdedisponibilizarumaAPIparaserconsumidapelanossa aplicação, também disponibiliza nosso projeto para onavegador.Precisamosdesabilitaresteúltimorecurso,poisquemvai disponibilizar nossa aplicação para o navegador será owebpack-de-server.

Vamos alterar o arquivo server/config/express.js ecomentarasseguinteslinhasdecódigo:

//server/config/express.js

//códigoanterioromitido

//comentaressaslinhasquecompartilhamosarquivosestáticos

20.7 WEBPACK DEV SERVER ECONFIGURAÇÃO

20.7WEBPACKDEVSERVERECONFIGURAÇÃO 457

Page 481: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

/*app.set('clientPath',path.join(__dirname,'../..','client'));console.log(app.get('clientPath'));app.use(express.static(app.get('clientPath')));*/

//códigoposterioromitido

Essa alteração é suficiente para que nosso servidordisponibilizeapenasaAPIdenegociações.

Precisamos alterar agora client/app-

src/domain/negociacao/NegociacaoService.js . NósestávamosusandooendereçorelativodaAPI,porquenossapáginaficava no mesmo servidor da API. Porém, como realizamos adivisão, precisamos adicionar http://localhost:3000 aoendereço:

//client/app-src/domain/negociacao/NegociacaoController.js

import{HttpService}from'../../util/HttpService.js';import{Negociacao}from'./Negociacao.js';

exportclassNegociacaoService{

constructor(){

this._http=newHttpService();}

obtemNegociacoesDaSemana(){

returnthis._http.get('http://localhost:3000/negociacoes/semana')

//códigoomitido}

obtemNegociacoesDaSemanaAnterior(){

returnthis._http

458 20.7WEBPACKDEVSERVERECONFIGURAÇÃO

Page 482: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

.get('http://localhost:3000/negociacoes/anterior')//códigoomitido

}

obtemNegociacoesDaSemanaRetrasada(){

returnthis._http.get('http://localhost:3000/negociacoes/retrasada')//códigoomitido

}

//códigoposterioromitido}

Precisamos alterar também client/app-src/app.js, poisacessamosaAPIpararealizarmosumPOST:

//client/app-src/app.js

//códigoanterioromitidofetch('http://localhost:3000/negociacoes',config)

.then(()=>console.log('Dadoenviadocomsucesso'));

Agora jápodemos instalarnossonovo servidor.No terminal,dentrodapastaclient,executaremosocomando:

[email protected]

Em seguida, vamos adicionar o npm script start emclient/package.json,quelevantaránossoservidor.

"scripts":{"test":"echo\"Error:notestspecified\"&&exit1","build-dev":"webpack--configwebpack.config.js",

"build-prod":"cross-envNODE_ENV=productionwebpack--configwebpack.config.js",

"start":"webpack-dev-server"},

Antesdecontinuarmos,vamosapagarapastaclient/dist.Ela só existirá quando executarmos os scripts build-dev ou

20.7WEBPACKDEVSERVERECONFIGURAÇÃO 459

Page 483: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

build-prod . Quando estivermos usando o webpack-dev-server, o build será feito na memória do servidor, evitando aescrita em disco para acelerar o seu carregamento. É coisa decangaceiromesmo!

Fechetodososterminais.Emseguida,comumnovoabertoedentrodapastaclient,executeocomando:

npmstart

Receberemosaseguintemensagemnoconsole:

>[email protected]/Users/flavioalmeida/Desktop/20/client>webpack-dev-server

Projectisrunningathttp://localhost:8080/webpackoutputisservedfrom/

//outrasmensagensdecompilação

Acessamosagoranossaaplicaçãoemumanovaporta,a8080através do endereço http://localhost:8080 . Porém, algopareceestarerrado.

Nossa página carrega,mas o dist/bundle.js indicado natagscriptdeindex.htmlnãofoiencontrado.Arazãodissoéquenossoservidordisponibilizouoarquivoemmemória,masnoendereço localhost:8080/bundle.js , e nãolocalhost:8080/dist/bundle.js.Podemosajustarocaminho

do bundle com uma pequena alteração emclient/webpack.config.js.

Emclient/webpack.config.js,dentrodeoutput,vamosadicionarachavepublicPath. Ela indicaráo caminhopúblicoacessívelatravésdonavegadorparanossobundle:

460 20.7WEBPACKDEVSERVERECONFIGURAÇÃO

Page 484: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

//client/webpack.config.js

constpath=require('path');constbabiliPlugin=require('babili-webpack-plugin');

letplugins=[]

if(process.env.NODE_ENV=='production'){

plugins.push(newbabiliPlugin());}

module.exports={entry:'./app-src/app.js',output:{

filename:'bundle.js',path:path.resolve(__dirname,'dist'),publicPath:"dist"

},//códigoposterioromitido

Agora,bastapararmososervidoreexecutá-lonovamentecomnpmstart, para que ele fique de pémais uma vez comnossa

aplicaçãoacessandocorretamentedist/bundle.js.

Experimenteagorarealizaralteraçõesnoprojeto,porexemplo,adicionando um alert no método adiciona() deNegociacaoController . Um novo bundle em memória será

gerado e o navegador automaticamente será recarregado.Fantástico!

Mas ainda não acabou, podemos avançar ainda mais pelosertãodoWebpack!

Umoutropontoemnossaaplicaçãoquepodesermelhoradoé

20.8 TRATANDO ARQUIVOS CSS COMOMÓDULOS

20.8TRATANDOARQUIVOSCSSCOMOMÓDULOS 461

Page 485: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

a maneira como gerenciamos e utilizamos as dependências defront-end. Vamos começar primeiro atacando a questão dosarquivosCSS.

Dentrodeclient/css, temososarquivosdoBootstrapqueforam disponibilizados com o download do projeto usado noinício do livro. Contudo, se quisermos utilizar outras bibliotecasCSS,teremosdebaixá-lasmanualmente,paraentãocopiá-lasparadentrodapastaclient/css.

Comcertezanãoqueremosassumiressatarefa,aindamaisquetemosonpm,ogerenciadordepacotesdoNode.jsquepodenosajudaragerenciarasdependênciasdofront-end.Oprimeiropassoquefaremoséapagarapastaclient/css, comos arquivosdoBootstrapqueforamdisponibilizados.Nãosepreocupe,confie!

Agora, como verdadeiros cangaceiros, vamos baixar oBootstrapatravésdonpm:

[email protected]

Como se não bastasse, removeremos a importação dosarquivos CSS do Bootstrap em client/index.html . A tag<head>ficaráassim:

<!--client/index.html--><!DOCTYPEhtml><html>

<head><metacharset="UTF-8"><metaname="viewport"content="width=device-width"><title>Negociações</title>

</head>

Agora, em vez de importamos o Bootstrap diretamente em

462 20.8TRATANDOARQUIVOSCSSCOMOMÓDULOS

Page 486: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

client/index.html , vamos importá-lo como se fosse ummóduloemclient/app-src/app.js,issomesmo!

//client/app-src/app.js

//ATÉUMCANGACEIROVIRARIAOSOLHOSPARAISSO!import'bootstrap/dist/css/bootstrap.css';import'bootstrap/dist/css/bootstrap-theme.css';

import{NegociacaoController}from'./controllers/NegociacaoController.js';import{Negociacao}from'./domain/index.js';

//códigoposterioromitido

Realizamos o import debootstrap/dist/css/bootstrap.css.Dealgumamaneiraque

aindanãosabemos,oWebpackentendeporpadrãoquedesejamosacessar na verdadeclient/node_modules/bootstrap/dist/css/bootstrap.css .

É uma maneira nada convencional de importar um CSS, aindamaisqueestamostratandooCSSdoBootstrapcomoummódulo!Seráquefunciona?

Olhandonoterminal,vemosaseguintemensagemdeerro:

ERRORin./node_modules/bootstrap/dist/css/bootstrap.cssModuleparsefailed:/Users/flavioalmeida/Desktop/20/client/node_modules/bootstrap/dist/css/bootstrap.cssUnexpectedtoken(7:5)Youmayneedanappropriateloadertohandlethisfiletype.

OWebpack não sabe tratar o Bootstrap nem qualquer outroCSS como um módulo. Para que isso seja possível, precisamosensiná-loatravésdeumloaderquesaibalidarcomessasituação.

Para conseguirmos carregar CSS como módulos, precisamosdoauxíliodocss-loader(https://github.com/webpack-contrib/css-

20.8TRATANDOARQUIVOSCSSCOMOMÓDULOS 463

Page 487: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

loader) e do style-loader (https://github.com/webpack-contrib/style-loader). O primeiro plugin transforma os arquivosCSSimportadosemumJSON,eosegundoutilizaessainformaçãoparaadicionardemaneirainlineosestilosdiretamentenoHTML,atravésdatag<style>,quandoobundleforcarregado.

Vamos parar nosso servidor, para então instalar os loadersatravésdoterminal:

[email protected]@0.18.2--save-dev

Isso ainda não é suficiente, precisamos configurá-los emclient/webpack.config.js.Realizandoamodificação:

constpath=require('path');constbabiliPlugin=require('babili-webpack-plugin');

letplugins=[]

if(process.env.NODE_ENV=='production'){

pluginn.push(newbabiliPlugin());}

module.exports={entry:'./app-src/app.js',output:{

filename:'bundle.js',path:path.resolve(__dirname,'dist'),publicPath:"dist"

},module:{

rules:[{

test:/\.js$/,exclude:/node_modules/,use:{

loader:'babel-loader'}

},

464 20.8TRATANDOARQUIVOSCSSCOMOMÓDULOS

Page 488: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

/*NOVAREGRAAQUI*/{

test:/\.css$/,loader:'style-loader!css-loader'

}]

},plugins

}

Adicionamosumanovaregraqueseaplicaatodososarquivosque terminam com a extensão .css. Para estes arquivos, osloadersstyle-loadereocss-loaderentrarãoemaçãoparacarregá-los.Seráquefunciona?

Quando subimos mais uma vez o servidor com o comandonpm start, recebemos uma sucessão de mensagem de erros,

porém, como estamos próximos a nos tornarmos cangaceirosJavaScript,elasnãodevemnosapavorar.

OproblemaestánadependênciadoBootstrapdearquivosdefontesusadosporele.Duranteobuild,oWebpacknãosabelidarcom esses arquivos, inclusive nem sabe que eles devem estardentrodapastaclient/distduranteoprocessodedistribuição.Precisamostreiná-loatravésdenovosplugins.

Os plugins url-loader (https://github.com/webpack-contrib/url-loader) e file-loader (https://github.com/webpack-contrib/file-loader) são aqueles que conseguirão lidar com ocarregamentodosarquivosdefontesutilizadospeloBootstrap.

Atravésdoterminale,comosempre,dentrodapastaclient,vamosinstalarosplugins:

[email protected]@0.11.2--save-dev

20.8TRATANDOARQUIVOSCSSCOMOMÓDULOS 465

Page 489: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Por fim, precisamos colocar uma regra para cada tipo dearquivode fonte acessadopeloBootstrap.Não se assuste comasexpressões regulares, elas são expressões clássicas do mundoWebpack:

//client/webpack.config.js

constpath=require('path');constbabiliPlugin=require("babili-webpack-plugin");

letplugins=[]

if(process.env.NODE_ENV=='production'){

plugins.push(newbabiliPlugin());}

module.exports={entry:'./app-src/app.js',output:{

filename:'bundle.js',path:path.resolve(__dirname,'dist'),publicPath:"dist"

},module:{

rules:[{

test:/\.js$/,exclude:/node_modules/,use:{

loader:'babel-loader'}

},{

test:/\.css$/,loader:'style-loader!css-loader'

},{

test:/\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/,loader:'url-loader?limit=10000&mimetype=applicat

ion/font-woff'},{

466 20.8TRATANDOARQUIVOSCSSCOMOMÓDULOS

Page 490: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

test:/\.ttf(\?v=\d+\.\d+\.\d+)?$/,loader:'url-loader?limit=10000&mimetype=applicat

ion/octet-stream'},{

test:/\.eot(\?v=\d+\.\d+\.\d+)?$/,loader:'file-loader'

},{

test:/\.svg(\?v=\d+\.\d+\.\d+)?$/,loader:'url-loader?limit=10000&mimetype=image/sv

g+xml'}

]},plugins

}

Os loaders são aplicados da direita para a esquerda. Logo, ocss-loaderseráaplicadoprimeiroparaentãoostyle-loaderentraremação.

Agora, parando e subindo mais uma vez nosso servidor, obuild do Webpack é realizado sem problema algum e nossaaplicação continua sendo acessível através dehttp://localhost:8080.

Autilizaçãodocss-loader e dostyle-loader permiteimportarmos qualquerCSS comoummódulo emnossos scripts.Como o client/app-src/app.js é o primeiro módulo a sercarregadopela aplicação,nadamais justodoque importarmosoBootstrap e qualquer outro CSS que desejarmos utilizar em

nossaaplicação.Podemosatéfazerumteste.

Vamos criar o arquivo client/css/meucss.css com oseguinte estilo que adiciona uma sombra na borda da tabela denegociações:

20.8TRATANDOARQUIVOSCSSCOMOMÓDULOS 467

Page 491: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

/*client/css/meucss.css*/

table{box-shadow:5px5px5px;

}

Agora, em client/app-src/app.js , vamos importar oarquivo CSS que acabamos de criar.Mas atenção, não podemosfazersimplesmenteimport'css/meucss.css',poisoWebpackentenderá que desejamos acessarclient/node_modules/css/meucss.css , já que este é seudefault . Para importarmos corretamente meucss.css ,

precisamos descer uma pasta, por isso usamos import

'../css/meucss.css':

//client/app-src/app.js

import'bootstrap/dist/css/bootstrap.css';import'bootstrap/dist/css/bootstrap-theme.css';

//importou!import'../css/meucss.css';

import{NegociacaoController}from'./controllers/NegociacaoController.js';import{Negociacao}

//códigoposterioromitido

Essa alteração fará com que o nosso navegador sejaautomaticamenterecarregado, levandoemconsideraçãoumnovoarquivo bundle.js criado em memória com a alteração queacabamosdefazer.

468 20.8TRATANDOARQUIVOSCSSCOMOMÓDULOS

Page 492: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Apráticade importarumarquivoCSScomomódulobrilhaainda mais quando trabalhamos com componentes. Umabiblioteca que utiliza essa estratégia é a React(https://facebook.github.io/react/).

Tudofuncionacomoesperado.Masseráquefuncionamesmo?Éoqueveremosaseguir.

Umolharatentoperceberáqueaaplicaçãocarregaráe,duranteuma janelade tempobempequena, ficará semestilo algum. Issoaconteceporqueosestilosque importamosestãosendoaplicadosprogramativamente pelo bundle que foi carregado. Aliás, eleincorporatodooCSSdoBootstrap,umbaitaCSSgigante!

Podemos voltar com a abordagem tradicional de carregar osarquivos CSS através da tag link e, ao mesmo tempo,aproveitarmos o processo de concatenação e minificação paradiferentesambientes.

Primeiro, vamos alterar client/index.html e importar oseguinteCSSqueconstruiremosduranteoprocessodebuild:

<!--client/index.html-->

<!DOCTYPEhtml><html>

<head>

20.9 RESOLVENDO O FOUC (FLASH OFUNSTYLEDCONTENT)

20.9RESOLVENDOOFOUC(FLASHOFUNSTYLEDCONTENT) 469

Page 493: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

<metacharset="UTF-8"><metaname="viewport"content="width=device-width"><title>Negociações</title><linkrel="stylesheet"href="dist/styles.css">

</head><!--códigoposterioromitido-->

Agora, vamos instalar o plugin extract-text-webpack-plugin(https://github.com/webpack-contrib/extract-text-webpack-plugin)atravésdoterminal,aindadentrodapastaclient:

[email protected]

Com o plugin instalado, precisamos importá-lo emwebpack.config.jseguardarsuainstânciadentrodanossalistade plugins. Sua instância recebe como parâmetro o nome doarquivo CSS que será gerado no final. Também será precisoalterarmosaregra(rule)dosarquivosterminadosem.css.

Usaremos a funçãoextract() presentenopluginque faráum fallback para style-loader , caso não consiga extrair oarquivoCSS.Nossoarquivowebpack.config.jsestaráassim:

//client/webpack.config.js

constpath=require('path');constbabiliPlugin=require('babili-webpack-plugin');constextractTextPlugin=require('extract-text-webpack-plugin');

letplugins=[]

//ADICIONANDOOPLUGIN,//MAISABAIXO,MODIFICAÇÃODOLOADER

plugins.push(newextractTextPlugin("styles.css")

);

if(process.env.NODE_ENV=='production'){

470 20.9RESOLVENDOOFOUC(FLASHOFUNSTYLEDCONTENT)

Page 494: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

plugins.push(newbabiliPlugin());}

module.exports={entry:'./app-src/app.js',output:{

filename:'bundle.js',path:path.resolve(__dirname,'dist'),publicPath:"dist"

},module:{

rules:[{

test:/\.js$/,exclude:/node_modules/,use:{

loader:'babel-loader'}

},

//MODIFICANDOOLOADER

{test:/\.css$/,use:extractTextPlugin.extract({

fallback:"style-loader",use:"css-loader"

})},{

test:/\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/,loader:'url-loader?limit=10000&mimetype=applicat

ion/font-woff'},{

test:/\.ttf(\?v=\d+\.\d+\.\d+)?$/,loader:'url-loader?limit=10000&mimetype=applicat

ion/octet-stream'},{

test:/\.eot(\?v=\d+\.\d+\.\d+)?$/,loader:'file-loader'

},{

test:/\.svg(\?v=\d+\.\d+\.\d+)?$/,

20.9RESOLVENDOOFOUC(FLASHOFUNSTYLEDCONTENT) 471

Page 495: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

loader:'url-loader?limit=10000&mimetype=image/svg+xml'

}]

},plugins

}

Agora, podemos fazer o build da nossa aplicação dedesenvolvimento ou de produção, pois nos dois casos teremostodos os arquivos CSS concatenados em um único arquivo,separando-odobundle.js.

Masseráqueestátudonamaisperfeitaharmonia?Veremosnapróximaseção.

Um cangaceiro mais atento verificará que o arquivodist/style.css gerado não foiminificado. Podemos resolver

isso com o plugin optimize-css-assets-webpack-plugin(https://github.com/NMFR/optimize-css-assets-webpack-plugin).Todavia,eledependedeumminificadorCSS,nocaso,usaremosocssnano(http://cssnano.co).

Vamosinstalarambosatravésdoterminal:

npminstalloptimize-css-assets-webpack-plugin@[email protected]

//client/webpack.config.js

constpath=require('path');constbabiliPlugin=require('babili-webpack-plugin');constextractTextPlugin=require('extract-text-webpack-plugin');

20.10 RESOLVEMOS UM PROBLEMA ECRIAMOSOUTRO,MASTEMSOLUÇÃO!

472 20.10RESOLVEMOSUMPROBLEMAECRIAMOSOUTRO,MASTEMSOLUÇÃO!

Page 496: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

//IMPORTOUOPLUGIN

constoptimizeCSSAssetsPlugin=require('optimize-css-assets-webpack-plugin');

letplugins=[]

plugins.push(newextractTextPlugin("styles.css")

);

if(process.env.NODE_ENV=='production'){

plugins.push(newbabiliPlugin());

//ADICIONOUOPLUGINPARAOBUILDDEPRODUÇÃO

plugins.push(newoptimizeCSSAssetsPlugin({cssProcessor:require('cssnano'),cssProcessorOptions:{

discardComments:{removeAll:true

}},

canPrint:true}));

}

//códigoposterioromitido

Agora,aoexecutarmosnoterminaloscriptnpmrunbuild-prod,oarquivodist/styles.cssestaráminificado.

Talvez o leitor esteja se perguntando qual a diferença entreloader e plugin. A grosso modo, o primeiro trabalha com cadaarquivoindividualmenteduranteouantesdeobundlesergerado.Já o segundo trabalha comobundleno final do seuprocessodegeração.

20.11IMPORTANDOSCRIPTS

20.11IMPORTANDOSCRIPTS 473

Page 497: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Somos capazes de importar bibliotecas diretamente declient/node_modules, inclusive aprendemos a tratar arquivosCSS comomódulos. Será que o procedimento de importação debibliotecas JavaScriptdentrodeclient/mode_modules segueomesmoprocesso?Veremos.

Não temos um uso prático em nossa aplicação para oscomponentes do Bootstrap que utilizam seu scriptbootstrap.js . Contudo, vamos importá-lo para verificar seconseguimoscarregá-locorretamente.

Alterandoclient/app-src/app.js:

//client/app-src/app.js

import'bootstrap/dist/css/bootstrap.css';import'bootstrap/dist/css/bootstrap-theme.css';

//carregouoscriptdobootstrap,apenasomodalimport'bootstrap/js/modal.js';import'../css/meucss.css';

//códigoposterioromitido

Bom, pelo menos, no terminal não temos nenhum erro decompilaçãonoterminal.Masseabrirmosoconsoledonavegador,veremosaseguintemensagemdeerro:

UncaughtError:Bootstrap'sJavaScriptrequiresjQuery

Faztodosentido,porqueobootstrap.jsdependedojQuery(https://jquery.com) para funcionar, a biblioteca front-end queainda vive no coletivo imaginário dos programadores front-end.Como estamos usando o próprio npm para gerenciar nossasbibliotecas front-end, nada mais justo do que baixar o jQueryatravésdele:

474 20.11IMPORTANDOSCRIPTS

Page 498: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

[email protected]

Agora, em client/app-src/app.js , vamos importar ojQueryantesdoscriptdobootstrap.js:

//client/app-src/app.js

import'bootstrap/dist/css/bootstrap.css';import'bootstrap/dist/css/bootstrap-theme.css';

//importouantesimport'jquery/dist/jquery.js';

import'bootstrap/js/modal.js';import'../css/meucss.css';

//códigoposterioromitido

Sabemosqueessaalteraçãogeraráumnovobundle.jsefaráorecarregamentodonavegador.Paranossasurpresa,continuamoscomoproblemaanteriornoconsoledonavegador:

bundle.js:11106UncaughtError:Bootstrap'sJavaScriptrequiresjQuery

EsseWebpack é cheio de surpresas, não émesmo? Vejamoscomoprocederaseguir.

O jQuerynão é encontrado em tempode execuçãoporque éumabibliotecaquevivenoescopoglobal.QuandoelaéprocessadapeloWebpack,elaéimportadacomosefosseummóduloedeixade ser acessível pelo restante da aplicação. Precisamos de umcomportamentodiferente.

20.12 LIDANDO COM DEPENDÊNCIASGLOBAIS

20.12LIDANDOCOMDEPENDÊNCIASGLOBAIS 475

Page 499: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Para tornarmos o jQuery globalmente disponível, precisamosda ajuda do webpack.ProvidePlugin(https://webpack.js.org/plugins/provide-plugin/). Owebpack.ProvidePluginautomaticamentecarregamódulosemvez de termos de importá-los em qualquer lugar da nossaaplicação.

Aboanotíciaéquenãoprecisamosinstalá-lo,poiseleépadrãodoWebpack.Basta importarmosomódulowebpack atravésderequire('webpack')emclient/webpack.config.js:

//client/webpack.config.js

constpath=require('path');constbabiliPlugin=require('babili-webpack-plugin');constextractTextPlugin=require('extract-text-webpack-plugin');constoptimizeCSSAssetsPlugin=require('optimize-css-assets-webpack-plugin');

//IMPORTOUOMÓDULODOWEBPACK!constwebpack=require('webpack');

letplugins=[]

plugins.push(newextractTextPlugin("styles.css"));

//OPLUGINVALETANTOPARAPRODUÇÃO//QUANTOPARADESENVOLVIMENTO

plugins.push(newwebpack.ProvidePlugin({$:'jquery/dist/jquery.js',jQuery:'jquery/dist/jquery.js'}));

if(process.env.NODE_ENV=='production'){

plugins.push(newbabiliPlugin());

476 20.12LIDANDOCOMDEPENDÊNCIASGLOBAIS

Page 500: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

plugins.push(newoptimizeCSSAssetsPlugin({cssProcessor:require('cssnano'),cssProcessorOptions:{discardComments:{removeAll:true}},canPrint:true}));}

//códigoposterioromitido

Na modificação realizada, estamos tornandojquery/dist/jquery.jsacessívelcom$ejQueryparatodososmódulosdaaplicação.Essesnomessãoimportantes,porquesãoosaliasesusadospelabiblioteca.

Podemos remover o import do jQuery que fizemos emclient/app-src/app.js, pois, comovimos, ele será carregadopelowebpack.ProvidePlugin:

//client/app-src/app.js

import'bootstrap/dist/css/bootstrap.css';import'bootstrap/dist/css/bootstrap-theme.css';

//removeuoscriptdojQuery

import'bootstrap/js/modal.js';import'../css/meucss.css';

//códigoposterioromitido

NãotentetestaradisponibilidadedojQueryatravésdoconsoledo navegador, pois ele foi encapsulado dentro de uma funçãoduranteoprocessodebuild.

20.12LIDANDOCOMDEPENDÊNCIASGLOBAIS 477

Page 501: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

OconsoleChromepossui uma funçãoauxiliar chamada$,disponível quando o jQuery não é carregado. Isso podeconfundi-lo, achando que este é o jQuery que carregamosatravésdoProvidePlugin.

Parasaberseeleestádisponívelounão,podemosrealizarumtestenopróprioclient/app-src/app.js:

//client/app-src/app.js

import'bootstrap/dist/css/bootstrap.css';import'bootstrap/dist/css/bootstrap-theme.css';import'bootstrap/js/modal.js';import'../css/meucss.css';import{NegociacaoController}from'./controllers/NegociacaoController.js';import{Negociacao}from'./domain/index.js';

$('h1').on('click',()=>alert('Foiclicado!'));

//códigoposterioromitido

Um cangaceiro só acredita vendo. Dessa forma, nada maisjustodoqueverificarnonavegadorofuncionamentodaaplicação.

Aliás,podemosfazeramesmacoisacomoscriptdoBootstrapque carregamos em nossa aplicação. Assim como jQuery, ele éativadoglobalmente,poismodificaocoredojQueryparasuportarsuasnovasfunções.

Podemos testaratravésdeconsole.log($('h1').modal) aexistênciada funçãomodal que só estarádisponível se o scriptmodal.jsdoBootstrapforcarregado:

478 20.12LIDANDOCOMDEPENDÊNCIASGLOBAIS

Page 502: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

//client/app-src/app.js

import'bootstrap/dist/css/bootstrap.css';import'bootstrap/dist/css/bootstrap-theme.css';import'bootstrap/js/modal.js';import'../css/meucss.css';import{NegociacaoController}from'./controllers/NegociacaoController.js';import{Negociacao}from'./domain/index.js';

$('h1').on('click',()=>alert('Foiclicado!'));

//provandoaexistênciadafunção!console.log('Funçãoadicionadapelobootstrap:');console.log($('h1').modal);

//códigoposterioromitido

Excelente!AgorajápodemosrealizarmaisumnovoajusteemnossaconfiguraçãodoWebpack.

Cadamódulo do nosso bundle é envolvido por umwrapper,quenadamaisédoqueumafunção.Contudo,aexistênciadesseswrappers tornam a execução do nosso script no navegador umpoucomaislenta.

Entretanto, a partir doWebpack 3, podemos ativar o ScopeHoisting. Ele consiste em concatenar o escopo de todos osmódulos em um único wrapper, permitindo assim que nossocódigosejaexecutadomaisrapidamentenonavegador.

Vamos ativar esse recurso apenas no build de produção,adicionandoumainstânciadeModuleConcatenationPluginemnossalistadeplugins:

20.13 OTIMIZANDO O BUILD COM SCOPEHOISTING

20.13OTIMIZANDOOBUILDCOMSCOPEHOISTING 479

Page 503: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

//client/webpack.config.js

//códigoanterioromitido

if(process.env.NODE_ENV=='production'){

//ATIVANDOOSCOPEHOISTING

plugins.push(newwebpack.optimize.ModuleConcatenationPlugin());

plugins.push(newbabiliPlugin());

plugins.push(newoptimizeCSSAssetsPlugin({cssProcessor:require('cssnano'),cssProcessorOptions:{discardComments:{removeAll:true}},canPrint:true}));}//códigoposterioromitido

Isso já é suficiente.Contudo, devido à dimensão reduzida danossaaplicação,teremosdificuldadedevernapráticaasmudançasnotempodeexecuçãodanossaaplicação.

Obundle.js,criadopelonossoscriptdoWebpack,possuiocódigodosnossosmódulos,inclusiveocódigodasbibliotecasqueutilizamos.O problema dessa abordagem é o aproveitamento docachedonavegador.

Toda vez que alterarmos o código do nosso projeto, algo

20.14 SEPARANDO NOSSO CÓDIGO DASBIBLIOTECAS

480 20.14SEPARANDONOSSOCÓDIGODASBIBLIOTECAS

Page 504: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

extremamente comum, geraremos um novo bundle.js quedeverá ser baixado e cacheado pelo navegador. Contudo, setivermosdoisbundles, um para nosso código e outro para asbibliotecas, o último ficará mais tempo no cache do navegador,poismudacommenosfrequência.

ConseguimosessadivisãoatravésdoCommonsChunkPlugin,que já vem com o próprio Webpack. Alterandoclient/webpack.config.js:

constpath=require('path');constbabiliPlugin=require('babili-webpack-plugin');constextractTextPlugin=require('extract-text-webpack-plugin');constoptimizeCSSAssetsPlugin=require('optimize-css-assets-webpack-plugin');constwebpack=require('webpack');

letplugins=[]

//osdoispluginsquejáconfiguramosforamomitidos

plugins.push(newwebpack.optimize.CommonsChunkPlugin({name:'vendor',filename:'vendor.bundle.js'}));//códigoposterioromitido

Comestaconfiguração,estamosdizendoque,paraapartedanossa aplicação chamada de vendor, todas as bibliotecas quevamosindicarequefazempartedenode_modulesfarãopartedevendor.bundle.js.

Agora,precisamosdividirnossaaplicaçãoemduaspartes:

//client/webpack.config.js

20.14SEPARANDONOSSOCÓDIGODASBIBLIOTECAS 481

Page 505: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

//códigoanterioromitido

//MUDAMOSOENTRY!

module.exports={

entry:{app:'./app-src/app.js',vendor:['jquery','bootstrap','reflect-metadata']},//códigoposterioromitido

Nossaaplicaçãoagorapossuidoispontosdeentrada,app evendor. Este nome não é por acaso, ele deve coincidir com onamequedemosnaconfiguraçãodoCommonsChunkPlugin.Apropriedade recebe um array com o nome dos módulos em node_modules que desejamos adicionar no bundlevendor.bundle.js.

Há mais um detalhe antes de realizarmos o build do nossoprojeto. Precisamos alterar client/index.html para quecarregueovendor.bundle.js.Nãoprecisaremosmaisimportaroreflect-metadata,poiselefarápartedevendor.bundle.js.

<!--client/index.html--><!--códigoanterioromitido-->

<divid="negociacoes"></div>

<!--importouonovobundle--><scriptsrc="dist/vendor.bundle.js"></script><scriptsrc="dist/bundle.js"></script>

</body></html>

Agora, ao realizar o build do nosso projeto, nossa pastaclient/distteráumaestruturaparecidacom:

482 20.14SEPARANDONOSSOCÓDIGODASBIBLIOTECAS

Page 506: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

├──448c34a56d699c29117adc64c43affeb.woff2├──89889688147bd7575d6327160d64e760.svg├──bundle.js├──e18bbf611f2a2e43afc071aa2f4e1512.ttf├──f4769f9bdb7466be65088239c12046d1.eot├──fa2772327f55d8198301fdb8bcfc8158.woff├──styles.css└──vendor.bundle.js

Tivemosquealteraroarquivoclient/index.htmlmais deuma vez ao longo da nossa aventura com o Webpack.Adicionamosaimportaçãodestyle.cssgeradopeloextract-text-webpack-plugin e também a importação devendor.bundle.js gerado pelo CommonsChunkPlugin . Esseprocessomanual não precisa ser assim. Com o auxílio dohtml-webpack-plugin (https://github.com/jantimon/html-webpack-plugin)podemosautomatizaresseprocesso.

Ohtml-webpack-pluginécapazdegerarumnovoarquivo html com todas as importações de artefatos gerados peloWebpack automaticamente. Inclusive, podemos indicar umtemplateinicialquesofreráatransformaçãonolugardecriarmosum novo. Perfeito, pois já temos um código HTML escrito emclient/index.html.

Vamosinstalaropluginatravésdoterminal:

[email protected]

Antes de continuarmos, vamos renomear o arquivo client/index.html para client/main.html . A ideia égerarmosoarquivoindex.htmldentrodeclient/dist com

20.15 GERANDO A PÁGINA PRINCIPALAUTOMATICAMENTE

20.15GERANDOAPÁGINAPRINCIPALAUTOMATICAMENTE 483

Page 507: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

todososoutrosarquivosgeradospeloWebpack.

Alémderenomearoarquivo,precisamosremovertodasastags<link> e<script> que importamarquivosCSS e JavaScriptrespectivamente.

Tendoacertezadasalteraçõesquefizemosantes,sónosrestaalteraroarquivoclient/webpack.config.js.Primeiro,vamosimportareadicionaroplugincomoprimeirodanossalista:

//client/webpack.config.js

constpath=require('path');constbabiliPlugin require('babili webpack plugin')constextractTextPlugin=require('extract-text-webpack-plugin');constoptimizeCSSAssetsPlugin=require('optimize-css-assets-webpack-plugin');constwebpack=require('webpack');

//importouonovopluginconstHtmlWebpackPlugin=require('html-webpack-plugin');

letplugins=[]

//adicionoucomoprimeirodalistaplugins.push(newHtmlWebpackPlugin({hash:true,minify:{html5:true,collapseWhitespace:true,removeComments:true,},filename:'index.html',template:__dirname+'/main.html',}));

//códigoposterioromitido

O plugin recebe um objeto como parâmetro com suasconfigurações,sãoelas:

484 20.15GERANDOAPÁGINAPRINCIPALAUTOMATICAMENTE

Page 508: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

hash:quandotrue, adicionaumhashno finaldaURLdosarquivos script e CSS importados no arquivo HTML gerado,importanteparaversionamentoecachenonavegador.Quandoumbundlediferenteforgerado,ohashserádiferenteeissoésuficienteparainvalidarocachedonavegador,fazendo-ocarregaroarquivomaisnovo.

minify: recebe um objeto como parâmetro com asconfigurações utilizadas para minificar o HTML. Podemosconsultar todas as configurações possíveis no endereçohttps://github.com/kangax/html-minifier#options-quick-reference

filename: o nome do arquivo HTML que será gerado.Respeitaráovalordopathdeoutputquejáconfiguramoslogonoiníciodacriaçãodoarquivowebpack.config.js.

template:caminhodoarquivoqueservirácomotemplateparageraçãodeindex.html.

Por fim, precisamos demais uma alteração.Comoo arquivo index.html gerado ficará em client/dist/index.html ,precisamosajustarocaminhodosimportsdosscriptsedosestilos.Para isso, vamos remover a propriedadepublicPath da chaveoutput:

//client/webpack.config.js

//códigoanterioromitido

module.exports={entry:{app:'./app-src/app.js',vendor:['jquery','bootstrap','reflect-metadata']},

20.15GERANDOAPÁGINAPRINCIPALAUTOMATICAMENTE 485

Page 509: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

output:{filename:'bundle.js',path:path.resolve(__dirname,'dist')/*removeupublicPath*/},//códigoposterioromitido

Pronto. Agora podemos testar a criação de um build, porexemplo,odedesenvolvimento:

npmrunbuild-dev

Olhandoclient/dist vemos que o arquivo index.htmlfoigerado:

├──448c34a56d699c29117adc64c43affeb.woff2├──89889688147bd7575d6327160d64e760.svg├──bundle.js├──e18bbf611f2a2e43afc071aa2f4e1512.ttf├──f4769f9bdb7466be65088239c12046d1.eot├──fa2772327f55d8198301fdb8bcfc8158.woff├──index.html├──styles.css└──vendor.bundle.js

Nossa solução funciona para o build de produção e tambémquandoestamosusandooWebpackDevServer.

Quando usamos o System.js, somos obrigados a indicar aextensãodearquivo.jsemnossosimports.ComWebpack,issonãoémaisnecessário.Vejamosumexemplo:

//client/app-src/util/DaoFactory.js

//REMOVEU.jsdonomedoarquivoimport{ConnectionFactory}from'./ConnectionFactory';

20.16 SIMPLIFICANDO AINDA MAIS AIMPORTAÇÃODEMÓDULOS

486 20.16SIMPLIFICANDOAINDAMAISAIMPORTAÇÃODEMÓDULOS

Page 510: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

//REMOVEU.jsdonomedoarquivoimport{NegociacaoDao}from'../domain/negociacao/NegociacaoDao;

exportasyncfunctiongetNegociacaoDao(){

letconn=awaitConnectionFactory.getConnection();returnnewNegociacaoDao(conn);}

Além disso, podemos importar diretamente um barrel semtermos que indicar o arquivo index.js, indicando apenas ocaminhodapastaquecontémindex.js.Vejamosumexemplo:

//client/app-src/controllers/NegociacaoController.js

//removeuOindex.js,importandoapenasapasta

import{Negociacoes,NegociacaoService,Negociacao}from'../domain';import{NegociacoesView,MensagemView,Mensagem,DateConverter}from'../ui';import{getNegociacaoDao,Bind,getExceptionMessage,debounce,controller,bindEvent}from'../util';

@controller('#data','#quantidade','#valor')exportclassNegociacaoController{//códigoposterioromitido

Escrevermenosésemprebom,aindamaisemalgocorriqueirocomoaimportaçãodemódulos.

Nossa aplicação é embrião de uma Single Page Application,aquela que não recarrega durante seu uso e que se pautafortemente em JavaScript. Aliás, esse tipo de aplicação carregatodososscriptsqueprecisanoprimeirocarregamentodapágina.

20.17CODESPLITTINGELAZYLOADING

20.17CODESPLITTINGELAZYLOADING 487

Page 511: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Todavia, quando o tamanho do bundle é muito grande, oprimeiro carregamento da página deixará a desejar, o que podeimpactarnaexperiênciadousuário.

O tempo de carregamento da aplicação pode ser reduzidoatravés do code splitting (separação de código) do bundleprincipal da aplicação em bundle menores carregando-os sobdemanda através da técnica de lazy loading (carregamentopreguiçoso).

Para que possamos ver esse poderoso recurso em ação,elegeremosomóduloNegociacaoServicecomoaquelequeserácarregadosobdemanda.

Oprimeiropassoéremovermostodasasimportaçõesestáticasdomódulorealizadasemnossaaplicação.Primeiro,vamosalterarclient/app-src/domain/index.jsremovendoaimportaçãodomódulo.Nossoarquivoficaráassim:

//client/app-src/domain/index.js

export*from'./negociacao/Negociacao';

export*from'./negociacao/NegociacaoDao';

//NÃOIMPORTAMAISOMÓDULONegociacaoService

export*from'./negociacao/Negociacoes';

Agora, em client/app-

src/controller/NegociacaoController.js vamos remover aimportaçãodeNegociacaoServicelogonoiníciodadeclaraçãodo nosso módulo, inclusive não teremos mais a propriedadethis._service,poisoserviçoserácarregadosobdemanda:

//client/app-src/controllers/NegociacaoController.js

488 20.17CODESPLITTINGELAZYLOADING

Page 512: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

//REMOVEUAIMPORTAÇÃODENegociacaoServiceimport{Negociacoes,Negociacao}from'../domain';

import{NegociacoesView,MensagemView,Mensagem,DateConverter}from'../ui';import{getNegociacaoDao,Bind,getExceptionMessage,debounce,controller,bindEvent}from'../util';

@controller('#data','#quantidade','#valor')exportclassNegociacaoController{

constructor(_inputData,_inputQuantidade,_inputValor){

Object.assign(this,{_inputData,_inputQuantidade,_inputValor})

this._negociacoes=newBind(newNegociacoes(),newNegociacoesView('#negociacoes'),'adiciona','esvazia');

this._mensagem=newBind(newMensagem(),newMensagemView('#mensagemView'),'texto');

//REMOVEUthis._service=newNegociacaoService();

this._init();}

//códigoposterioromitido

Agora,comauxíliodafunçãoSystem.importimportaremoso módulo ../domain/negociacao/NegociacaoService . OretornodeSystem.importéumapromisequenosdaráacessoaumobjetoque representaomódulo importado. Sendoo retornoda funçãoumapromise, podemosusarawait, pois a chamadaSystem.import está dentro de um método async, no caso

20.17CODESPLITTINGELAZYLOADING 489

Page 513: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

importaNegociacoes:

//client/app-src/controllers/NegociacaoController.js

//códigoanterioromitido

@bindEvent('click','#botao-importa')@debounce()asyncimportaNegociacoes(){

try{//Lazyloadingdomódulo.//Importaremos{NegociacaoService}domódulo.

const{NegociacaoService}=awaitSystem.import('../domain/negociacao/NegociacaoService');

//criandoumanovainstânciadaclasseconstservice=newNegociacaoService();

//MUDAMOSDEthis._serviceparaservice

constnegociacoes=awaitservice.obtemNegociacoesDoPeriodo();console.log(negociacoes);negociacoes.filter(novaNegociacao=>

!this._negociacoes.paraArray().some(negociacaoExistente=>novaNegociacao.equals(negociacaoExistente))).forEach(negociacao=>this._negociacoes.adiciona(negociacao));

this._mensagem.texto='Negociaçõesdoperíodoimportadascomsucesso';}catch(err){this._mensagem.texto=getExceptionMessage(err);}}

//códigoposterioromitido

Com as alterações realizadas, faremos um teste. Através do

490 20.17CODESPLITTINGELAZYLOADING

Page 514: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

terminal vamos executar o build de desenvolvimento, o maisrápido:

npmrunbuild-dev

Dentro da pasta client/dist existirá um novo arquivobundle,oarquivo0.bundle.js:

├──0.bundle.js├──448c34a56d699c29117adc64c43affeb.woff2├──89889688147bd7575d6327160d64e760.svg├──bundle.js├──e18bbf611f2a2e43afc071aa2f4e1512.ttf├──f4769f9bdb7466be65088239c12046d1.eot├──fa2772327f55d8198301fdb8bcfc8158.woff├──index.html├──styles.css└──vendor.bundle.js

DuranteoprocessodebuildWebpackéinteligenteosuficienteparasaberqueháumcarregamentosobdemandadeummóduloquenão foi importado estaticamente por nenhumoutromóduloda aplicação. Detectado o carregamento sob demanda, um novobundleserácriadocomocódigodomóduloqueserácarregadoapenasquandoousuárioclicarnobotão "Importarnegociações",poisénaaçãodométododocontrollerchamadopelobotãoqueomóduloserácarregadopelaprimeiravez.Semaistardeclicarmosoutras vezes no botão, nossa aplicação será automaticamenteinteligente para saber que o módulo já foi carregado e não obaixaránovamente.

Podemos inclusive testarnossaaplicaçãoatravésdoWebpackDev Server. Pelo terminal, vamos subir nosso servidor com ocomando:

npmstart

20.17CODESPLITTINGELAZYLOADING 491

Page 515: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

Ao acessarmos a aplicação através do endereçolocalhost:8080 ela continuará funcionando.Porém,podemos

ficar mais pertos da especificação ECMAScript do queimaginamos,assuntodapróximaseção.

OECMAScript2015(ES6)especificouumamaneiradedefinirmódulos resolvidos estaticamente em tempo de compilação,porém não definiu uma maneira padronizada de carregar omódulo assincronamente sob demanda. Há uma proposta paraintroduzir importações dinâmicas na linguagem que está bemavançada e que pode ser acompanhada emhttps://github.com/tc39/proposal-dynamic-import.Aboa

notíciaéqueoChromeeoutrosnavegadoresjáderaminícioasuaimplementação.

Webpack é compatível com a nova sintaxe import() ,substitutadeSystem.import(),porémoparserdoBabelnãoécapaz de reconhecer a sintaxe import() como válida sem oauxíliodeumplugin.Podemosresolverissofacilmenteinstalandoo plugin babel-plugin-syntax-dynamic-import

(https://www.npmjs.com/package/babel-plugin-syntax-dynamic-import).

No terminal e dentro da pasta client vamos executar ocomando:

[email protected]

Em seguida, vamos alterarclient/.babelrc e adicionar opluginnalistadeplugins:

20.18SYSTEM.IMPORTVSIMPORT

492 20.18SYSTEM.IMPORTVSIMPORT

Page 516: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

{"presets":["es2017"],"plugins":["transform-decorators-legacy","babel-plugin-syntax-dynamic-import"]}

Porfim,podemosalterarachamadadeSystem.importparaimportemNegociacaoController:

//client/app-src/controllers/NegociacaoController.js

//códigoanterioromitido

@bindEvent('click','#botao-importa')@debounce()asyncimportaNegociacoes(){

//MUDOUDESystem.importparaimport

const{NegociacaoService}=awaitimport('../domain/negociacao/NegociacaoService');

//códigoposterioromitido

Podemos realizar o build do projeto, seja ele dedesenvolvimento ou de produção que tudo continuaráfuncionando,inclusiveoWebpackDevserver.

E viva o cangaço! Agora que completamos definitivamentenossaaplicação,podemosgerarobuilddeproduçãoparaquesejarealizadoodeploynoservidordenossapreferência:

npmrunbuild-prod

Agora,sóprecisamoslevarparaproduçãooconteúdodapasta

20.19 QUAIS SÃO OS ARQUIVOS DEDISTRIBUIÇÃO?

20.19QUAISSÃOOSARQUIVOSDEDISTRIBUIÇÃO? 493

Page 517: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

dist.

NossaaplicaçãoéumprojetoembrionáriodeumaSinglePageApplication, aquele tipo de aplicação quenão recarrega a páginadurante seu uso e que depende única e exclusivamente deJavaScriptparafuncionar.Nãotemosumsistemaderoteamentoeoutros recursos de uma aplicação mais completa criada porframeworks comoAngular, React, Ember entre outros.Mas issonãonosimpededecolocá-lanoar.

Valelembrarquenossaaplicaçãoéconstituídadeduaspartes.Aprimeira, a aplicação cliente, aquela que rodanonavegador, asegunda, a API do backend. Focaremos apenas o processo dedeploy (colocar no ar) da aplicação cliente, foco desta obra.Todavia,nadaimpedequeoleitorimplementeaAPIutilizadapelocurso utilizando outras linguagens como PHP, Java, C#, etc.realizando o deploy de sua API utilizando o serviço dehospedagem que mais se familiarizar. Aliás, é muito comumaplicações Single Page Application realizarem essa divisãomarcanteentreodeploydoclienteedaAPI.

Uma maneira rápida e gratuita sem grande burocracia derealizarmosodeploydanossaaplicaçãoéatravésdoGitHubPages(https://pages.github.com/).Oautorpresumequeoleitorjátenhauma conta no GitHub, configurado a chave de acesso em suamáquina, saiba clonar um repositório já existente e já tenhainstaladooGitemsuamáquina.ParaoleitorquenuncatrabalhoucomGitHub, esta é uma boa oportunidade de criar sua conta e

20.20 DEPLOY DO PROJETO NO GITHUBPAGES

494 20.20DEPLOYDOPROJETONOGITHUBPAGES

Page 518: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

seguirotutorialnoprópriositehttps://github.com/.

ParacriarmosumapáginanoGitHubPagesprecisamoscriarum novo repositório obrigatoriamente com a estruturausuario.github.io.Ousuariononomedorepositórioéonomedo nosso usuário cadastrado no GitHub. Por exemplo, orepositório flaviohenriquealmeida.github.io inicia comflaviohenriquealmeida,usuáriodoautordestelivro.Depoisdecriado,cloneorepositórioemsuamáquinaemqualquerdiretórioquenãosejaomesmodanossaaplicação:

//clonandoorepositório,useoendereçodoseurepositório.

[email protected]:flaviohenriquealmeida/flaviohenriquealmeida.github.io.git

Excelente! Agora, vamos voltar para nosso projeto e gerar obuilddeprodução:

npmrunbuild-prod

Dentro de instantes, será criada a pasta client/dist comtodososarquivosdonossoprojeto.Vamoscopiaroconteúdodapastaclient/dist paradentrododiretóriodo repositórioqueacabamosdeclonar.

No terminal, agora dentro da pasta do repositório, vamosexecutaroscomandos:

gitadd.gitcommit-am"Primeirocommit"gitpush

Pronto,nossoprojetojáestánoar.Paraacessá-loacessamosaURL https://nomedousuario.github.io/, no exemplo queacabamos de ver, a URL é

20.20DEPLOYDOPROJETONOGITHUBPAGES 495

Page 519: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

https://flaviohenriquealmeida.github.io/ . Contudo, hádoisproblemasqueprecisamserdestacados.

Nossaaplicaçãoécarregadanonavegador,porémnãoécapazde acessar nossa API mesmo utilizando o endereçohttp://localhost:3000.IssoaconteceporqueoGitHubpagessópermitirárequisiçõesAjaxparadomíniosqueutilizemhttpsenãohttp.CasovocêqueiradesenvolversuaprópriaAPIparasuaaplicação não esqueça desse detalhe, inclusive precisará habilitar CORS (https://pt.wikipedia.org/wiki/Cross-origin_resource_sharing)casocontrárioseunavegadorserecusaráa realizar a requisição Consulte a documentação da suaplataforma/linguagem preferida para criação de APIs para sabercomoprocedernahabilitaçãodeCORS.

OutropontoéqueaURLdaAPIutilizadaemnossaaplicaçãoaponta para um servidor local, de desenvolvimento. Porém,quandonossaaplicaçãoentraemprodução,oendereçodeveráserodaAPIqueestánoarenãooendereçolocaldedesenvolvimento.Dessa forma,precisamos fazer comquenossobuilddeproduçãoseja capaz de levar em consideração outro endereço diferente dehttp://localhost:3000, isto é, que considere o endereço deumaAPIemprodução.Comofaremos isso?Éoqueveremosnapróximaseção.

O Webpack já vem por padrão com o pluginwebpack.DefinePlugin. Este plugin permite definir variáveisquesóexistirãoduranteoprocessodebuildequepodemreceber

20.21ALTERANDOOENDEREÇODAAPINOBUILDDEPRODUÇÃO

496 20.21ALTERANDOOENDEREÇODAAPINOBUILDDEPRODUÇÃO

Page 520: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

diferentes valores de acordo com o tipo de build que estamosrealizando.

Primeiro, vamos declarar a variável SERVICE_URL quereceberá como valor padrão o endereçohttp://localhost:3000.Éumaexigênciaqueastringatribuídaà variável seja transformada em um JSON através deJSON.stringify().Emseguida,vamosatribuirumnovovaloràvariáveldentrodacondiçãoifdeprodução:

//client/webpack.config.js

//códigoanterioromitido

letSERVICE_URL=JSON.stringify('http://localhost:3000');

if(process.env.NODE_ENV=='production'){

SERVICE_URL=JSON.stringify("http://enderecodasuaapi.com");

//códigoposterioromitido

Agora,vamosadicionaropluginnalistadeplugins:

//client/webpack.config.js

//códigoanterioromitido

letSERVICE_URL=JSON.stringify('http://localhost:3000');

if(process.env.NODE_ENV=='production'){

SERVICE_URL=JSON.stringify("http://enderecodasuaapi.com");

plugins.push(newwebpack.optimize.ModuleConcatenationPlugin());plugins.push(newbabiliPlugin());plugins.push(newoptimizeCSSAssetsPlugin({cssProcessor:require('cssnano'),cssProcessorOptions:{discardComments:{

20.21ALTERANDOOENDEREÇODAAPINOBUILDDEPRODUÇÃO 497

Page 521: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

removeAll:true}},canPrint:true}));}

//ADICIONANDOOPLUGINNALISTA

plugins.push(newwebpack.DefinePlugin({SERVICE_URL}));

//códigoposterioromitido

Excelente! Por fim, precisamos alterarNegociacaoService.js e app.js para que façam uso davariávelSERVICE_URL.

Alterando client/app-

src/domain/negociacao/NegociacaoService.js:

//client/app-src/domain/negociacao/NegociacaoService.js

//códigoanterioromitido

obtemNegociacoesDaSemana(){

//PERCEBAMAMUDANÇA,UTILIZANDOTEMPLATELITERAL!

returnthis._http.get(`${SERVICE_URL}/negociacoes/semana`)

//códigoomitido}

obtemNegociacoesDaSemanaAnterior(){

returnthis._http.get(`${SERVICE_URL}/negociacoes/anterior`)//códigoomitido}

498 20.21ALTERANDOOENDEREÇODAAPINOBUILDDEPRODUÇÃO

Page 522: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

obtemNegociacoesDaSemanaRetrasada(){

returnthis._http.get(`${SERVICE_URL}/negociacoes/retrasada`)

//códigoomitido}

//posteriorcódigoomitido

Porfim,alterandoclient/app-src/app.js:

//client/app-src/app.js

//códigoanterioromitido

fetch(`${SERVICE_URL}/negociacoes`,config).then(()=>console.log('Dadoenviadocomsucesso'));

Com as alterações que fizemos, os projetos criados com osscriptsbuild-prodebuild-devterãoendereçosdiferentesdeAPI. Aliás, vale lembrar que o Webpack Dev Server levará emconsideração o mesmo endereço de API do build dedesenvolvimento, faz todo sentido pois este servidor é para serusadoapenaslocalmente.

Porfim,quandousamosoWebpackDevServernãofazmuitosentido exibirmos no terminal as milhares de mensagens decompilação e criação dos nossos bundles. Queremos apenasmensagens de erro durante o build. Podemos resolver issofacilmente adicionando a propriedade devServer no arquivoclient/webpack.config.js:

//client/webpack.config.js

//códigoanterioromitido

plugins,devServer:{

20.21ALTERANDOOENDEREÇODAAPINOBUILDDEPRODUÇÃO 499

Page 523: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

noInfo:true}}

ApropriedadedevServer:{noInfo:true} indicaqueapenasasmensagensdeerroserãoexibidasnoconsoleenquantoservirmosnossaaplicaçãoatravésdoWebpackDevServer.

Aolongodestelivro,aplicamosdesdeoinícioomodeloMVCepadrõesdeprojetospararesolvermosproblemas.Otempotodotransitamosentreosparadigmasorientadoaobjetoseofuncional.

FomosintroduzidosgradativamenteaosrecursosdalinguagemJavaScriptdasversõesES5,ES2015(ES6),ES2016(ES7), inclusive2017(ES8),importantesparanossoprojeto.

Antecipamos o futuro usando recursos que ainda estão emprocesso de consolidação na linguagem, graças ao uso de umtranscompilador.AprendemosWebpackeatédeployrealizamos!

Caroleitor,aotrilharseucaminhoatéestaseção,vocêacabadese tornarumCangaceiro JavaScript.Cada linhade códigopodelheserútilnoseudiaadia,ouatémesmoservirdeinspiraçãoparaquevocêcriesuasprópriassoluções.Paratodosvocês,meusvotosdesucesso.

Por fim, é possível acompanhar todas as propostas demudanças na linguagem JavaScript emhttps://github.com/tc39/proposals, para se manter atualizado.Inclusive, a partir deste endereço, você saberá o estágio atual daimplementação de Decorators na linguagem. É importante ficar

20.22CONSIDERAÇÕESFINAIS

500 20.22CONSIDERAÇÕESFINAIS

Page 524: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

atentoparaosestágios(stages)daspropostas.Sãoeles:

Stage0:qualquerdiscussão,ideia,mudançaouadiçãoque ainda não foi submetida formalmente comoproposta.

Stage 1: uma proposta é formalizada e espera-seabordar preocupações transversais, interações comoutras propostas e preocupações de implementação.As propostas nesta etapa identificam um problemadiscreto e oferecem uma solução concreta para esseproblema.

Stage2:apropostadeveoferecerumrascunhoinicialdaespecificação.

Stage 3: neste estágio avançado, o editor deespecificações e os revisores designados devem terassinadoaespecificação final.É improvávelqueumapropostadesta fasemudealémdascorreçõesparaosproblemasidentificados.

State4:apropostacheganesteestágioquandoexistempelomenosduas implementações independentes queaprovamostestesdeaceitação.

Olharofuturoéterumanovaperspectivadopresente.

20.22CONSIDERAÇÕESFINAIS 501

Page 525: tcxsproject.com.br Livros Hacker Gorpo Orko... · erros. Aprenderemos a automatizar esse processo. Capítulo 09: aplicaremos o padrão de projeto Proxy para implementarmos nossa própria

O código completo deste capítulo você encontra emhttps://github.com/flaviohenriquealmeida/cangaceiro-javascript/tree/master/20

502 20.22CONSIDERAÇÕESFINAIS