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

Seguindo com nossa série, vamos agora fazer funcionar o método POST do nosso módulo Ajax!

Testando o método POST

No último artigo, criamos um describe() para testar o método get(). Faremos o mesmo agora para o método post(). No arquivo tests/test.ajax.js:

1
2
3
4
5
6
7
8
9
describe( 'Test `post` method', function() {
it( 'Should return an object', function( done ) {
var ajax = new Ajax();
ajax.post( 'http://localhost:3000/api/user/joao' ).done(function( response ) {
response.should.be.an( 'object' );
done();
});
});
});

O método post() na nossa API de testes retorna um objeto com os dados do usuário passado. Estamos chamando os dados do usuário joao, então, nosso primeiro teste, é verificar se a requisição nos retorna um objeto.

Obviamente, o teste não passa, pois ainda não implementamos a funcionalidade para o método post() no nosso módulo. Sem mais delongas, vamos fazer isso agora mesmo. No arquivo src/ajax.js:

1
2
3
4
5
6
7
$public.post = function post( url ) {
var xhr = new XMLHttpRequest();
xhr.open( 'POST', url || '', true );
xhr.addEventListener( 'readystatechange', $private.handleReadyStateChange, false );
xhr.send();
return $private.promises();
};

O método post na nossa API recebe via URL o parâmetro slug, onde iremos fazer algo com os dados desse usuário. No exemplo, nós simplesmente retornamos os dados, mas em uma API real, provavelmente iremos alterar esses dados, ou tratar de alguma forma.

Por ser simples, a implementação no nosso módulo é basicamente a mesma do método get(), com a diferença do método open(), onde passamos o atributo POST, ao invés de GET.

Agora nosso teste passa! Estamos com todos os testes verdes, mas temos código repetido. Já sabe o que temos que fazer né? Isso mesmo: REFACTORY!

Esse é o momento em que deixamos nosso código um pouco melhor, mais legível, muitas vezes removendo código - pois menos é mais :D - mas sempre mantendo os testes passando. Não podemos implementar nada novo nesse momento, apenas melhorar o que já temos :)

Vamos então fazer o refactory. Criaremos um novo método para envolver o código repetido, e passaremos por parâmetro somente o tipo e a URL. No arquivo src/ajax.js:

1
2
3
4
5
6
7
$private.XHRConnection = function XHRConnection( type, url ) {
var xhr = new XMLHttpRequest();
xhr.open( type, url || '', true );
xhr.addEventListener( 'readystatechange', $private.handleReadyStateChange, false );
xhr.send();
return $private.promises();
};

E os nossos métodos get() e post() agora ficarão assim:

1
2
3
4
5
6
7
$public.get = function get( url ) {
return $private.XHRConnection( 'GET', url );
};

$public.post = function post( url ) {
return $private.XHRConnection( 'POST', url );
};

Se executarmos nossos testes, veremos que tudo continua passando lindamente! Refactory executado com sucesso \o/

Como disse acima, na maioria dos casos, os métodos post, put e delete podem receber os parâmetros implicitamente, ao invés de enviar via URL. Vamos então preparar nossa API para receber um POST através da URL http://localhost:3000/user, e passar o parâmetro implicitamente.

Primeiro vamos precisar instalar o body-parser, que vai parsear os dados que recebermos via POST em req.body. Instale com o comando abaixo no seu terminal:

1
npm i --save-dev body-parser

Agora, vamos adicionar o body-parser à nossa API. No início do nosso arquivo api/app.js, vamos adicionar o body-parser:

1
var bodyParser = require( 'body-parser' );

Agora, antes da chamada onde setamos o header res.setHeader( 'Access-Control-Allow-Origin', '*' );, vamos dizer ao connect que ele deve usar o middleware do bodyParser:

1
2
app.use( bodyParser.urlencoded({ extended: false }) );
app.use( bodyParser.json() );

Vamos também fazer uma pequena alteração na função postResponse():

1
2
3
4
5
function postRequest( req, res, next ) {
var user = req.params.slug || req.body.slug;
res.setHeader( 'Content-Type', 'application/json' );
res.end( JSON.stringify( users[ user ] ) );
}

Essa alteração é para que possamos obter dados vindo tanto da URL, com req.params.slug, como os dados enviados no corpo da requisição, com req.body.slug. Como vamos sempre usar um ou outro, podemos tratar dessa forma, com ||.

E por fim, vamos criar mais um endpoint para nossa nova requisição à http://localhost:3000/user:

1
2
router.post( '/api/user', postRequest );
router.post( '/api/user/:slug', postRequest );

A linha 2, já existe na nossa API, somente adicionamos a linha 1. Vamos testar nossa API com o Postman, e verificar se o request retorna a requisição que desejamos:

Pronto! Tudo funciona corretamente! Só precisamos enviar o header x-www-form-urlencoded na nossa requisição :D

Se ficou em dúvida de como o arquivo deve ficar, consulte o repositório onde estamos desenvolvendo nosso módulo.

Mas para não ficarmos só no teste manual, vamos adicionar mais um teste no nosso arquivo tests/test.ajax.js, para essa URL:

1
2
3
4
5
6
7
8
it( 'Should return data about `joao`', function( done ) {
var ajax = new Ajax();
ajax.post( 'http://localhost:3000/api/user', 'slug=joao' ).done(function( response ) {
console.log( response );
response.name.should.be.equal( 'João da Silva' );
done();
});
});

A novidade aqui é que passamos mais um parâmetro no método post(): os dados que serão enviados ao servidor, em formato de query string. Passamos o parâmetro slug, que é o que nossa API espera, com o slug do usuário que queremos os dados de volta. Para passar mais dados, você pode usar o formato: dado1=valor1&dado2=valor2&dado3=valor3.

Vamos agora implementar essa funcionalidade em src/ajax.js. Primeiro, passamos o parâmetro para o método post():

1
2
3
$public.post = function post( url, data ) {
return $private.XHRConnection( 'POST', url, data );
};

Agora vamos alterar o método da conexão, para receber os dados e enviar ao servidor, com o header correto:

1
2
3
4
5
6
7
8
$private.XHRConnection = function XHRConnection( type, url, data ) {
var xhr = new XMLHttpRequest();
xhr.open( type, url || '', true );
xhr.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded' );
xhr.addEventListener( 'readystatechange', $private.handleReadyStateChange, false );
xhr.send( data );
return $private.promises();
};

Passamos o parâmetro data no método, que será passado para xhr.send(), e adicionamos o header application/x-www-form-urlencoded.

Agora nosso método faz requisições GET e POST, e todos os testes estão passando! o/

Os métodos put() e delete() são praticamente o post(), somente mudando para o verbo correto - PUT e DELETE.

Podemos ainda melhorar o tipo de envio de dados: ao invés de passar uma query string, poderíamos passar um objeto e, no módulo, tratar esses dados, convertendo para query string antes de fazer o envio.

Como você pode ver, tem muitas coisas que podemos fazer para melhorar: o envio dos dados, headers dinâmicos, credenciais para validação no servidor (usuário e senha), até que todos os métodos do objeto XMLHttpRequest estejam cobertos. A série acaba por aqui, mas vou fazendo essas implementaçôes aos poucos! Se quiser acompanhar, dê um watch no repositório: https://github.com/fdaciuk/ajax

Dúvidas? Comente!

Até o próximo o/

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