1) O que é um "inferno de callback" para alguém que não conhece javascript e node.js?
Esta outra pergunta tem alguns exemplos de inferno de retorno de chamada de Javascript: Como evitar aninhamento longo de funções assíncronas em Node.js
O problema em Javascript é que a única maneira de "congelar" um cálculo e fazer com que o "resto" seja executado posteriormente (de forma assíncrona) é colocar "o resto" dentro de um retorno de chamada.
Por exemplo, digamos que eu queira executar um código parecido com este:
x = getData();
y = getMoreData(x);
z = getMoreData(y);
...
O que acontecerá se eu quiser tornar as funções getData assíncronas, o que significa que tenho a chance de executar algum outro código enquanto espero que eles retornem seus valores? Em Javascript, a única maneira seria reescrever tudo o que toca em uma computação assíncrona usando o estilo de passagem de continuação :
getData(function(x){
getMoreData(x, function(y){
getMoreData(y, function(z){
...
});
});
});
Acho que não preciso convencer ninguém de que essa versão é mais feia que a anterior. :-)
2) Quando (em que tipo de configurações) ocorre o "problema do callback hell"?
Quando você tem muitas funções de retorno de chamada em seu código! Fica mais difícil trabalhar com eles quanto mais você tem em seu código e fica particularmente ruim quando você precisa fazer loops, blocos try-catch e coisas assim.
Por exemplo, até onde eu sei, em JavaScript, a única maneira de executar uma série de funções assíncronas em que uma é executada após os retornos anteriores é usando uma função recursiva. Você não pode usar um loop for.
// we would like to write the following
for(var i=0; i<10; i++){
doSomething(i);
}
blah();
Em vez disso, talvez precisemos acabar escrevendo:
function loop(i, onDone){
if(i >= 10){
onDone()
}else{
doSomething(i, function(){
loop(i+1, onDone);
});
}
}
loop(0, function(){
blah();
});
//ugh!
O número de perguntas que recebemos aqui no StackOverflow perguntando como fazer esse tipo de coisa é uma prova de como isso é confuso :)
3) Por que isso ocorre?
Isso ocorre porque no JavaScript a única maneira de atrasar um cálculo para que ele seja executado após o retorno da chamada assíncrona é colocar o código atrasado dentro de uma função de retorno de chamada. Você não pode atrasar o código que foi escrito no estilo síncrono tradicional, então você acaba com callbacks aninhados em todos os lugares.
4) Ou o "inferno de callback" pode ocorrer também em um único aplicativo threaded?
A programação assíncrona tem a ver com simultaneidade, enquanto um thread único tem a ver com paralelismo. Na verdade, os dois conceitos não são a mesma coisa.
Você ainda pode ter código simultâneo em um único contexto de thread. Na verdade, JavaScript, a rainha do inferno de callbacks, é de thread único.
Qual é a diferença entre simultaneidade e paralelismo?
5) você poderia, por favor, mostrar também como o RX resolve o "problema do callback hell" naquele exemplo simples.
Não sei nada sobre RX em particular, mas normalmente esse problema é resolvido adicionando suporte nativo para computação assíncrona na linguagem de programação. As implementações podem variar e incluir: assíncrono, geradores, corrotinas e callcc.
Em Python, podemos implementar esse exemplo de loop anterior com algo ao longo das linhas de:
def myLoop():
for i in range(10):
doSomething(i)
yield
myGen = myLoop()
Este não é o código completo, mas a ideia é que o "rendimento" pause nosso loop for até que alguém chame myGen.next (). O importante é que ainda podemos escrever o código usando um loop for, sem precisar transformar a lógica "de dentro para fora" como fizemos naquela loop
função recursiva .
Apenas responda à pergunta: você poderia, por favor, mostrar também como o RX resolve o "problema do callback hell" naquele exemplo simples?
A mágica é
flatMap
. Podemos escrever o seguinte código em Rx para o exemplo de @ hugomg:É como se você estivesse escrevendo alguns códigos FP síncronos, mas na verdade você pode torná-los assíncronos por
Scheduler
.fonte
Para abordar a questão de como Rx resolve o inferno de callback :
Primeiro, vamos descrever o inferno de callback novamente.
Imagine um caso em que devemos fazer http para obter três recursos - pessoa, planeta e galáxia. Nosso objetivo é encontrar a galáxia em que a pessoa vive. Primeiro, devemos pegar a pessoa, depois o planeta, depois a galáxia. São três retornos de chamada para três operações assíncronas.
Cada retorno de chamada é aninhado. Cada retorno de chamada interno depende de seu pai. Isso leva ao estilo "pirâmide da desgraça" do inferno de callback . O código parece um sinal>.
Para resolver isso em RxJs, você poderia fazer algo assim:
Com o operador
mergeMap
AKAflatMap
, você pode torná-lo mais sucinto:Como você pode ver, o código é simplificado e contém uma única cadeia de chamadas de método. Não temos uma "pirâmide da desgraça".
Conseqüentemente, o inferno de callback é evitado.
Caso você esteja se perguntando, as promessas são outra maneira de evitar o inferno do callback, mas as promessas são ansiosas , não preguiçosas como as observáveis e (falando de modo geral) você não pode cancelá-las tão facilmente.
fonte
Inferno de retorno de chamada é qualquer código em que o uso de retornos de chamada de função em código assíncrono se torna obscuro ou difícil de seguir. Geralmente, quando há mais de um nível de indireção, o código que usa callbacks pode se tornar mais difícil de seguir, de refatorar e de testar. Um cheiro de código é vários níveis de indentação devido à passagem de várias camadas de literais de função.
Isso geralmente acontece quando o comportamento tem dependências, ou seja, quando A deve acontecer antes de B deve acontecer antes de C. Então você obtém um código como este:
Se você tiver muitas dependências comportamentais em seu código como essa, pode se tornar problemático rapidamente. Especialmente se ramificar ...
Isso não vai funcionar. Como podemos fazer com que o código assíncrono seja executado em uma determinada ordem sem ter que passar todos esses callbacks?
RX é a abreviatura de 'extensões reativas'. Não usei, mas o Google sugere que é uma estrutura baseada em eventos, o que faz sentido. Os eventos são um padrão comum para fazer o código ser executado em ordem, sem criar um acoplamento frágil . Você pode fazer C ouvir o evento 'bFinished' que só acontece depois que B é chamado de ouvir 'aFinished'. Você pode então facilmente adicionar etapas extras ou estender esse tipo de comportamento e pode facilmente testar se o seu código é executado em ordem meramente transmitindo eventos em seu caso de teste.
fonte
Inferno de retorno de chamada significa que você está dentro de um retorno de chamada ou dentro de outro retorno de chamada e ele vai para a enésima chamada até que suas necessidades não sejam atendidas.
Vamos entender por meio de um exemplo de chamada ajax falsa usando a API de tempo limite definido, vamos assumir que temos uma API de receita, precisamos baixar todas as receitas.
No exemplo acima, após 1,5 segundo quando o temporizador expira, o código de retorno de chamada será executado, em outras palavras, por meio de nossa chamada ajax falsa, todas as receitas serão baixadas do servidor. Agora precisamos baixar os dados de uma receita específica.
Para baixar os dados de uma receita específica, escrevemos o código em nosso primeiro retorno de chamada e passamos o Id da receita
Agora, digamos que precisamos baixar todas as receitas do mesmo editor da receita cujo id é 7638.
Para preencher nossas necessidades, que é baixar todas as receitas do nome da editora suru, escrevemos o código dentro de nosso segundo retorno. É claro que escrevemos uma cadeia de retorno de chamada chamada inferno de retorno de chamada.
Se quiser evitar o inferno do retorno de chamada, você pode usar Promise, que é o recurso js es6, cada promessa recebe um retorno de chamada que é chamado quando uma promessa é cumprida. O retorno de chamada de promessa tem duas opções: é resolvido ou rejeitado. Suponha que sua chamada de API seja bem-sucedida, você pode chamar resolve e passar dados por meio do resolve ; você pode obter esses dados usando then () . Mas se sua API falhar, você pode usar rejeitar, usar catch para detectar o erro. Lembre-se de uma promessa, sempre use então para resolver e pegue para rejeitar
Vamos resolver o problema anterior do callback hell usando uma promessa.
Agora baixe a receita particular:
Agora podemos escrever outra chamada de método allRecipeOfAPublisher como getRecipe, que também retornará uma promessa, e podemos escrever outro then () para receber a promessa de resolução de allRecipeOfAPublisher, espero que neste ponto você possa fazer isso sozinho.
Portanto, aprendemos como construir e consumir promessas, agora vamos tornar o consumo de uma promessa mais fácil usando async / await, que é apresentado no es8.
No exemplo acima, usamos uma função assíncrona porque ela será executada em segundo plano, dentro da função assíncrona usamos a palavra-chave await antes de cada método que retorna ou é uma promessa porque esperar nessa posição até que a promessa seja cumprida, em outras palavras no códigos abaixo até getIds concluído resolvido ou rejeitado programa irá parar de executar códigos abaixo daquela linha quando IDs retornados, então chamamos novamente a função getRecipe () com um id e esperamos usando a palavra-chave await até que os dados retornassem. Então foi assim que finalmente nos recuperamos do inferno de callback.
Para usar o await, precisaremos de uma função assíncrona, podemos retornar uma promessa, então use para resolver promessa e cath para rejeitar promessa
do exemplo acima:
fonte
Uma maneira de evitar o inferno do retorno de chamada é usar o FRP, que é uma "versão aprimorada" do RX.
Comecei a usar o FRP recentemente porque encontrei uma boa implementação dele chamada
Sodium
( http://sodium.nz/ ).Um código típico se parece com isto (Scala.js):
selectedNote.updates()
é umStream
que é acionado seselectedNode
(que é aCell
) muda,NodeEditorWidget
então é atualizado de forma correspondente.Portanto, dependendo do conteúdo do
selectedNode
Cell
, o editado atualmenteNote
será alterado.Este código evita Callback-s inteiramente, quase, Cacllback-s são empurrados para a "camada externa" / "superfície" do aplicativo, onde a lógica de manipulação de estado faz interface com o mundo externo. Não há retornos de chamada necessários para propagar dados dentro da lógica de tratamento de estado interno (que implementa uma máquina de estado).
O código-fonte completo está aqui
O snippet de código acima corresponde ao seguinte exemplo simples de Criar / Exibir / Atualizar:
Este código também envia atualizações para o servidor, portanto, as alterações nas Entidades atualizadas são salvas no servidor automaticamente.
Todo o tratamento de eventos é feito usando
Stream
s eCell
s. Esses são conceitos de FRP. Os retornos de chamada são necessários apenas quando a lógica do FRP faz interface com o mundo externo, como entrada do usuário, edição de texto, pressionamento de um botão, retorno de chamada AJAX.O fluxo de dados é descrito explicitamente, de forma declarativa, usando FRP (implementado pela biblioteca Sodium), portanto, nenhuma manipulação de eventos / lógica de retorno de chamada é necessária para descrever o fluxo de dados.
FRP (que é uma versão mais "estrita" de RX) é uma maneira de descrever um gráfico de fluxo de dados, que pode conter nós que contêm estado. Os eventos acionam mudanças de estado no estado que contém os nós (chamados de
Cell
s).O sódio é uma biblioteca FRP de ordem superior, o que significa que usando a
flatMap
/switch
primitiva pode-se reorganizar o gráfico de fluxo de dados em tempo de execução.Recomendo dar uma olhada no livro Sodium , ele explica em detalhes como o FRP se livra de todos os Callbacks que não são essenciais para descrever a lógica do fluxo de dados que tem a ver com a atualização do estado dos aplicativos em resposta a alguns estímulos externos.
Usando o FRP, apenas os retornos de chamada precisam ser mantidos que descrevem a interação com o mundo externo. Em outras palavras, o fluxo de dados é descrito de maneira funcional / declarativa quando se usa uma estrutura FRP (como o Sodium) ou quando se usa uma estrutura "semelhante ao FRP" (como RX).
O sódio também está disponível para Javascript / Typescript.
fonte
Se você não tem um conhecimento sobre callback e hell callback, não há problema. A primeira coisa é que call back e call back hell.Por exemplo: Hell call back é como um podemos armazenar uma classe dentro de uma classe.Como você ouviu sobre isso aninhado na linguagem C, C ++. Aninhado Significa que uma classe dentro de outra classe.
fonte
Use jazz.js https://github.com/Javanile/Jazz.js
simplifica assim:
fonte