Skip to content

Instantly share code, notes, and snippets.

@igorlima
Last active December 31, 2015 03:49
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save igorlima/7930016 to your computer and use it in GitHub Desktop.
Save igorlima/7930016 to your computer and use it in GitHub Desktop.
JavaScript de forma assíncrona e legível

Gittip Donate Button

A programação assíncrona possui a vantagem de gerar códigos perfomáticos. Em certos casos, a implementação de diversas funções assíncronas encadeadas através de funções callback pode prejudicar a leitura e a manutenção do código. Para demonstrar esse encadeamento, vamos utilizar um trecho de código que utiliza a API do Selenium 2. Baseado em um exemplo do site do SauceLabs.

A API do Selenium WebDriver pode ser utilizada por diversas linguagem de programação, porém, em nosso exemplo, iremos utilizar o NodeJS (JavaScript) e o gerenciador de pacotes NPM, que podem ser baixados no site oficial. O NPM é necessário para instalar o PhantomJS e o WD, utilizando o seguinte script:

npm install -g phantomjs

npm install wd

O NodeJS possui dois tipos de dependências: global ou local. Quando uma dependência é global, o pacote passa a ser executável, tornando possível a utilização da dependência através da linha de comando. Já as dependências locais são instaladas no diretório corrente, dentro de node_modules.

O primeiro script, utilizando o parâmetro -g, instala o PhantomJS como dependência global, que é um WebKit headless totalmente em JavaScript e possui suporte rápido e nativo para vários padrões web como manipulação de DOM, seletores CSS, JSON, Canvas e SVG.

Já o segundo script, instala como dependência local o WD, que é um cliente NodeJS para facilitar o acesso à API do Selenium 2 e suporta métodos como: fazer requisições GET e POST, clicar no botão VOLTAR do navegador, fazer refresh no navegador, pegar um printscreen da tela atual, redimensionar e mover a janela do navegador, submeter formulário, digitar texto, usar cookies, selecionar um elemento DOM, clicar e mover um elemento, etc.

Após a instalação das dependências, vamos criar um arquivo com vários callbacks encadeados. Esse código possui seis passos: (i) abrir o navegador, (ii) acessar uma página de teste, (iii) verificar o título da página, (iv) submeter um formulário, (v) verificar a url da página após enviar o formulário e (vi) fechar o navegador. Algo bem simples. Suficiente para ressaltar a quantidade de callbacks encadeados.

Um detalhe importante sobre assincronismo é que, na maioria dos casos, os callbacks possuem como parâmetro uma variável de erro, que serve para impedir a execução dos callbacks subsequentes, caso haja algum problema.

Criado o arquivo exemplo, é preciso, em um outro terminal, rodar o PhantomJS em modo WebDriver, digitando o seguinte comando:

phantomjs --webdriver=localhost:8910

Com o PhantomJS rodando em segundo plano, execute o exemplo usando o node. Segue o comando e uma ilustração do resultado obtido:

node um-exemplo-COM-varios-callbacks-encadeados.js

ilustração do resultado obtido após executar o exemplo COM vários callbacks encadeados

Para evitar tantos callbacks encadeados, vamos utilizar a biblioteca Async que prover várias funções que facilitam a programação assíncrona em JavaScript. Nesse exemplo usaremos a função waterfall. Uma alternativa mais leve para código Front-End é a biblioteca Underscore. Para instalar o Async, utilize o seguinte script:

npm install async

Agora vamos criar um outro arquivo sem tantos encadeamentos e executá-lo. Lembre-se que o PhantomJS ainda deve estar rodando em segundo plano. Segue o comando e uma ilustração do resultado obtido:

node um-exemplo-SEM-varios-callbacks-encadeados.js

ilustração do resultado obtido após executar o exemplo SEM vários callbacks encadeados

Esse trecho de código exemplifica como vários callbacks encadeados podem ser evitados com o uso de uma estrutura de controle. Muito obrigado.

var webdriver = require('wd'),
assert = require('assert'),
browser = webdriver.remote({
hostname: "localhost",
port: 8910
});
browser.init({}, function(erro, id_da_sessao, recursos_webdriver) {
console.log('navegador aberto');
browser.get("http://saucelabs.com/test/guinea-pig", function(erro) {
console.log('pagina de teste aberta');
browser.title(function(erro, title) {
console.log('verificando titulo da pagina...');
assert.ok( title.indexOf('I am a page title - Sauce Labs')===0, 'titulo NAO esta correto');
browser.elementById('submit', function(erro, elemento) {
console.log('botao enviar encontrado');
browser.clickElement(elemento, function(erro) {
console.log('botao clicado');
browser.eval("window.location.href", function(erro, href) {
console.log('verificando url da pagina...');
assert.ok(href.indexOf('guinea')>0, 'pagina NAO esta correta');
browser.quit(function(erro){
console.log('navegador fechado');
});
});
});
});
});
});
});
var webdriver = require('wd'),
async = require('async'),
assert = require('assert'),
browser = webdriver.remote({
hostname: "localhost",
port: 8910
});
async.waterfall([
function(callback_navegador_aberto) {
browser.init({}, callback_navegador_aberto);
},
function(id_da_sessao, recursos_webdriver, callback_pagina_aberta) {
console.log('navegador aberto');
browser.get("http://saucelabs.com/test/guinea-pig", callback_pagina_aberta);
},
function(callback_titulo) {
console.log('pagina de teste aberta');
browser.title(callback_titulo);
},
function(title, callback_elemento_encontrado) {
console.log('verificando titulo da pagina...');
assert.ok( title.indexOf('I am a page title - Sauce Labs')===0, 'titulo NAO esta correto');
browser.elementById('submit', callback_elemento_encontrado);
},
function(elemento, callback_botao_clicado) {
console.log('botao enviar encontrado');
browser.clickElement(elemento, callback_botao_clicado);
},
function(callback_verificar_url) {
console.log('botao clicado');
browser.eval("window.location.href", callback_verificar_url);
},
function(href, callback_navegador_fechado) {
console.log('verificando url da pagina...');
assert.ok(href.indexOf('guinea')>0, 'pagina NAO esta correta');
browser.quit(callback_navegador_fechado);
},
function(callback_final) {
console.log('navegador fechado');
callback_final();
}
], function(erro){
erro && console.log('algum erro ocorreu', erro);
});
@ronaldoveras
Copy link

Muito bom. Deixa o código bem mais legível. Tutorial massa.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment