Como evitar o aninhamento longo de funções assíncronas no Node.js

158

Quero criar uma página que exiba alguns dados de um banco de dados, por isso criei algumas funções que obtêm esses dados do meu banco de dados. Sou apenas um novato no Node.js, pelo que entendi, se eu quiser usá-los em uma única página (resposta HTTP), precisarei aninhar todos:

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = "<h1>Demo page</h1>";
  getSomeDate(client, function(someData) {
    html += "<p>"+ someData +"</p>";
    getSomeOtherDate(client, function(someOtherData) {
      html += "<p>"+ someOtherData +"</p>";
      getMoreData(client, function(moreData) {
        html += "<p>"+ moreData +"</p>";
        res.write(html);
        res.end();
      });
    });
  });

Se houver muitas funções como essa, o aninhamento se tornará um problema .

Há alguma maneira de evitar isto? Eu acho que tem a ver com a maneira como você combina várias funções assíncronas, o que parece ser algo fundamental.

Kay Pale
fonte
12
Então, quando você tem 10 funções assíncronas, você tem 10 níveis de indentação?
Kay pálido
Este link pode ajudar. stackoverflow.com/a/4631909/290340
Evan Solha
1
Outro problema: inserir outra função entre getSomeDatee getSomeOtherDateacaba alterando o recuo de muitas linhas, o que dificulta a leitura do histórico do git ( git blameé inútil depois disso), e você provavelmente comete erros ao fazer isso manualmente
Daniel Alder

Respostas:

73

Observação interessante. Observe que, em JavaScript, você normalmente pode substituir funções de retorno de chamada anônimas inline por variáveis ​​de função nomeadas.

Os seguintes:

http.createServer(function (req, res) {
   // inline callback function ...

   getSomeData(client, function (someData) {
      // another inline callback function ...

      getMoreData(client, function(moreData) {
         // one more inline callback function ...
      });
   });

   // etc ...
});

Pode ser reescrito para ter algo parecido com isto:

var moreDataParser = function (moreData) {
   // date parsing logic
};

var someDataParser = function (someData) {
   // some data parsing logic

   getMoreData(client, moreDataParser);
};

var createServerCallback = function (req, res) {
   // create server logic

   getSomeData(client, someDataParser);

   // etc ...
};

http.createServer(createServerCallback);

No entanto, a menos que você planeje reutilizar a lógica de retorno de chamada em outros lugares, geralmente é muito mais fácil ler funções anônimas inline, como no seu exemplo. Isso também poupará você de encontrar um nome para todos os retornos de chamada.

Além disso, observe que, como o @pst observou em um comentário abaixo, se você estiver acessando variáveis ​​de fechamento dentro das funções internas, o acima não seria uma tradução direta. Nesses casos, o uso de funções anônimas inline é ainda mais preferível.

Daniel Vassallo
fonte
26
No entanto, (e realmente apenas para entender o trade-off) quando não aninhados, algumas semânticas de fechamento sobre variáveis podem ser perdidas, por isso não é uma tradução direta. No exemplo acima, o acesso a 'res' in getMoreDataé perdido.
2
Eu acho que sua solução está quebrada: someDataParserna verdade, analisa TODOS os dados, pois também chama getMoreData. Nesse sentido, o nome da função está incorreto e torna-se aparente que realmente não removemos o problema de aninhamento.
Konstantin Schubert
63

Kay, basta usar um desses módulos.

Vai virar isso:

dbGet('userIdOf:bobvance', function(userId) {
    dbSet('user:' + userId + ':email', '[email protected]', function() {
        dbSet('user:' + userId + ':firstName', 'Bob', function() {
            dbSet('user:' + userId + ':lastName', 'Vance', function() {
                okWeAreDone();
            });
        });
    });
});

Nisso:

flow.exec(
    function() {
        dbGet('userIdOf:bobvance', this);

    },function(userId) {
        dbSet('user:' + userId + ':email', '[email protected]', this.MULTI());
        dbSet('user:' + userId + ':firstName', 'Bob', this.MULTI());
        dbSet('user:' + userId + ':lastName', 'Vance', this.MULTI());

    },function() {
        okWeAreDone()
    }
);
Baggz
fonte
9
Deu uma olhada rápida em flow-js, step e async e parece que eles lidam apenas com a ordem de execução da função. No meu caso, há acesso a variáveis ​​de fechamento em linha em cada recuo. Assim, por exemplo, as funções funcionam assim: obtenha req / res HTTP, obtenha ID do usuário do DB para cookie, receba email do ID do usuário posterior, obtenha mais dados para o email posterior, ..., obtenha X para o Y posterior, ... Se não me engano, essas estruturas garantem apenas que as funções assíncronas serão executadas na ordem correta, mas em todo corpo da função não há como obter a variável fornecida naturalmente pelos fechamentos (?) Obrigado :)
Kay Pale
9
Em termos de classificação dessas bibliotecas, verifiquei o número de "Estrelas" em cada uma no Github. async tem o máximo com 3000, o passo é o próximo com 1000, os outros são significativamente menores. Claro, nem todos eles fazem a mesma coisa :-)
kgilpin
3
@KayPale Eu costumo usar async.waterfall e, às vezes, tenho minhas próprias funções para cada estágio / etapa que repassará o que a próxima etapa precisa, ou definirá variáveis ​​antes da chamada async.METHOD para que esteja disponível no downline. Também utilizarei METHODNAME.bind (...) para minhas chamadas assíncronas. *, O que também funciona muito bem.
precisa saber é o seguinte
Uma pergunta rápida: na sua lista de módulos, os dois últimos são iguais? Ou seja, "async.js" e "async"
dari0h 6/09/16
18

Na maior parte, eu concordo com Daniel Vassallo. Se você pode dividir uma função complicada e profundamente aninhada em funções nomeadas separadas, geralmente é uma boa idéia. Nos momentos em que faz sentido fazê-lo em uma única função, você pode usar uma das muitas bibliotecas assíncronas node.js disponíveis. As pessoas criaram várias maneiras diferentes de resolver isso, então dê uma olhada na página de módulos node.js. e veja o que você pensa.

Eu mesmo escrevi um módulo para isso, chamado async.js . Usando isso, o exemplo acima pode ser atualizado para:

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  async.series({
    someData: async.apply(getSomeDate, client),
    someOtherData: async.apply(getSomeOtherDate, client),
    moreData: async.apply(getMoreData, client)
  },
  function (err, results) {
    var html = "<h1>Demo page</h1>";
    html += "<p>" + results.someData + "</p>";
    html += "<p>" + results.someOtherData + "</p>";
    html += "<p>" + results.moreData + "</p>";
    res.write(html);
    res.end();
  });
});

Uma coisa legal dessa abordagem é que você pode alterar rapidamente seu código para buscar os dados em paralelo, alterando a função 'series' para 'paralelamente'. Além disso, o async.js também funcionará dentro do navegador, para que você possa usar os mesmos métodos usados ​​no node.js, caso encontre algum código assíncrono complicado.

Espero que seja útil!

Caolan
fonte
Oi Caolan e obrigado pela resposta! No meu caso, há acesso a variáveis ​​de fechamento em linha em cada recuo. Assim, por exemplo, as funções funcionam assim: obtenha req / res HTTP, obtenha ID do usuário do DB para cookie, receba email do ID do usuário posterior, obtenha mais dados para o email posterior, ..., obtenha X para o Y posterior, ... Se não me engano, o código que você sugere garante apenas que as funções assíncronas serão executadas na ordem correta, mas em todo corpo da função não há como obter a variável fornecida naturalmente pelos fechamentos no meu código original. É esse o caso?
Kay Pale
3
O que você está tentando alcançar é arquitetonicamente chamado de pipeline de dados. Você pode usar a cascata assíncrona para esses casos.
Rudolf Meijering
18

Você pode usar esse truque com uma matriz em vez de funções aninhadas ou um módulo.

Muito mais fácil para os olhos.

var fs = require("fs");
var chain = [
    function() { 
        console.log("step1");
        fs.stat("f1.js",chain.shift());
    },
    function(err, stats) {
        console.log("step2");
        fs.stat("f2.js",chain.shift());
    },
    function(err, stats) {
        console.log("step3");
        fs.stat("f2.js",chain.shift());
    },
    function(err, stats) {
        console.log("step4");
        fs.stat("f2.js",chain.shift());
    },
    function(err, stats) {
        console.log("step5");
        fs.stat("f2.js",chain.shift());
    },
    function(err, stats) {
        console.log("done");
    },
];
chain.shift()();

