
[ad_1]
O inferno de callback é real. Os desenvolvedores geralmente veem os retornos de chamada como puro mal, até o ponto de evitá-los. A flexibilidade do JavaScript não ajuda em nada com isso. Mas não é necessário evitar retornos de chamada. A boa notícia é que existem passos simples para se salvar do inferno do callback.
Eliminar retornos de chamada em seu código é como amputar uma perna boa. Uma função de retorno de chamada é um dos pilares do JavaScript e uma de suas partes boas. Quando você substitui os retornos de chamada, geralmente está apenas trocando problemas.
Alguns dizem que callbacks são verrugas feias e são a razão para estudar idiomas melhores. Bem, os retornos de chamada são tão feios?
Empunhar callbacks em JavaScript tem seu próprio conjunto de recompensas. Não há razão para evitar JavaScript porque retornos de chamada podem se transformar em verrugas feias. Podemos apenas ter certeza de que isso não aconteça.
Vamos mergulhar no que a programação de som tem a oferecer com retornos de chamada. Nossa preferência é seguir os princípios SOLID e ver onde isso nos leva.
O que é o inferno de retorno de chamada?
Você pode estar se perguntando o que é um retorno de chamada e por que você deve se importar. Em JavaScript, um ligue de volta é uma função que atua como um delegado. O delegado executa em um momento arbitrário no futuro. Em JavaScript, a delegação acontece quando a função de recebimento chama o retorno de chamada. A função receptora pode fazê-lo em qualquer ponto arbitrário de sua execução.
Resumindo, um callback é uma função passada como argumento para outra função. Não há execução imediata, pois a função receptora decide quando chamá-la. O exemplo de código a seguir ilustra:
function receiver(fn) {
return fn();
}
function callback() {
return 'foobar';
}
var callbackResponse = receiver(callback);
Se você já escreveu uma solicitação Ajax, encontrou funções de retorno de chamada. O código assíncrono usa essa abordagem, pois não há garantia de quando o retorno de chamada será executado.
O problema com retornos de chamada decorre de ter código assíncrono que depende de outro retorno de chamada. Podemos usar setTimeout
para simular chamadas assíncronas com funções de retorno de chamada.
Fique a vontade para acompanhar. O repositório está disponível no GitHub, e a maioria dos trechos de código virá de lá para que você possa jogar junto.
Eis a pirâmide da perdição!
setTimeout(function (name) {
var catList = name + ',';
setTimeout(function (name) {
catList += name + ',';
setTimeout(function (name) {
catList += name + ',';
setTimeout(function (name) {
catList += name + ',';
setTimeout(function (name) {
catList += name;
console.log(catList);
}, 1, 'Lion');
}, 1, 'Snow Leopard');
}, 1, 'Lynx');
}, 1, 'Jaguar');
}, 1, 'Panther');
Observando o código acima, setTimeout
obtém uma função de retorno de chamada que é executada após um milissegundo. O último parâmetro apenas alimenta o callback com dados. Isso é como uma chamada Ajax, exceto que o retorno name
parâmetro viria do servidor.
Há uma boa visão geral da função setTimeout no SitePoint.
Em nosso código, estamos reunindo uma lista de gatos ferozes por meio de código assíncrono. Cada retorno de chamada nos dá um único nome de gato e o anexamos à lista. O que estamos tentando alcançar parece razoável. Mas dada a flexibilidade das funções JavaScript, isso é um pesadelo.
Funções anônimas
Observe o uso de funções anônimas nesse exemplo anterior. Funções anônimas são expressões de função sem nome que são atribuídas a uma variável ou passadas como um argumento para outras funções.
O uso de funções anônimas em seu código não é recomendado por alguns padrões de programação. É melhor nomeá-los, então use function getCat(name){}
ao invés de function (name){}
. Colocar nomes em funções adiciona clareza aos seus programas. Essas funções anônimas são fáceis de digitar, mas fazem você correr para uma estrada para o inferno. Quando você se encontra indo por essa estrada sinuosa de recortes, é melhor parar e repensar.
Uma abordagem ingênua para quebrar essa bagunça de retornos de chamada é usar declarações de função:
setTimeout(getPanther, 1, 'Panther');
var catList = '';
function getPanther(name) {
catList = name + ',';
setTimeout(getJaguar, 1, 'Jaguar');
}
function getJaguar(name) {
catList += name + ',';
setTimeout(getLynx, 1, 'Lynx');
}
function getLynx(name) {
catList += name + ',';
setTimeout(getSnowLeopard, 1, 'Snow Leopard');
}
function getSnowLeopard(name) {
catList += name + ',';
setTimeout(getLion, 1, 'Lion');
}
function getLion(name) {
catList += name;
console.log(catList);
}
Você não encontrará este trecho no repositório, mas a melhoria incremental pode ser encontrada neste commit.
Cada função recebe sua própria declaração. Uma vantagem é que não temos mais a horrível pirâmide. Cada função fica isolada e focada em sua própria tarefa específica. Cada função agora tem um motivo para mudar, então é um passo na direção certa. Observe que getPanther()
, por exemplo, é atribuído ao parâmetro. JavaScript não se importa como criamos callbacks. Mas quais são as desvantagens?
Para obter um detalhamento completo das diferenças, consulte este artigo do SitePoint sobre Expressões de função versus declarações de função.
Uma desvantagem, porém, é que cada declaração de função não tem mais escopo dentro do retorno de chamada. Em vez de usar callbacks como encerramento, cada função agora fica colada ao escopo externo. Daí porque catList
é declarado no escopo externo, pois isso concede aos retornos de chamada acesso à lista. Às vezes, destruir o escopo global não é a solução ideal. Há também duplicação de código, pois anexa um gato à lista e chama o próximo retorno de chamada.
Estes são cheiros de código herdados do inferno de retorno de chamada. Às vezes, o esforço para entrar na liberdade de retorno de chamada precisa de perseverança e atenção aos detalhes. Pode começar a parecer que a doença é melhor do que a cura. Existe uma maneira de codificar isso melhor?
Inversão de dependência
O princípio de inversão de dependência diz que devemos codificar para abstrações, não para detalhes de implementação. No núcleo, pegamos um grande problema e o dividimos em pequenas dependências. Essas dependências tornam-se independentes para onde os detalhes de implementação são irrelevantes.
Este princípio SOLID afirma:
Ao seguir esse princípio, os relacionamentos de dependência convencionais estabelecidos de módulos de definição de política de alto nível para módulos de dependência de baixo nível são revertidos, tornando os módulos de alto nível independentes dos detalhes de implementação do módulo de baixo nível.
Então, o que esse blob de texto significa? A boa notícia é que, ao atribuir um callback a um parâmetro, já estamos fazendo isso! Pelo menos em parte, para desacoplar, pense nos retornos de chamada como dependências. Essa dependência torna-se um contrato. Deste ponto em diante, estamos fazendo programação SOLID.
Uma maneira de obter liberdade de retorno de chamada é criar um contrato:
fn(catList);
Isso define o que planejamos fazer com o retorno de chamada. Ele precisa acompanhar um único parâmetro – ou seja, nossa lista de gatos ferozes.
Essa dependência agora pode ser alimentada por meio de um parâmetro:
function buildFerociousCats(list, returnValue, fn) {
setTimeout(function asyncCall(data) {
var catList = list === '' ? data : list + ',' + data;
fn(catList);
}, 1, returnValue);
}
Observe que a expressão da função asyncCall
fica no escopo para o fechamento buildFerociousCats
. Essa técnica é poderosa quando combinada com retornos de chamada na programação assíncrona. O contrato é executado de forma assíncrona e ganha o data
precisa, tudo com programação de som. O contrato ganha a liberdade de que precisa à medida que se desvincula da implementação. Código bonito usa a flexibilidade do JavaScript para sua própria vantagem.
O resto do que precisa acontecer torna-se evidente. Nós podemos fazer isso:
buildFerociousCats('', 'Panther', getJaguar);
function getJaguar(list) {
buildFerociousCats(list, 'Jaguar', getLynx);
}
function getLynx(list) {
buildFerociousCats(list, 'Lynx', getSnowLeopard);
}
function getSnowLeopard(list) {
buildFerociousCats(list, 'Snow Leopard', getLion);
}
function getLion(list) {
buildFerociousCats(list, 'Lion', printList);
}
function printList(list) {
console.log(list);
}
Não há duplicação de código aqui. O retorno de chamada agora mantém o controle de seu próprio estado sem variáveis globais. Um retorno de chamada como getLion
pode ficar encadeado com qualquer coisa que siga o contrato – ou seja, qualquer abstração que tenha uma lista de gatos ferozes como parâmetro. Este código de exemplo está disponível no GitHub.
Callbacks polimórficos
Ok, vamos ficar um pouco loucos. E se quiséssemos mudar o comportamento de criar uma lista separada por vírgulas para uma lista delimitada por barra vertical? Um problema que podemos imaginar é que buildFerociousCats
foi colado a um detalhe de implementação. Observe o uso de list + ',' + data
para fazer isso.
A resposta simples é o comportamento polimórfico com retornos de chamada. O princípio permanece: trate os retornos de chamada como um contrato e torne a implementação irrelevante. Uma vez que o retorno de chamada se eleva a uma abstração, os detalhes específicos podem mudar à vontade.
O polimorfismo abre novas formas de reutilização de código em JavaScript. Pense em um polimórfico callback como uma forma de definir um contrato estrito, enquanto permite liberdade suficiente para que os detalhes da implementação não importem mais. Observe que ainda estamos falando sobre inversão de dependência. Um retorno de chamada polimórfico é apenas um nome chique que indica uma maneira de levar essa ideia adiante.
Vamos definir o contrato. Podemos usar o list
e data
parâmetros neste contrato:
cat.delimiter(cat.list, data);
Então podemos fazer alguns ajustes para buildFerociousCats
:
function buildFerociousCats(cat, returnValue, next) {
setTimeout(function asyncCall(data) {
var catList = cat.delimiter(cat.list, data);
next({ list: catList, delimiter: cat.delimiter });
}, 1, returnValue);
}
O objeto JavaScript cat
agora encapsula o list
dados e delimiter
função. o next
callbacks assíncronos de cadeias de retorno de chamada — anteriormente chamados fn
. Observe que há liberdade para agrupar parâmetros à vontade com um objeto JavaScript. o cat
objeto espera duas chaves específicas — list
e delimiter
. Este objeto JavaScript agora faz parte do contrato. O resto do código permanece o mesmo.
Para acionar isso, podemos fazer isso:
buildFerociousCats({ list: '', delimiter: commaDelimiter }, 'Panther', getJaguar);
buildFerociousCats({ list: '', delimiter: pipeDelimiter }, 'Panther', getJaguar);
Os retornos de chamada são trocados. Desde que os contratos sejam cumpridos, os detalhes de implementação são irrelevantes. Podemos mudar o comportamento com facilidade. O retorno de chamada, que agora é uma dependência, é invertido em um contrato de alto nível. Essa ideia pega o que já sabemos sobre callbacks e o eleva a um novo nível. A redução de retornos de chamada em contratos aumenta as abstrações e desacopla os módulos de software.
O que é tão radical aqui é que os testes de unidade fluem naturalmente de módulos independentes. o delimiter
contrato é uma função pura. Isso significa que, dado um número de entradas, obtemos a mesma saída todas as vezes. Esse nível de testabilidade aumenta a confiança de que a solução funcionará. Afinal, a independência modular garante o direito de autoavaliação.
Um teste de unidade eficaz em torno do delimitador de barra vertical pode ser algo assim:
describe('A pipe delimiter', function () {
it('adds a pipe in the list', function () {
var list = pipeDelimiter('Cat', 'Cat');
assert.equal(list, 'Cat|Cat');
});
});
Vou deixar você imaginar como são os detalhes da implementação. Sinta-se à vontade para verificar o commit no GitHub.
Promessas
Uma promessa é simplesmente um wrapper em torno do mecanismo de retorno de chamada e permite um então possível para continuar o fluxo de execução. Isso torna o código mais reutilizável porque você pode retornar uma promessa e encadear a promessa.
Vamos construir em cima do retorno de chamada polimórfico e envolver isso em uma promessa. Ajuste o buildFerociousCats
função e faça com que ela retorne uma promessa:
function buildFerociousCats(cat, returnValue, next) {
return new Promise((resolve) => {
setTimeout(function asyncCall(data) {
var catList = cat.delimiter(cat.list, data);
resolve(next({ list: catList, delimiter: cat.delimiter }));
}, 1, returnValue);
});
}
Observe o uso de resolve
: em vez de usar o callback diretamente, é isso que resolve a promessa. O código consumidor pode aplicar um then
para continuar o fluxo de execução.
Como agora estamos retornando uma promessa, o código deve acompanhar a promessa na execução do retorno de chamada.
Vamos atualizar as funções de callback para retornar a promessa:
function getJaguar(cat) {
return buildFerociousCats(cat, 'Jaguar', getLynx);
}
function getLynx(cat) {
return buildFerociousCats(cat, 'Lynx', getSnowLeopard);
}
function getSnowLeopard(cat) {
return buildFerociousCats(cat, 'Snow Leopard', getLion);
}
function getLion(cat) {
return buildFerociousCats(cat, 'Lion', printList);
}
function printList(cat) {
console.log(cat.list);
}
O último retorno de chamada não encadeia promessas, porque não tem uma promessa de retorno. Manter o controle das promessas é importante para garantir uma continuação no final. Por analogia, quando fazemos uma promessa, a melhor maneira de cumprir a promessa é lembrar que já fizemos essa promessa.
Agora vamos atualizar a chamada principal com uma chamada de função então:
buildFerociousCats({ list: '', delimiter: commaDelimiter }, 'Panther', getJaguar)
.then(() => console.log('DONE'));
Se executarmos o código, veremos que “DONE” é impresso no final. Se nos esquecermos de retornar uma promessa em algum lugar do fluxo, “DONE” aparecerá fora de ordem, porque perde o rastro da promessa original feita.
Sinta-se à vontade para verificar o commit para promessas no GitHub.
Assíncrono/Aguardar
Por fim, podemos pensar em async/await como açúcar sintático em torno de uma promessa. Para JavaScript, async/await é na verdade uma promessa, mas para o programador parece mais um código síncrono.
Do código que temos até agora, vamos nos livrar do then
e envolva a chamada em async/await:
async function run() {
await buildFerociousCats({ list: '', delimiter: pipeDelimiter }, 'Panther', getJaguar)
console.log('DONE');
}
run().then(() => console.log('DONE DONE'));
A saída “DONE” é executada logo após o await
, porque funciona muito como código síncrono. Enquanto a chamada para buildFerociousCats
retorna uma promessa, podemos aguardar a chamada. o async
marca a função como retornando uma promessa, então ainda não é possível encadear a chamada em run
com mais um then
. Desde que o que chamamos retorne uma promessa, podemos encadear promessas indefinidamente.
Você pode verificar isso no commit async/await no GitHub.
Tenha em mente que todo esse código assíncrono é executado no contexto de um único thread. Um retorno de chamada JavaScript se encaixa bem nesse paradigma de thread único, porque o retorno de chamada é enfileirado de forma que não bloqueie a execução adicional. Isso torna mais fácil para o mecanismo JavaScript acompanhar os retornos de chamada e pegar o retorno de chamada imediatamente sem ter que lidar com a sincronização de vários encadeamentos.
Conclusão
Dominar retornos de chamada em JavaScript é entender todas as minúcias. Espero que você veja as variações sutis nas funções JavaScript. Uma função de retorno de chamada se torna incompreendida quando não temos os fundamentos. Assim que as funções JavaScript estiverem claras, os princípios SOLID logo se seguirão. Requer uma forte compreensão dos fundamentos para ter uma chance de programação SOLID. A flexibilidade inerente à linguagem coloca o peso da responsabilidade no programador.
O que eu mais amo é que JavaScript capacita uma boa programação. Uma boa compreensão de todas as minúcias e fundamentos nos levará longe algum Língua. Essa abordagem para funções de retorno de chamada é super importante no JavaScript vanilla. Por necessidade, todos os cantos e recantos levarão nossas habilidades para o próximo nível.
[ad_2]
Source link