Lidando com a pirâmide de retorno de chamada node.js.

9

Acabei de começar a usar o nó, e uma coisa que notei rapidamente é a rapidez com que os retornos de chamada podem criar um nível bobo de recuo:

doStuff(arg1, arg2, function(err, result) {
    doMoreStuff(arg3, arg4, function(err, result) {
        doEvenMoreStuff(arg5, arg6, function(err, result) {
            omgHowDidIGetHere();
        });
    });
});

O guia de estilo oficial diz para colocar cada retorno de chamada em uma função separada, mas isso parece excessivamente restritivo ao uso de fechamentos e tornar um único objeto declarado no nível superior disponível várias camadas abaixo, pois o objeto deve ser passado por todas as retornos de chamada intermediários.

É bom usar o escopo da função para ajudar aqui? Coloque todas as funções de retorno de chamada que precisam acessar um objeto global-ish dentro de uma função que declare esse objeto, para que ele seja encerrado?

function topLevelFunction(globalishObject, callback) {

    function doMoreStuffImpl(err, result) {
        doMoreStuff(arg5, arg6, function(err, result) {
            callback(null, globalishObject);
        });
    }

    doStuff(arg1, arg2, doMoreStuffImpl);
}

e assim por diante por várias outras camadas ...

Ou existem estruturas etc para ajudar a reduzir os níveis de recuo sem declarar uma função nomeada para cada retorno de chamada? Como você lida com a pirâmide de retorno de chamada?

thecoop
fonte
2
Observe um problema adicional com os fechamentos - no fechamento de JS captura todo o contexto pai (em outros idiomas, ele captura apenas a variável usada ou aquela solicitada especificamente pelo usuário) causando alguns vazamentos de memória, se a hierarquia de retorno de chamada for profunda o suficiente e, por exemplo, o retorno de chamada é retido em algum lugar.
Eugene

Respostas:

7

As promessas fornecem uma separação clara de preocupações entre o comportamento assíncrono e a interface, para que funções assíncronas possam ser chamadas sem retornos de chamada e a interação de retorno de chamada possa ser feita na interface de promessa genérica.

Existem várias implementações de "promessas":


Por exemplo, você pode reescrever esses retornos de chamada aninhados

http.get(url.parse("http://test.com/"), function(res) {
    console.log(res.statusCode);
    http.get(url.parse(res.headers["location"]), function(res) {
        console.log(res.statusCode);
    });
});

gostar

httpGet(url.parse("http://test.com/")).then(function (res) {
    console.log(res.statusCode);
    return httpGet(url.parse(res.headers["location"]));
}).then(function (res) {
    console.log(res.statusCode);
});

Em vez de retorno de chamada, a(b(c()))você liga o ".then" a().then(b()).then(c()).


Uma introdução aqui: http://howtonode.org/promises

Fabien Sa
fonte
você se importaria de explicar mais sobre o que esses recursos fazem e por que os recomenda como resposta à pergunta? "Respostas apenas para links" não são bem-vindas no Stack Exchange
gnat
11
Ok desculpe. Eu adicionei um exemplo e mais informações.
Fabian Sa
3

Como alternativa às promessas, você deve dar uma olhada na yieldpalavra - chave em combinação com as funções do gerador que serão introduzidas no EcmaScript 6. Ambas estão disponíveis hoje nas compilações do Node.js. 0.11.x, mas exigem que você especifique adicionalmente o --harmonysinalizador ao executar o Node .js:

$ node --harmony app.js

Usando essas construções e uma biblioteca como do TJ Holowaychuk co permitem escrever código assíncrono em um estilo que olha como código síncrono, embora ele ainda funciona de forma assíncrona. Basicamente, essas coisas juntas implementam suporte co-rotineiro para Node.js.

Basicamente, o que você precisa fazer é escrever uma função geradora para o código que executa coisas assíncronas, chamar as coisas assíncronas, mas prefixar com a yieldpalavra - chave. Portanto, no final, seu código se parece com:

var run = function * () {
  var data = yield doSomethingAsync();
  console.log(data);
};

Para executar esta função geradora, você precisa de uma biblioteca como a co mencionada anteriormente. A chamada então se parece com:

co(run);

Ou, para colocar em linha:

co(function * () {
  // ...
});

Observe que nas funções do gerador você pode chamar outras funções do gerador, tudo o que você precisa fazer é prefixá-las yieldnovamente.

Para uma introdução a este tópico, pesquise no Google termos como yield generators es6 async nodejse você deve encontrar toneladas de informações. Demora um tempo para se acostumar, mas depois que você o obtém, não deseja mais voltar.

Observe que isso não apenas fornece uma sintaxe melhor para chamar funções, mas também permite que você use as coisas lógicas usuais (síncronas) do fluxo de controle, como forloops ou try/ catch. Chega de brincar com muitas retornos de chamada e todas essas coisas.

Boa sorte e divirta-se :-)!

Golo Roden
fonte
0

Agora você tem o pacote asyncawait , com uma sintaxe muito próxima do que deve ser o futuro suporte nativo do await& asyncin Node.

Basicamente, ele permite que você escreva códigos assíncronos com aparência síncrona , reduzindo drasticamente os níveis de LOC e recuo, com a compensação de uma pequena perda de desempenho (os números dos proprietários dos pacotes têm uma velocidade de 79% em comparação com os retornos brutos), esperançosamente reduzidos quando o suporte nativo estiver disponível.

Ainda é uma boa opção para sair da IMO do inferno de retorno de chamada / pirâmide do pesadelo, quando o desempenho não é a principal preocupação e o estilo de escrita síncrona melhor se adapta às necessidades do seu projeto.

Exemplo básico do pacote doc:

var foo = async (function() {
    var resultA = await (firstAsyncCall());
    var resultB = await (secondAsyncCallUsing(resultA));
    var resultC = await (thirdAsyncCallUsing(resultB));
    return doSomethingWith(resultC);
});
Z gelado
fonte