Você pode estender o idioma para processos paralelos ou mesmo cadeias paralelas de processos:

var fs = require("fs");
var fork1 = 2, fork2 = 2, chain = [
    function() { 
        console.log("step1");
        fs.stat("f1.js",chain.shift());
    },
    function(err, stats) {
        console.log("step2");
        var next = chain.shift();
        fs.stat("f2a.js",next);
        fs.stat("f2b.js",next);
    },
    function(err, stats) {
        if ( --fork1 )
            return;
        console.log("step3");
        var next = chain.shift();

        var chain1 = [
            function() { 
                console.log("step4aa");
                fs.stat("f1.js",chain1.shift());
            },
            function(err, stats) { 
                console.log("step4ab");
                fs.stat("f1ab.js",next);
            },
        ];
        chain1.shift()();

        var chain2 = [
            function() { 
                console.log("step4ba");
                fs.stat("f1.js",chain2.shift());
            },
            function(err, stats) { 
                console.log("step4bb");
                fs.stat("f1ab.js",next);
            },
        ];
        chain2.shift()();
    },
    function(err, stats) {
        if ( --fork2 )
            return;
        console.log("done");
    },
];
chain.shift()();
Guido
fonte
15

Eu gosto muito de async.js para esse fim.

O problema foi resolvido pelo comando waterfall:

cascata (tarefas, [retorno de chamada])

Executa uma matriz de funções em série, cada uma passando seus resultados para a próxima na matriz. No entanto, se alguma das funções transmitir um erro ao retorno de chamada, a próxima função não será executada e o retorno de chamada principal será imediatamente chamado com o erro.

Argumentos

tarefas - Uma matriz de funções a serem executadas; cada função recebe um retorno de chamada (err, resultado1, resultado2, ...) que deve ser chamado ao concluir. O primeiro argumento é um erro (que pode ser nulo) e quaisquer outros argumentos serão passados ​​como argumentos para a próxima tarefa. retorno de chamada (err, [resultados]) - Um retorno de chamada opcional para executar quando todas as funções forem concluídas. Serão passados ​​os resultados do retorno de chamada da última tarefa.

Exemplo

async.waterfall([
    function(callback){
        callback(null, 'one', 'two');
    },
    function(arg1, arg2, callback){
        callback(null, 'three');
    },
    function(arg1, callback){
        // arg1 now equals 'three'
        callback(null, 'done');
    }
], function (err, result) {
    // result now equals 'done'    
});

Quanto às variáveis ​​req, res, elas serão compartilhadas no mesmo escopo da função (req, res) {}, que encerrou toda a chamada async.waterfall.

Além disso, o assíncrono é muito limpo. O que quero dizer é que eu mudo muitos casos como este:

function(o,cb){
    function2(o,function(err, resp){
        cb(err,resp);
    })
}

Para primeiro:

function(o,cb){
    function2(o,cb);
}

Então para isso:

function2(o,cb);

Então para isso:

async.waterfall([function2,function3,function4],optionalcb)

Ele também permite que muitas funções pré-fabricadas preparadas para o assíncrono sejam chamadas a partir do util.js muito rapidamente. Apenas encadeie o que você quer fazer, certifique-se de que o, cb seja tratado universalmente. Isso acelera bastante todo o processo de codificação.

Grant Li
fonte
11

O que você precisa é de um pouco de açúcar sintático. Chek isto:

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = ["<h1>Demo page</h1>"];
  var pushHTML = html.push.bind(html);

  Queue.push( getSomeData.partial(client, pushHTML) );
  Queue.push( getSomeOtherData.partial(client, pushHTML) );
  Queue.push( getMoreData.partial(client, pushHTML) );
  Queue.push( function() {
    res.write(html.join(''));
    res.end();
  });
  Queue.execute();
}); 

