Javascript - Criando um módulo Ajax com Promises - Parte 1

No artigo anterior, você viu como as Promises resolvem o problema de callback hell. Agora vamos ver na prática como criar nossas próprias Promises!

O que iremos fazer?

Nosso desafio é criar um módulo para usar Ajax, sem qualquer library ou framework, e que possamos obter os resultados das requisições via Promises.

O nosso módulo será feito usando a técnica de TDD, ou seja, primeiro criamos um teste com o resultado esperado, e então partimos para o código em si :)

E o nosso primeiro passo é criar a estrutura do nosso projeto!

Você pode acompanhar o andamento desse módulo através desse repositório no Github :D

O que dá pra fazer com Ajax?

Acho que o exemplo mais comum é o carrinho de compras com Ajax.

Estrutura do projeto

Execute o comando abaixo no seu terminal, em um diretório separado para o projeto:

1
mkdir src tests && touch .editorconfig .jshintrc .gitignore gulpfile.js index.html src/ajax.js tests/test.ajax.js && echo "{}" > package.json

Isso irá gerar a seguinte estrutura:

1
2
3
4
5
6
7
8
9
10
.
├── .editorconfig
├── .gitignore
├── .jshintrc
├── gulpfile.js
├── package.json
├── src
│   └── ajax.js
└── tests
└── ajaxTest.js

Com nosso projeto criado, vamos montar nosso ambiente de testes :D

Montando o ambiente de testes

Se você ainda não está habituado com testes, ou nunca escreveu um, ou ainda não sabe para o que realmente eles servem, é muito importante que você leia esse artigo antes de continuar :D

Vamos instalar os módulos do Gulp necessários para que possamos fazer os testes no nosso módulo:

1
npm i --save-dev gulp gulp-mocha gulp-istanbul gulp-load-plugins chai

Configurações dos arquivos

Nosso arquivo .jshintrc ficará assim:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"curly" : true,
"eqeqeq" : true,
"immed" : true,
"latedef" : true,
"newcap" : true,
"noarg" : true,
"sub" : true,
"undef" : true,
"boss" : true,
"eqnull" : true,
"node" : true,
"globals" : {
"document" : true,
"window" : true
}

}

Já o .editorconfig terá a seguinte configuração:

1
root = true
  
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = false
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
  
[*.md]
trim_trailing_whitespace = false

E o .gitignore:

1
node_modules/

E agora vamos configurar nosso gulpfile.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
'use strict';

var gulp = require( 'gulp' );
var g = require( 'gulp-load-plugins' )();

var allTestFiles = './tests/**/*.js';

gulp.task( 'assets', function() {
gulp.src([
'node_modules/gulp-mocha/node_modules/mocha/mocha.{js,css}',
'node_modules/chai/chai.js'
])
.pipe( gulp.dest( 'public' ) );
});

gulp.task( 'mocha', function() {
gulp.src([ allTestFiles ], { read : false })
.pipe(
g.mocha({ reporter: 'list' })
);
});

gulp.task( 'test', function( done ) {
gulp.src([ allTestFiles ])
.pipe( g.istanbul() )
.on( 'finish', function() {
gulp.src([ allTestFiles ])
.pipe( g.mocha() )
.on( 'error', done )
.pipe( g.istanbul.writeReports() )
.on( 'end', done );
});
});

gulp.task( 'default', [ 'assets' ], function() {
gulp.watch([ allTestFiles, 'src/ajax.js' ], [ 'test' ]);
});

E Enfim, nosso ‘index.html’:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Ajax module in VanillaJS</title>
<link rel="stylesheet" href="public/mocha.css" />
</head>
<body>
<div id="mocha"></div>
<script src="src/ajax.js"></script>
<script src="public/chai.js"></script>
<script src="public/mocha.js"></script>
<script>mocha.setup('bdd')</script>
<script src="tests/test.ajax.js"></script>
<script>mocha.run()</script>
</body>
</html>

Acessando essa index.html no seu navegador, você terá acesso à todos os testes, podendo acompanhá-los à medida em que forem sendo implementados :)

Detalhes do método

Antes de darmos início, nós precisamos definir o que precisa ser implementado no nosso módulo. Nessa etapa, não vamos definir código, mas sim como ele deve funcionar.

Basicamente, nós queremos que nosso módulo utilize Ajax (XMLHttpRequest), e que ele responda aos métodos get, post, put e delete.

A resposta deve vir no formato de Promises, com os métodos done e error.

Esse será nosso MVP.

Já pode colocar o Gulp pra rodar e ficar assistindo nossos arquivos, com o comando:

1
gulp

Usando o padrão UMD

Para que nosso módulo funcione corretamente com CommonJS, AMD ou sem nenhum padrão de módulos específico, precisamos usar o formato UMD.

