Asynchronous Refactoring - Refactoring JavaScript
Asynchronous Refactoring - Refactoring JavaScript
Refatoração Assíncrona
Async tornou-se a norma para muitas APIs.Por exemplo, um vindo deum paradigma
principalmente síncrono (linguagem ou estilo) pode esperar que o http módulo do
nó se comporte assim:
console.log(response.body);
Mas isso será impresso undefined . A razão é que nossa response constante é, na
verdade, nomeada com um pouco de otimismo. O valor de retorno de http.get é um
ClientRequest objeto, não uma resposta. Ele descreve a solicitação, mas não o
resultado.
Há boas razões para usar chamadas HTTP remotas assíncronas. Se nossa chamada re-
mota fosse síncrona, ela necessariamente “pararia o mundo” (STW) e manteria nosso
programa esperando. Ainda assim, quando olhamos para a alternativa imediata, esse
compromisso pode parecer frustrantemente complexo:
console.log(chunk.toString());
});
});
P O R Q U E TO S T R I N G ( ) ?
chunk é um Buffer dos personagens. Tente deixar o toString() , e você verá algo como
<Buffer 3c 21 44 4f 43 ... > .
Este é claramente um processo mais complicado do que nossa ideia de como uma API
HTTP síncrona funcionaria. Ele força não apenas o estilo assíncrono, mas também o
paradigma funcional.Temos duas funções assíncronas internas. Se você tentar se ape-
gar ao uso de codificação de estilo síncrono além dessa chamada inicial, sua frustra-
ção não vai parar:
theResult.push(chunk.toString());
});
});
console.log(theResult);
Agora podemos obter uma matriz dos pedaços da resposta, certo? Não. Isso imprime
uma matriz vazia: [] .
});
});
A última linha é impressa antes que a função mais interna tenha a chance de executar
a terceira linha (aliás, ela imprime essa instrução de log duas vezes). Então, se é ape-
nas uma questão de esperar , e queremos fazer algo com os pedaços, devemos ser ca-
pazes de esperar, certo?Mas quanto tempo esperamos? 500 milissegundos é tempo
suficiente?
theResult.push(chunk.toString());
});
});
setTimeout(function(){console.log(theResult)}, 500);
É difícil dizer. Usando essa abordagem, podemos acabar com um array vazio ou um
array com um ou mais elementos. Se realmente queremos ter certeza de que os dados
estão no lugar antes de registrá-los (ou fazer qualquer outra coisa com eles), acabare-
mos esperando demais e amarrando nosso programa também. Se esperarmos muito
pouco, perderemos alguns dados. Portanto, esta solução não é muito boa. Não só é im-
previsível, mas envolve definir o estado através de um efeito colateral.
SETTIMEOUT E O LOOP DE EVENTOS
Há duas questões a ter em mente com este tipo de situação. Primeiro, o loop de even-
tos ficará preso fazendo outra coisa em 300 milissegundos? Pode ser.
console.log('the egg');
console.log('the egg');
O ovo vence novamente, mas as galinhas estão por toda parte. Tente executá-lo em di-
ferentes consoles do navegador e no node. No momento da redação deste artigo, a or-
dem no Chrome e no Firefox é diferente, mas consistente. A ordem no nó varia de
execução para execução.
levelOne(function(){
levelTwo(function(){
levelThree(function(){
levelFour(function(){
});
});
});
});
Vamos voltar ao código da última seção. Claramente, vamos querer alguma forma as-
síncrona aqui, mas como gerenciamos a complexidade e o aninhamento necessários
com algo assim?
console.log(chunk.toString());
});
});
Observe que isso pode ser muito mais complicado com muito mais níveis de aninha-
mento. Este é o inferno de retorno e a pirâmide da destruição em ação. Não há uma
linha estrita para quanto recuo constitui uma pirâmide de destruição ou quantos re-
tornos de chamada colocam você no “inferno”.
Já temos uma estratégia para isso das partes anteriores do livro. Nós simplesmente
desanonimizamos e extraímos uma função como esta:
const http = require('http');
function printBody(chunk){
console.log(chunk.toString());
};
result.on('data', printBody);
});
function printBody(chunk){
console.log(chunk.toString());
};
function getResults(result){
result.on('data', printBody);
};
http.get('http://refactoringjs.com', getResults);
Agora ficamos com duas funções nomeadas e a última linha como um pedaçode cli-
ente ou código de chamada .
bodyArray.push(chunk);
};
console.log(bodyArray.join(''))
};
result.on('data', saveBody);
result.on('end', printBody);
};
http.get('http://refactoringjs.com', getResults);
Se você decidir mover as coisas para um objeto, uma coisa a ter em mente é a facili-
dade com que o this contexto será descartado:
const getBody = {
bodyArray: [],
saveBody: function(chunk){
this.bodyArray.push(chunk);
},
printBody: function(){
console.log(this.bodyArray.join(''))
},
getResult: function(result){
}
};
http.get('http://refactoringjs.com', getBody.getResult);
Isso significa que na última linha, getBody.getResult não é uma função. Alterar
essa última linha nos leva um pouco mais longe:
http.get('http://refactoringjs.com', getBody.getResult.bind(getBody));
const getBody = {
bodyArray: [],
saveBody: function(chunk){
this.bodyArray.push(chunk);
},
printBody: function(){
console.log(this.bodyArray.join(''))
},
getResult: function(result){
}
};
http.get('http://refactoringjs.com', getBody.getResult.bind(getBody));
Vale a pena colocar isso em um objeto? Vale a pena desativar o que era uma pirâmide
de destruição bastante rasa? Eliminamos o inferno de callback? Talvez não, mas é
bom ter opções. Vamos ficar com este formulário para iniciar a próxima seção.
Primeiro, contando com retornos de chamada para fazer o trabalho real do nosso pro-
grama, também contamos com os efeitos colaterais. Saímos do mundo simples de va-
lores de retorno. Não estamos apenas retornando valores de funções. Estamos execu-
tando-os (e retornando imediatamente sem nada de valor ) , mas os retornos de cha-
mada serão executados em algum momento . Nossa base fundamental para alcançar a
confiança depende de saber o que está acontecendo em nosso código, e a assíncrona
em JavaScript, como a conhecemos até agora, mina completamente isso.
Segundo, e relacionado ao primeiro ponto, não temos testes! Mas o que testaríamos
afinal? Vamos começar com nossas antigas suposições sobre como o teste funciona e
tentar testar algum valor conhecido. Sabemos que depois que a função for executada,
bodyArray ela deverá conter alguns dados. Em outras palavras, seu comprimento
não deve ser igual a zero.
Com isso em mente, vamos trabalhar como testebibliotecado Capítulo 9 chamado fita.
É um pouco mais simples que o mocha, e você pode executá-lo apenas executando .
Você pode instalá-lo com . node whatever-you-call-the-file.js npm install
tape
const getBody = {
...
http.get('http://refactoringjs.com',
getBody.getResult.bind(getBody));
assert.notEqual(getBody.bodyArray.length, 0);
assert.end();
});
Por quê? Por ser executado antes bodyArray tem chance de ser atualizado!
Você pode instintivamente querer rastejar de volta para um mundo síncrono confor-
tável com uma atualização para o teste como esta:
http.get('http://refactoringjs.com',
getBody.getResult.bind(getBody));
setTimeout(() => {
assert.end();
}, 3000);
});
Então, como podemos adiar nossa afirmação até que bodyArray seja preenchido?
Como estamos testando um efeito colateral e nosso código não é muito “amigável para
retorno de chamada”, ficamos presos a setTimeout menos que reescrevamos o có-
digo ou adicionemos algumas maquinações estranhas aos nossos testes. Em um caso
ideal, printBody receberia um retorno de chamada que seria executado para indicar
que tudo está pronto.
Trocando uma ferramenta cega por outra, podemos eliminar nossa dependência
setTimeout substituindo a função existente que indica quando as coisas são feitas:
getBody.printBody = function(){
assert.end();
}
http.get('http://refactoringjs.com',
getBody.getResult.bind(getBody));
});
Isso pode parecer ultrajante, por algumas razões. Primeiro, ele sobrescreve uma fun-
ção que podemos querer testar mais tarde (teremos que restaurar a implementação
original?). Em segundo lugar, as mudanças na printBody implementação do 's po-
dem levar a alguma complexidade mais tarde. Sem introduzir zombaria ou tentar ali-
mentar um retorno de chamada em cada função e evento, poderíamos fazer um
pouco melhor:
const getBody = {
...
printBody: function(){
console.log(this.bodyArray.join(''))
this.allDone();
},
allDone: function(){}
getBody.allDone = function(){
assert.end();
}
http.get('http://refactoringjs.com',
getBody.getResult.bind(getBody));
});
getBody.allDone = function(){
assert.end();
}
http.get('http://refactoringjs.com',
getBody.getResult.bind(getBody));
});
getBody.bodyArray = [];
getBody.allDone = function(){ };
http.get('http://refactoringjs.com',
getBody.getResult.bind(getBody));
assert.equal(getBody.bodyArray.length, 0);
assert.end();
});
function setup(){
getBody.bodyArray = [];
function teardown(){
getBody.allDone = function(){ };
setup();
getBody.allDone = function(){
teardown();
assert.end();
}
http.get('http://refactoringjs.com',
getBody.getResult.bind(getBody));
});
setup();
http.get('http://refactoringjs.com',
getBody.getResult.bind(getBody));
assert.equal(getBody.bodyArray.length, 0);
teardown();
assert.end();
});
Observe que o mocha e outras estruturas mais completas tentam lidar com a configu-
ração e a desmontagem em seu nome. Eles funcionam bem na maioria das vezes, mas
ter funções setup e explícitas teardown (como no último exemplo) lhe dá mais
controle.
T E S TA R PA R A L E L I Z A Ç Ã O
Nenhuma estrutura irá salvá-lo de testes executados em paralelo e sobrecarregar o estado com-
partilhado.A solução para isso é executar testes que compartilham o estado em série (como um
arquivo de teste de fita fará). E para aspectos díspares do código, dividi-los em módulos e dar a
cada um sua própria chance de executar de forma independente e em paralelo ainda permitirá
que você tenha execuções de teste paralelas e rápidas.
Arquitetonicamente, dividir seu código em módulos é provavelmente o que você queria fazer
de qualquer maneira, certo?
Se isso soa como muito trabalho, vá com mocha ou outra coisa que lide com a
configuração/desmontagem. Mas não se surpreenda se você ainda tiver um problema de para-
lelização ocasional (provavelmente resultando em falhas de teste).
function setup(){
getBody.bodyArray = [];
function teardown(){
getBody.allDone = function(){ };
getBody.allDone = testDouble.function();
testDouble.when(getBody.allDone()).thenDo(function(){
assert.notEqual(getBody.bodyArray.length, 0)
assert.end()
});
http.get('http://refactoringjs.com',
getBody.getResult.bind(getBody));
});
Quando fazemos um teste duplo como este para nossa função (também podemos fa-
zer isso para objetos inteiros), nosso código agora pode apenas “falsificar” a chamada
para allDone . Mais tipicamente, doubles são usados para evitar a execução de ope-
rações caras ou lentas (como chamar uma API externa), mas tenha cuidado ao usar
muito essa técnica, pois é possível falsificar tudo, o que resulta em testes inúteis. Uma
coisa a notar é o quão conveniente teardown é o nosso. É fácil reatribuir essa função
vazia, mas se estivéssemos criando duplicatas de mais funções (zombaria, stub, espio-
nagem, etc.), teardown isso poderia ficar bem complicado.
function setup(){
return Object.create(getBody);
};
newBody.allDone = testDouble.function();
testDouble.when(newBody.allDone()).thenDo(function(){
assert.notEqual(newBody.bodyArray.length, 0)
assert.end()
});
http.get('http://refactoringjs.com',
newBody.getResult.bind(newBody));
});
Em vez de ter que redefinir nosso objeto, podemos simplesmente usar um novo com
nossas execuções de teste. Nossa setup função pode não ser específica o suficiente
para cada situação, mas é perfeita para isso.
T E S TA M O S O S U F I C I E N T E ?
Dependendo da nossa confiança, podemos sempre adicionar mais testes. Nesse caso, podería-
mos ter optado por retornar a string HTML printBody e testá-la (provavelmente com uma re-
gex em vez de uma correspondência completa). Poderíamos ter feito um duplo para esta
chamada:
result.on('data', this.saveBody.bind(this));
Além disso, poderíamos testar o fato de que uma função foi chamada ou que foi chamada e não
produziu um erro .
Em muitos códigos assíncronos, os valores de retorno não são tão interessantes (ou geradores
de confiança) quanto saber quais funções foram chamadas e quais outros efeitos colaterais
ocorreram.
Nesta seção, criamos objetos e exploramos algumas opções de teste leves para código
assíncrono. Você pode se inclinar para ferramentas mais pesadas como mocha (como
usamos anteriormente) para testes e Sinon.JS (que não analisamos) para duplas. Ou
você pode experimentar ferramentas mais simples, como fita e testdouble.Você pode
até querer apenas raspar com setTimeout e assert ou wish declarações de tempos
em tempos.
Conforme discutimos no Capítulo 3 , você tem muitas opções para ferramentas e tes-
tes. Se algo parecer excessivamente complexo ou não fizer o que você precisa, você
sempre pode diminuir ou aumentar conforme necessário.Flexibilidade e clareza são
mais importantes do que bater em um parafuso com um martelo até que ele atinja a
parede.
Criar um objeto ajudou a manter os retornos de chamada organizados, mas essa não é
nossa única opção. Parte do que nos levou a essa solução foi a utilidade de ter um con-
têiner para armazenar nosso array de efeitos colaterais.Tivemos que fazer alguma
agregação com base na natureza de transmissão/emissão de eventos da http biblio-
teca do nó e nosso desejo de imprimir todo o corpo HTML de uma só vez. Se estivésse-
mos adicionando algo a uma página ou salvando um arquivo, poderíamos considerar
permitir que o arquivo ou DOM seja o próprio agregado e apenas passar os resultados
do fluxo para ele em vez de colocá-los em um formato intermediário (o array).
Vimos que passar uma função (retorno de chamada) para outra função é útil para
programação assíncrona, mas muda completamente a forma como trabalhamos nas
partes anteriores deste livro. Em vez de retornar algo valioso e agir sobre isso, esta-
mos deixando a função interna tomar as decisões (e sua função interna [e sua função
interna]). Esta é uma propriedadedo estilo de passagem de continuação (CPS)chamado
de inversão de controle (IoC) e, embora útil, tem algumas desvantagens:
Isso torna o teste mais difícil (embora parte disso seja apenas a natureza da pro-
gramação assíncrona).
É difícil misturar com código síncrono.
Os valores de retorno provavelmente não são mais importantes em toda a sequên-
cia de retornos de chamada. Isso nos faz confiar no teste de argumentos de retorno
de chamada para determinar valores intermediários.
function addOne(addend){
console.log(addend + 1);
};
addOne(2);
function two(callback){
callback(2);
};
O peso do algoritmo está agora no retorno de chamada, em vez da two função, que
simplesmente abre mão do controle e passa a 2 para o retorno de chamada. Como fi-
zemos antes, podemos nomear e extrair a função anônima, dando-nos isto:
function two(callback){
callback(2);
};
function addOne(addend){
console.log(addend + 1);
};
two(addOne);
O valor da função de chamada ( two ) é que ela fornece uma variável para o retorno
de chamada. Neste caso, isso é tudo o que faz. Se a two função precisasse que seu va-
lor viesse de alguma tarefa de execução mais longa, gostaríamos de uma versão de re-
torno de chamada (CPS). No entanto, porque two pode retornar imediatamente, o es-
tilo direto é bom, se não for preferível.
Vamos adicionar uma three função que precisa funcionar de forma assíncrona:
function three(callback){
setTimeout(function(){
callback(3);
},
500);
};
three(addOne);
function three(){
setTimeout(function(){
return 3
},
500);
function addOne(addend){
console.log(addend + 1);
};
addOne(three());
function addOne(addend){
console.log(addend + 1);
};
function three(callback){
setTimeout(function(){
callback(3);
},
500);
};
three(addOne);
Observe que também poderíamos ter escrito nossa addOne função para receber um
retorno de chamada, assim:
callback(addend + 1);
};
function three(callback){
setTimeout(function(){
},
500);
};
three(addOne);
O exemplo da última seção pode parecer redundante com nosso uso anterior da
http.get função, mas há quatro razões pelas quais a introduzimos:
assert.end();
});
});
Para fins de teste, estamos basicamente tratando result como se fosse um valor de
retorno. Em vez de testar o valor de retorno, estamos testando o parâmetro que é pas-
sado para o retorno de chamada. Quanto à leitura, poderíamos dizer o seguinte:
Veja como a addOne função e seu teste seriam diferentes se estivéssemos apenas re-
tornando o resultado:
function addOneSync(addend){
return addend + 1;
};
...
assert.equal(addOneSync(3), 4);
assert.end();
});
Aqui nós:
Um desses processos é muito mais simples, mas estamos vivendo em um mundo as-
síncrono a maior parte do tempo. Além disso, como vimos anteriormente, nossa
three função não tem o luxo de ter um analógico síncrono utilizável. Segue nosso
teste:
});
assert.end();
});
A three função recebe apenas um argumento. Essa é uma função, que deixamos anô-
nima. Essa função anônima recebe dois parâmetros, que são fornecidos como argu-
mentos quando a função anônima é chamada dentro de three . Um é o result e o
outro é o callback . Nossos testes confirmam que result é 3 e callback é
console.log .
testDouble.replace(console, 'log')
testDouble.verify(console.log(4));
testDouble.reset();
assert.end();
});
});
Promessas
Se você gosta de escrever JavaScript assíncrono, mas não gosta da bagunça que vem
com a inversão de controle, as promessas são para você.Vamos recapitular o que vi-
mos até agora.No estilo direto, você retorna valores de funções e usa esses valores de
retorno em outras funções.No CPS (estilo de passagem de continuação), você inverte o
controle pelo código de chamada fornecendo (e geralmente definindo inline) um re-
torno de chamada a ser executado pela função que é chamada. Os valores de retorno
tornam-se pouco melhores do que sem sentido e, em vez disso, os argumentos passa-
dos do retorno de chamada (um é convencionalmente chamado result ) tornam-se o
foco dos testes e dos retornos de chamada subsequentes.
Embora o uso de callbacks abra a possibilidadede código assíncrono (sem usar algum
tipo de polling), introduzimos alguma complexidade tanto na maneira como estrutu-
ramos nossas funções quanto na maneira como as chamamos.
// promises
four()
.then(addOne)
.then(console.log);
Isso é bem direto. Temos uma função que retorna a 4 (envolvido em uma promessa),
atuado por addOne (que por sua vez retorna uma promessa), que por sua vez é atu-
ado por console.log .
Se estamos procurando compor funções, as promessas são muito mais fáceis de traba-
lhar. Os retornos de chamada nos prendem a nomes de função de codificação (e/ou
literais de função para retornos de chamada) nas declarações de função, passando-os
como parâmetros extras na função ou usando algumas outras alternativas não triviais
e um tanto confusas.
Com promessas, estamos apenas encadeando valores.Temos um valor (embrulhado
em uma promessa) e o then desembrulha esperando (quando necessário) e, em se-
guida, passamos esse valor como parâmetro para a promessa ou função. Para ilustrar
um pouco mais da interface, também poderíamos escrever o formulário 2:
// form 1
four()
.then(addOne)
.then(console.log);
// form 2
four()
Nessas formas, temos uma função literal ou referência. Tanto as definições de função
quanto as chamadas de função addOne e console.log acontecem em outros luga-
res. A Forma 1 é preferível quando possível (também conhecida como “sem pontos”,
um estilo que discutiremos mais no próximo capítulo).
Se você ainda não tem certeza sobre a utilidade depromessas maisretornos de chamada, dê
uma olhada nisso:
four()
.then(addOne)
.then(addOne)
.then(addOne)
.then(addOne)
.then(addOne)
.then(console.log);
Podemos encadear quantos addOne s quisermos.É basicamente uma interface fluente se você
ignorar as then chamadas e é amigável para assíncronas.Você pode fazer isso com o CPS, mas
está indo para a pirâmide da desgraça (e resultados intermediários difíceis de testar).
Agora que temos uma boa ideia do porquêpromessas costumam ser uma boa escolha
em vez de retornos de chamada, vamos dar uma olhada em como realmente as
implementamos:
four()
.then(addOne)
.then(console.log);
function addOne(addend){
};
function four(){
});
};
As três primeiras linhas devem ser muito familiares agora. Então, como funcionam as
novas funções? Pode parecer complexo dentro dos corpos das funções, mas observe
que não estamos mais passando callbacks, o que pode ficar muito confuso. Além
disso, recebemos nossas return declarações de volta!
Infelizmente, o que estamos devolvendo são promessas, que podem parecer difíceis
de entender. Mas eles não são. É como fazer torradas:
1. Você começa com uma promessa (geralmente criada com new Promise , mas o
exemplo anterior também mostra que você pode criar uma com
Promise.resolve ).
2. Você coloca um valor ou o processo para criar um valor (que pode ser assíncrono)
na promessa.
3. Após um timer ou receber o resultado de alguma função assíncrona, o valor é defi-
nido por resolve(someValue) .
4. Esse valor é retornado em uma promessa. Você o tira da torradeira – er, prometo –
com a then função e consome o valor como achar melhor.
Não? Bom. Discutiremos burritos no Capítulo 11 . Eles são como fazer torradas.
Mas uma promessa não é uma estrutura funcional de alto nível como um functor ou
mônada ou similar? Talvez, mas esse é um tópico enorme, e não podemos entrar em
tudo isso neste livro. O Capítulo 11 oferece uma boa introdução à codificação funcio-
nal prática, mas deixaremos a teoria de fora e focaremos no uso de boas interfaces de
codificação.
De volta ao nosso exemplo, nossa addOne função retorna uma promessa criada com
Promise.resolve(addend + 1) . Isso é bom para casos em que precisamos apenas
de um valor, mas usando a new Promise função construtora e fornecendoum re-
torno de chamada (o executor ) que chama resolve ou reject (as duas funções no-
meadas pela assinatura do retorno de chamada do construtor, o executor) oferece
mais flexibilidade.
ALGUMAS CONSIDERAÇÕES SOBRE ENTÃO
function addOne(addend){
return addend + 1;
Por quê? Porque then vai aceitar uma promessa ou uma função (ou duas, na ver-
dade: a primeira para cumprimento e a segunda para rejeição). Tente isto:
four()
.then(() => 6)
.then(console.log);
Neste caso, 6 será impresso. O then retorno de chamada do primeiro joga fora o 4 e
apenas passa adiante 6 .
No entanto, observe que four não pode ser uma função simples retornando um valor
simples, mesmo sem considerar o setTimeout aspecto da mesma. A primeira função
em uma cadeia de promessas deve ser “ then -able”—isto é, retornar um objeto que
suporte a interface. .then(fulfillment, rejection) Dito isso, você pode começar
apenas com uma promessa resolvida:
Promise.resolve()
.then(() => 4)
.then(() => 6)
.then(console.log);
Promessas de teste
function addOne(addend){
function four(){
})
addOne(3).then((result) => {
assert.end();
});
});
four().then((result) => {
assert.end();
});
});
testdouble.replace(console, 'log')
four()
.then(addOne)
.then(console.log)
.then(() => {
testdouble.verify(console.log(5));
assert.pass();
testdouble.reset();
assert.end();
}).catch((e) => {
testdouble.reset();
console.log(e);
})
});
Os dois primeiros testes, que são de baixo nível, permanecem relativamente inaltera-
dos. O teste de ponta a ponta mudou bastante. Depois de substituir
console.log para que possamos monitorá-lo, iniciamos a cadeia de promessas com
nossa four função de retorno de promessa. Nós encadeamos nossos addOne e
console.log callbacks com then funções. E então temos outro then , com uma fun-
ção anônima como único argumento. Dentro dessa função anônima, nós verify que
console.log foi chamado com 5 . Depois disso, chamamos assert.pass para que
nossa saída de teste confirme três em vez de dois testes aprovados. Precisamos disso
porque verify não faz parte tape e não produz uma afirmação passageira. Então
fazemos a desmontagem com testdouble.reset e assert.end .
Você pode estar se perguntando o que estamos fazendo com o catch . Bem, infeliz-
mente, depois de substituirmos console.log , nossos erros não imprimirão mais
nada! catch nos permite colocar de console.log volta testdouble.replace an-
tes de imprimir o erro com console.log(e) .
A ALTERAÇÃO DO CÓDIGO DE ESTILO DE RETORNO DE CHAMADA EM PROMESSAS ESTÁ
“REFATORANDO?”
Provavelmente não, a menos que você não esteja preocupado com testes de unidade e
considere sua “interface” como algumas interações de alto nível com o código.O valor
nas promessas é que elas alteram as interfaces, e essas são provavelmente onde você
gostaria que seus testes estivessem.
Então, por que gastar tanto tempo com promessas na refatoração de JavaScript ?
Existem três razões. Primeiro, você provavelmente ouvirá alguém em algum mo-
mento falar sobre “refatorar para usar promessas”. Você saberá que isso realmente
significa suportar novas interfaces e escrever novo código com novos testes. Veja o di-
agrama no Capítulo 5 sobre quais testes escrever ( Figura 5-1 ). A segunda razão é que
saber onde você está no ciclo de teste (testando antes do novo código, aumentando a
cobertura ou refatorando) é a coisa mais importante para se ter em mãos ao desenvol-
ver confiança em uma base de código. Terceiro, há toneladas de coisas legais em Ja-
vaScript (canvas, webvr, webgl, etc.) que tornam possíveis aplicativos de nicho, mas a
programação assíncrona com promessas é cada vez mais importante para desenvol-
vedores de JavaScript de todos os tipos.
Empacotando
Na maioria das vezes, o uso de qualquer um desses recursos envolverá uma mudança
drástica no código e não a refatoração conforme o definimos. Mas ter um conheci-
mento básico das interfaces envolvidas em seu código, bem como testá-las, é crucial
antes que qualquer refatoração possa ocorrer.
Apesar de “refatorar para usar promessas” não se encaixar muito em nosso conceito
de refatoração, a interface é aquela que devemos preferir (pelo menos até async e
await se tornar mais amplamente disponível), porque gera valores de retorno úteis
em vez de depender da chamada de outras funções (efeitos colaterais ). Da mesma
forma, async e await são interessantes porque nos permitem escrever código de
aparência síncrona apenas adicionando algumas palavras-chave. No entanto, no mo-
mento da redação deste artigo, suas especificações e implementações ainda não fo-
ram realizadas .
Apoiar Sair