Bem arrumado , não é? Você pode perceber que o html se tornou uma matriz. Isso ocorre em parte porque as strings são imutáveis, então é melhor você armazenar sua saída em buffer em uma matriz do que descartar strings cada vez maiores. A outra razão é por causa de outra sintaxe agradável com bind.

Queueno exemplo é realmente apenas um exemplo e, juntamente com, partialpode ser implementado da seguinte maneira

// Functional programming for the rescue
Function.prototype.partial = function() {
  var fun = this,
      preArgs = Array.prototype.slice.call(arguments);
  return function() {
    fun.apply(null, preArgs.concat.apply(preArgs, arguments));
  };
};

Queue = [];
Queue.execute = function () {
  if (Queue.length) {
    Queue.shift()(Queue.execute);
  }
};
gblazex
fonte
1
Queue.execute () simplesmente executará as parciais uma após a outra, sem aguardar os resultados das chamadas assíncronas.
NGN
Local, obrigado. Eu atualizei a resposta. Aqui está um teste: jsbin.com/ebobo5/edit (com um opcional lastfunção)
gblazex
Olá galambalazs e obrigado pela resposta! No meu caso, há acesso a variáveis ​​de fechamento em linha em cada recuo. Assim, por exemplo, as funções funcionam assim: obtenha req / res HTTP, obtenha ID do usuário do DB para cookie, receba email do ID do usuário posterior, obtenha mais dados para o email posterior, ..., obtenha X para o Y posterior, ... Se não me engano, o código que você sugere garante apenas que as funções assíncronas serão executadas na ordem correta, mas em todo corpo da função não há como obter a variável fornecida naturalmente pelos fechamentos no meu código original. É esse o caso?
Kay Pale
1
Bem, você definitivamente perde fechamentos em todas as respostas. O que você pode fazer é criar um objeto no escopo global para dados compartilhados . Então, por exemplo, sua primeira função adiciona obj.emaile sua próxima função usa obj.emaile a exclui (ou apenas atribui null).
precisa saber é o seguinte
7

Estou apaixonado pelo Async.js desde que o encontrei. Tem uma async.seriesfunção que você pode usar para evitar aninhamentos longos.

Documentação:-


séries (tarefas, [retorno de chamada])

Execute uma matriz de funções em série, cada uma executando uma vez que a função anterior foi concluída. [...]

Argumentos

tasks- Uma matriz de funções a serem executadas; cada função recebe um retorno de chamada que deve chamar ao concluir. callback(err, [results])- Um retorno de chamada opcional para executar quando todas as funções forem concluídas. Essa função obtém uma matriz de todos os argumentos passados ​​para os retornos de chamada usados ​​na matriz.


Veja como podemos aplicá-lo ao seu código de exemplo: -

http.createServer(function (req, res) {

    res.writeHead(200, {'Content-Type': 'text/html'});

    var html = "<h1>Demo page</h1>";

    async.series([
        function (callback) {
            getSomeData(client, function (someData) { 
                html += "<p>"+ someData +"</p>";

                callback();
            });
        },

        function (callback) {
            getSomeOtherData(client, function (someOtherData) { 
                html += "<p>"+ someOtherData +"</p>";

                callback(); 
            });
        },

        funciton (callback) {
            getMoreData(client, function (moreData) {
                html += "<p>"+ moreData +"</p>";

                callback();
            });
        }
    ], function () {
        res.write(html);
        res.end();
    });
});
Salman von Abbas
fonte
6

O açúcar sintático mais simples que eu já vi é a promessa do nó.

npm install node-promessa || git clone https://github.com/kriszyp/node-promise

Usando isso, você pode encadear métodos assíncronos como:

firstMethod().then(secondMethod).then(thirdMethod);

O valor de retorno de cada um está disponível como argumento no próximo.

Nikhil Ranjan
fonte
3

O que você fez lá é pegar um padrão assíncrono e aplicá-lo a 3 funções chamadas em sequência, cada uma aguardando a conclusão da anterior antes de iniciar - ou seja, você as tornou síncronas . O ponto sobre a programação assíncrona é que você pode ter várias funções em execução ao mesmo tempo e não ter que esperar a conclusão de cada uma.