Nosso módulo src/ajax.js iniciará com a seguinte estrutura:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
;(function ( root, factory ) {
'use strict';
/* istanbul ignore next */
if ( typeof define === 'function' && define.amd ) {
define( 'Ajax', factory );
}
else if ( typeof exports === 'object' ) {
exports = module.exports = factory();
}
else {
root.Ajax = factory();
}
})(this, function() {
'use strict';

function Ajax() {
var $public = {};
var $private = {};

return $public;
}

return Ajax;
});

Das linhas 1 a 13, aplicamos o UMD. Agora nosso módulo é Universal :D

O comentário na linha 3, é para que o Istanbul ignore esse if, pois não teremos os 3 módulos juntos (AMD, CommonJS e sem nenhum), será somente um desses, então não temos como fazer com que toda essa parte seja coberta por testes. Logo, podemos ignorá-la.

Observação importante:

Se você tentar incluir esse módulo em qualquer um dos 3 formatos citados acima, vai ver que ele vai funcionar. Quando você não consegue dar cobertura a um código, como é o caso do UMD, você precisa, ao menos, garantir manualmente que tudo funciona. Não adianta comentar todo o código para que o Istanbul ignore, pois você estará enganando a si mesmo. Use essa feature com cautela.

Iniciando os testes

Agora sim estamos prontos para começar a codar! Nossa primeira asserção irá testar a interface do nosso módulo. Precisamos garantir que nosso módulo tenha os métodos get, post, put e delete.

Vamos então criar o teste para isso. A base do nosso arquivo tests/test.ajax.js ficará assim:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
;(function ( root, factory ) {
'use strict';

if ( typeof define === 'function' && define.amd ) {
define( function() {
require([ 'Ajax' ]);
});
}
else if ( typeof exports === 'object' ) {
exports = module.exports = factory( require( 'chai' ).should(), require( '../src/ajax' ) );
}
else {
root.Ajax = factory( root.chai.should(), root.Ajax );
}
})(this, function( should, Ajax ) {
'use strict';

describe( 'Test module interface', function() {
it( 'Should have `get` method', function() {
var ajax = new Ajax();
ajax.should.have.property( 'get' );
});
});
});

Vamos adicionar o UMD também para o nosso teste, pois ele ficará rodando no terminal, com Node. Assim nós poderemos acompanhar os resultados tanto no browser, como no terminal :)

Incluímos a biblioteca Chai, para fazer as asserções dos nossos testes.

Na linha 18, damos início ao teste:

O describe vai criar um wrapper com vários testes de uma parte específica do módulo. Nesse primeiro describe, vamos testar a interface do módulo.

Nosso primeiro teste, na linha 19, diz que nosso módulo “Deveria ter um método chamado get“.

Instanciamos o objeto do nosso módulo - new Ajax() - e fazemos a asserção, verificando se a propriedade get existe.

E o nosso teste quebra, pois esse método não existe:

E esse é exatamente o comportamento esperado, pois ainda não temos código no nosso módulo que o faça funcionar! Esse é o primeiro passo do TDD: O RED.

Agora, você lembra qual o próximo passo? Após nosso teste quebrar, pois adicionamos um teste para verificar algo que ainda não existe, vamos tentar fazer o teste passar, com o mínimo de código possível (baby steps):

Adicionamos então ao nosso arquivo src/ajax.js:

1
2
3
4
5
...
var $public = {};

$public.get = function get() {};
...

Pronto, agora temos um método get! Vamos ver se nosso teste passou?

Agora sim! Temos o GREEN, pois nosso teste passou.

Podemos ainda acompanhar no terminal como está a cobertura do nosso código:

Como estamos usando TDD, a tendência é que tenhamos sempre 100% do nosso código coberto por testes! :D

O próximo passo é o BLUE, ou Refactory. Nesse caso, não precisamos refatorar nosso código, pois não tem nada a ser refatorado.

Com o passar dos testes, dependendo do baby step que nós utilizarmos, precisaremos refatorar o código para que ele se mantenha em ordem. O passo do refactory serve somente para limpar o código já existente. Você nunca deve incluir nenhuma funcionalidade a mais no momento do refactory. Os testes que passam devem continuar passando, mas a leitura do código deve ser melhor do que você deixou da última vez :)

Curioso para ver como isso continua? Então aguarde o próximo post! :D

Fica como lição de casa pra você fazer os outros 3 testes com os métodos post, put e delete!

Nos próximos artigos, vamos continuar criando nosso módulo, até que ele resolva nosso primeiro objetivo: ter os 4 métodos e responder com as Promises.

Ficou com dúvidas? Comentae!

Até lá!

Sobre o #1postperday: https://blog.da2k.com.br/2014/12/31/um-post-por-dia/

Tem alguma sugestão para os próximos posts do #1postperday? Deixe ela aqui: https://github.com/fdaciuk/fdaciuk.github.io/issues/1