se getSomeDate () não fornece nada para getSomeOtherDate (), que não fornece nada para getMoreData (), por que você não os chama de forma assíncrona como js permite ou se eles são interdependentes (e não assíncronos) os escrevem como função única?

Você não precisa usar o aninhamento para controlar o fluxo - por exemplo, faça com que cada função termine chamando uma função comum que determina quando todos os 3 foram concluídos e depois envia a resposta.

Nick Tulett
fonte
2

Suponha que você possa fazer isso:

http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/html'});
    var html = "<h1>Demo page</h1>";
    chain([
        function (next) {
            getSomeDate(client, next);
        },
        function (next, someData) {
            html += "<p>"+ someData +"</p>";
            getSomeOtherDate(client, next);
        },
        function (next, someOtherData) {
            html += "<p>"+ someOtherData +"</p>";
            getMoreData(client, next);
        },
        function (next, moreData) {
            html += "<p>"+ moreData +"</p>";
            res.write(html);
            res.end();
        }
    ]);
});

Você só precisa implementar chain () para aplicar parcialmente cada função à próxima e chamar imediatamente apenas a primeira função:

function chain(fs) {
    var f = function () {};
    for (var i = fs.length - 1; i >= 0; i--) {
        f = fs[i].partial(f);
    }
    f();
}
ngn
fonte
Oi ngn e obrigado pela resposta! No meu caso, há acesso a variáveis ​​de fechamento em linha em cada recuo. Assim, por exemplo, as funções funcionam assim: obtenha req / res HTTP, obtenha ID do usuário do DB para cookie, receba email do ID do usuário posterior, obtenha mais dados para o email posterior, ..., obtenha X para o Y posterior, ... Se não me engano, o código que você sugere garante apenas que as funções assíncronas serão executadas na ordem correta, mas em todo corpo da função não há como obter a variável fornecida naturalmente pelos fechamentos no meu código original. É esse o caso?
Kay pálido
2

o inferno de retorno de chamada pode ser facilmente evitado em javascript puro com fechamento. a solução abaixo assume que todos os retornos de chamada seguem a assinatura da função (erro, dados).

http.createServer(function (req, res) {
  var modeNext, onNext;

  // closure variable to keep track of next-callback-state
  modeNext = 0;

  // next-callback-handler
  onNext = function (error, data) {
    if (error) {
      modeNext = Infinity;
    } else {
      modeNext += 1;
    }
    switch (modeNext) {

    case 0:
      res.writeHead(200, {'Content-Type': 'text/html'});
      var html = "<h1>Demo page</h1>";
      getSomeDate(client, onNext);
      break;

    // handle someData
    case 1:
        html += "<p>"+ data +"</p>";
        getSomeOtherDate(client, onNext);
        break;

    // handle someOtherData
    case 2:
      html += "<p>"+ data +"</p>";
      getMoreData(client, onNext);
      break;

    // handle moreData
    case 3:
      html += "<p>"+ data +"</p>";
      res.write(html);
      res.end();
      break;

    // general catch-all error-handler
    default:
      res.statusCode = 500;
      res.end(error.message + '\n' + error.stack);
    }
  };
  onNext();
});
kai zhu
fonte
1

Recentemente, criei uma abstração mais simples chamada wait.for para chamar funções assíncronas no modo de sincronização (com base em fibras). Está em um estágio inicial, mas funciona. Está em:

https://github.com/luciotato/waitfor

Usando wait.for , você pode chamar qualquer função nodejs assíncrona padrão, como se fosse uma função de sincronização.

usando wait.for seu código pode ser:

var http=require('http');
var wait=require('wait.for');

http.createServer(function(req, res) {
  wait.launchFiber(handleRequest,req, res); //run in a Fiber, keep node spinning
}).listen(8080);


//in a fiber
function handleRequest(req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = "<h1>Demo page</h1>";
  var someData = wait.for(getSomeDate,client);
  html += "<p>"+ someData +"</p>";
  var someOtherData = wait.for(getSomeOtherDate,client);
  html += "<p>"+ someOtherData +"</p>";
  var moreData = wait.for(getMoreData,client);
  html += "<p>"+ moreData +"</p>";
  res.write(html);
  res.end();
};

... ou se você quiser ser menos detalhado (e também adicionar captura de erro)

//in a fiber
function handleRequest(req, res) {
  try {
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.write(
    "<h1>Demo page</h1>" 
    + "<p>"+ wait.for(getSomeDate,client) +"</p>"
    + "<p>"+ wait.for(getSomeOtherDate,client) +"</p>"
    + "<p>"+ wait.for(getMoreData,client) +"</p>"
    );
    res.end();
  }
  catch(err) {
   res.end('error '+e.message); 
  }

};

Em todos os casos, getSomeDate , getSomeOtherDate e getMoreData devem ser funções assíncronas padrão com o último parâmetro, um retorno de chamada de função (err, data)

como em:

function getMoreData(client, callback){
  db.execute('select moredata from thedata where client_id=?',[client.id],
       ,function(err,data){
          if (err) callback(err);
          callback (null,data);
        });
}
Lucio M. Tato
fonte
1

Para resolver esse problema, escrevi nodent ( https://npmjs.org/package/nodent ) que pré-processa invisivelmente seu JS. Seu código de exemplo se tornaria (assíncrono, realmente - leia os documentos).

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = "<h1>Demo page</h1>";
  someData <<= getSomeDate(client) ;

  html += "<p>"+ someData +"</p>";
  someOtherData <<= getSomeOtherDate(client) ;

  html += "<p>"+ someOtherData +"</p>";
  moreData <<= getMoreData(client) ;

  html += "<p>"+ moreData +"</p>";
  res.write(html);
  res.end();
});

Claramente, existem muitas outras soluções, mas o pré-processamento tem a vantagem de ter pouca ou nenhuma sobrecarga de tempo de execução e, graças ao suporte ao mapa de origem, é fácil depurar também.

MatAtBread
fonte
0

Eu tive o mesmo problema. Eu vi as principais bibliotecas para executar funções assíncronas em nó, e elas apresentam um encadeamento tão natural (você precisa usar três ou mais métodos confs, etc.) para criar seu código.

Passei algumas semanas desenvolvendo uma solução para ser simples e fácil de ler. Por favor, tente o EnqJS . Todas as opiniões serão apreciadas.

Ao invés de:

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = "<h1>Demo page</h1>";
  getSomeDate(client, function(someData) {
    html += "<p>"+ someData +"</p>";
    getSomeOtherDate(client, function(someOtherData) {
      html += "<p>"+ someOtherData +"</p>";
      getMoreData(client, function(moreData) {
        html += "<p>"+ moreData +"</p>";
        res.write(html);
        res.end();
      });
    });
  });

com EnqJS:

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = "<h1>Demo page</h1>";

  enq(function(){
    var self=this;
    getSomeDate(client, function(someData){
      html += "<p>"+ someData +"</p>";
      self.return();
    })
  })(function(){
    var self=this;
    getSomeOtherDate(client, function(someOtherData){ 
      html += "<p>"+ someOtherData +"</p>";
      self.return();
    })
  })(function(){
    var self=this;
    getMoreData(client, function(moreData) {
      html += "<p>"+ moreData +"</p>";
      self.return();
      res.write(html);
      res.end();
    });
  });
});

Observe que o código parece ser maior que antes. Mas não está aninhado como antes. Para parecer mais natural, as cadeias são chamadas imediatamente:

enq(fn1)(fn2)(fn3)(fn4)(fn4)(...)

E para dizer que retornou, dentro da função que chamamos:

this.return(response)
Thadeu de Paula
fonte
0

Eu faço isso de uma maneira bastante primitiva, mas eficaz. Por exemplo, preciso obter um modelo com seus pais e filhos e digamos que preciso fazer consultas separadas para eles:

var getWithParents = function(id, next) {
  var getChildren = function(model, next) {
        /*... code ... */
        return next.pop()(model, next);
      },
      getParents = function(model, next) {
        /*... code ... */
        return next.pop()(model, next);
      }
      getModel = function(id, next) {
        /*... code ... */
        if (model) {
          // return next callbacl
          return next.pop()(model, next);
        } else {
          // return last callback
          return next.shift()(null, next);
        }
      }

  return getModel(id, [getParents, getChildren, next]);
}
mvbl fst
fonte
0

Use Fibers https://github.com/laverdet/node-fibers para que o código assíncrono pareça síncrono (sem bloquear)

Eu pessoalmente uso esse pequeno invólucro http://alexeypetrushin.github.com/synchronize Exemplo de código do meu projeto (todo método é realmente assíncrono, trabalhando com E / S de arquivo assíncrono) Eu até tenho medo de imaginar que confusão seria com retorno de chamada ou bibliotecas auxiliares de controle de fluxo assíncrono.

_update: (version, changesBasePath, changes, oldSite) ->
  @log 'updating...'
  @_updateIndex version, changes
  @_updateFiles version, changesBasePath, changes
  @_updateFilesIndexes version, changes
  configChanged = @_updateConfig version, changes
  @_updateModules version, changes, oldSite, configChanged
  @_saveIndex version
  @log "updated to #{version} version"
Alexey Petrushin
fonte
0

Task.js oferece a você o seguinte:

spawn(function*() {
    try {
        var [foo, bar] = yield join(read("foo.json"),
                                    read("bar.json")).timeout(1000);
        render(foo);
        render(bar);
    } catch (e) {
        console.log("read failed: " + e);
    }
});

Em vez disso:

var foo, bar;
var tid = setTimeout(function() { failure(new Error("timed out")) }, 1000);

var xhr1 = makeXHR("foo.json",
                   function(txt) { foo = txt; success() },
                   function(err) { failure() });
var xhr2 = makeXHR("bar.json",
                   function(txt) { bar = txt; success() },
                   function(e) { failure(e) });

function success() {
    if (typeof foo === "string" && typeof bar === "string") {
        cancelTimeout(tid);
        xhr1 = xhr2 = null;
        render(foo);
        render(bar);
    }
}

function failure(e) {
    xhr1 && xhr1.abort();
    xhr1 = null;
    xhr2 && xhr2.abort();
    xhr2 = null;
    console.log("read failed: " + e);
}
Janus Troelsen
fonte
0

Depois que os outros responderam, você declarou que seu problema eram variáveis ​​locais. Parece que uma maneira fácil de fazer isso é escrever uma função externa para conter essas variáveis ​​locais, depois usar várias funções internas nomeadas e acessá-las pelo nome. Dessa forma, você apenas aninhará duas profundidades, independentemente de quantas funções você precise encadear.

Aqui está a tentativa do meu novato de usar o mysqlmódulo Node.js com aninhamento:

function with_connection(sql, bindings, cb) {
    pool.getConnection(function(err, conn) {
        if (err) {
            console.log("Error in with_connection (getConnection): " + JSON.stringify(err));
            cb(true);
            return;
        }
        conn.query(sql, bindings, function(err, results) {
            if (err) {
                console.log("Error in with_connection (query): " + JSON.stringify(err));
                cb(true);
                return;
            }
            console.log("with_connection results: " + JSON.stringify(results));
            cb(false, results);
        });
    });
}

A seguir, é reescrita usando funções internas nomeadas. A função externa também with_connectionpode ser usada como suporte para variáveis ​​locais. (Aqui, eu tenho os parâmetros sql, bindings, cbque atuam de forma semelhante, mas você pode apenas definir algumas variáveis locais adicionais with_connection.)

function with_connection(sql, bindings, cb) {

    function getConnectionCb(err, conn) {
        if (err) {
            console.log("Error in with_connection/getConnectionCb: " + JSON.stringify(err));
            cb(true);
            return;
        }
        conn.query(sql, bindings, queryCb);
    }

    function queryCb(err, results) {
        if (err) {
            console.log("Error in with_connection/queryCb: " + JSON.stringify(err));
            cb(true);
            return;
        }
        cb(false, results);
    }

    pool.getConnection(getConnectionCb);
}

Eu pensava que talvez fosse possível criar um objeto com variáveis ​​de instância e usá-las como um substituto para as variáveis ​​locais. Mas agora acho que a abordagem acima, usando funções aninhadas e variáveis ​​locais, é mais simples e fácil de entender. Demora algum tempo para desaprender OO, parece :-)

Então, aqui está minha versão anterior com um objeto e variáveis ​​de instância.

function DbConnection(sql, bindings, cb) {
    this.sql = sql;
    this.bindings = bindings;
    this.cb = cb;
}
DbConnection.prototype.getConnection = function(err, conn) {
    var self = this;
    if (err) {
        console.log("Error in DbConnection.getConnection: " + JSON.stringify(err));
        this.cb(true);
        return;
    }
    conn.query(this.sql, this.bindings, function(err, results) { self.query(err, results); });
}
DbConnection.prototype.query = function(err, results) {
    var self = this;
    if (err) {
        console.log("Error in DbConnection.query: " + JSON.stringify(err));
        self.cb(true);
        return;
    }
    console.log("DbConnection results: " + JSON.stringify(results));
    self.cb(false, results);
}

function with_connection(sql, bindings, cb) {
    var dbc = new DbConnection(sql, bindings, cb);
    pool.getConnection(function (err, conn) { dbc.getConnection(err, conn); });
}

Acontece que isso bindpode ser usado com alguma vantagem. Isso me permite livrar-me das funções anônimas um tanto feias que criei que não fizeram muito, exceto encaminhar-se para uma chamada de método. Não pude passar o método diretamente porque ele estaria envolvido com o valor errado de this. Mas com bind, eu posso especificar o valor thisque eu quero.

function DbConnection(sql, bindings, cb) {
    this.sql = sql;
    this.bindings = bindings;
    this.cb = cb;
}
DbConnection.prototype.getConnection = function(err, conn) {
    var f = this.query.bind(this);
    if (err) {
        console.log("Error in DbConnection.getConnection: " + JSON.stringify(err));
        this.cb(true);
        return;
    }
    conn.query(this.sql, this.bindings, f);
}
DbConnection.prototype.query = function(err, results) {
    if (err) {
        console.log("Error in DbConnection.query: " + JSON.stringify(err));
        this.cb(true);
        return;
    }
    console.log("DbConnection results: " + JSON.stringify(results));
    this.cb(false, results);
}

// Get a connection from the pool, execute `sql` in it
// with the given `bindings`.  Invoke `cb(true)` on error,
// invoke `cb(false, results)` on success.  Here,
// `results` is an array of results from the query.
function with_connection(sql, bindings, cb) {
    var dbc = new DbConnection(sql, bindings, cb);
    var f = dbc.getConnection.bind(dbc);
    pool.getConnection(f);
}

Obviamente, nada disso é JS adequado com a codificação Node.js. - passei algumas horas nele. Mas talvez com um pouco de polimento essa técnica possa ajudar?

hibbelig
fonte
0

Se você não quiser usar "step" ou "seq", tente "line", que é uma função simples para reduzir o retorno de chamada assíncrona aninhada.

https://github.com/kevin0571/node-line

Kevin
fonte
0

Assíncrona em c #-like é outra maneira de fazer isso

https://github.com/yortus/asyncawait

async(function(){

    var foo = await(bar());
    var foo2 = await(bar2());
    var foo3 = await(bar2());

}
Artur Stary
fonte
0

Usando wire, seu código ficaria assim:

http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/html'});

    var l = new Wire();

    getSomeDate(client, l.branch('someData'));
    getSomeOtherDate(client, l.branch('someOtherData'));
    getMoreData(client, l.branch('moreData'));

    l.success(function(r) {
        res.write("<h1>Demo page</h1>"+
            "<p>"+ r['someData'] +"</p>"+
            "<p>"+ r['someOtherData'] +"</p>"+
            "<p>"+ r['moreData'] +"</p>");
        res.end();
    });
});
Daniel Garmoshka
fonte
0

para seu conhecimento, considere Jazz.js https://github.com/Javanile/Jazz.js/wiki/Script-showcase

    const jj = require ('jazz.js');

    // pilha ultra-compatível
    jj.script ([
        a => ProcessTaskOneCallbackAtEnd (a),
        b => ProcessTaskTwoCallbackAtEnd (b),
        c => ProcessTaskThreeCallbackAtEnd (c),
        d => ProcessTaskFourCallbackAtEnd (d),
        e => ProcessTaskFiveCallbackAtEnd (e),
    ]);

cicciodarkast
fonte