Как избежать длинной вложенности асинхронных функций в Node.js

Я хочу сделать страницу, которая отображает некоторые данные из БД, поэтому я создал некоторые функции, которые получают эти данные из моей БД. Я просто новичок в Node.js, поэтому, насколько я понимаю, если я хочу использовать их все на одной странице (HTTP-ответ), мне нужно было бы их разложить:

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();
      });
    });
  });

Если таких функций много, то вложенность становится проблемой.

Есть ли способ избежать этого? Я предполагаю, что это связано с тем, как вы объединяете несколько асинхронных функций, которые, как представляется, являются фундаментальными.

Ответ 1

Интересное наблюдение. Обратите внимание, что в JavaScript вы обычно можете заменить встроенные анонимные функции обратного вызова с именованными функциональными переменными.

Следующее:

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 ...
});

Можно переписать, чтобы выглядеть примерно так:

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);

Однако, если вы не планируете повторно использовать логику обратного вызова в других местах, часто гораздо проще читать встроенные анонимные функции, как в вашем примере. Это также избавит вас от необходимости находить имя для всех обратных вызовов.

Кроме того, обратите внимание, что в @pst, отмеченном в комментарии ниже, если вы обращаетесь к переменным закрытия внутри внутренних функций, это не будет простым переводом. В таких случаях предпочтительнее использовать встроенные анонимные функции.

Ответ 2

Kay, просто используйте один из этих модулей.

Он превратит это:

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();
            });
        });
    });
});

В это:

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()
    }
);

Ответ 3

По большей части, я согласен с Даниэлем Вассалло. Если вы можете разбить сложную и глубоко вложенную функцию на отдельные именованные функции, то это обычно хорошая идея. В те моменты, когда имеет смысл делать это внутри одной функции, вы можете использовать одну из многочисленных доступных асинхронных библиотек node.js. Люди придумали много разных способов решения этой проблемы, поэтому взгляните на страницу модулей node.js и посмотрите, что вы думаете.

Я написал для себя модуль, называемый async.js. Используя это, приведенный выше пример может быть обновлен до:

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();
  });
});

Одна из приятных вещей в этом подходе заключается в том, что вы можете быстро изменить свой код для параллельной выборки данных, изменив функцию "серия" на "параллельно". Что еще, async.js будет также работают внутри браузера, поэтому вы можете использовать те же методы, что и в node.js, если вы столкнулись с каким-нибудь сложным асинхронным кодом.

Надеюсь, что это полезно!

Ответ 4

Вы можете использовать этот трюк с помощью массива, а не вложенных функций или модуля.

Легче смотреть на глаза.

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()();

Вы можете расширить идиому для параллельных процессов или даже параллельных цепочек процессов:

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()();

Ответ 5

Мне нравится async.js для этой цели.

Проблема решается командой водопада:

водопад (задачи, [обратный вызов])

Запускает массив последовательностей, каждый из которых передает результаты в следующий массив. Однако, если какая-либо из функций передает ошибку обратному вызову, следующая функция не выполняется, и основной обратный вызов немедленно вызывается с ошибкой.

Аргументы

tasks - массив функций для запуска, каждой функции передается обратный вызов (err, result1, result2,...), который он должен вызывать при завершении. Первый аргумент - ошибка (которая может быть нулевой), и любые другие аргументы будут переданы в качестве аргументов для следующей задачи. callback (err, [results]) - необязательный обратный вызов для запуска после завершения всех функций. Это будет передано результаты последнего обратного вызова задачи.

Пример

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'    
});

Что касается переменных req, res, они будут совместно использоваться в той же области, что и функция (req, res) {}, которая закрыла весь вызов async.waterfall.

Не только так, async очень чистый. Я имею в виду, что я меняю много случаев следующим образом:

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

Во-первых:

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

Затем:

function2(o,cb);

Затем:

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

Он также позволяет много готовых функций, подготовленных для асинхронного вызова из util.js очень быстро. Просто подключите то, что вы хотите сделать, убедитесь, что o, cb используется повсеместно. Это значительно ускоряет весь процесс кодирования.

Ответ 6

Вам нужно немного синтаксического сахара. Чек это:

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();
}); 

Довольно опрятный, не так ли? Вы можете заметить, что html стал массивом. Это отчасти потому, что строки неизменяемы, поэтому вам лучше с буферизацией вывода в массиве, чем отбрасывание больших и больших строк. Другая причина связана с другим хорошим синтаксисом с bind.

Queue в этом примере - просто пример и вместе с partial может быть реализован следующим образом

// 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);
  }
};

Ответ 7

Я влюблен Async.js с тех пор, как нашел его. У него есть функция async.series, которую вы можете использовать, чтобы избежать длительного вложенности.

Документация: -


серия (задачи, [обратный вызов])

Запустите массив последовательностей, каждый из которых запускается после завершения предыдущей функции. [...]

Аргументы

tasks - массив функций для запуска, каждой функции передается обратный вызов, который он должен вызывать при завершении. callback(err, [results]) - Дополнительный обратный вызов для запуска после завершения всех функций. Эта функция получает массив всех аргументов, переданных обратным вызовам, используемым в массиве.


Вот как мы можем применить его к вашему примеру: -

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();
    });
});

Ответ 8

Самый простой синтаксический сахар, который я видел, - node -promise.

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

Используя это, вы можете использовать методы async как:

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

Возвращаемое значение каждого доступно в качестве аргумента в следующем.

Ответ 9

То, что вы там сделали, - это взять асинхронный шаблон и применить его к трем функциям, вызываемым последовательно, каждый из которых ожидает завершения предыдущего, прежде чем запускать - т.е. вы сделали их синхронными. Дело в асинхронном программировании состоит в том, что вы можете запускать сразу несколько функций и не дожидаться завершения каждого из них.

если getSomeDate() не предоставляет ничего для getSomeOtherDate(), который не предоставляет ничего для getMoreData(), то почему бы вам не назвать их асинхронно, поскольку js разрешает или если они взаимозависимы (а не асинхронны) их как одну функцию?

Вам не нужно использовать вложенность для управления потоком - например, чтобы завершить каждую функцию, вызвав общую функцию, которая определяет, когда все 3 завершили, а затем отправит ответ.

Ответ 10

Предположим, вы могли бы сделать это:

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();
        }
    ]);
});

Вам нужно всего лишь реализовать цепочку(), чтобы она частично применяла каждую функцию к следующей и сразу вызывала только первую функцию:

function chain(fs) {
    var f = function () {};
    for (var i = fs.length - 1; i >= 0; i--) {
        f = fs[i].partial(f);
    }
    f();
}

Ответ 11

Недавно я создал более простую абстракцию под названием wait.for для вызова асинхронных функций в режиме синхронизации (на основе Fibers). Это на ранней стадии, но работает. Он находится по адресу:

https://github.com/luciotato/waitfor

Используя wait.for, вы можете вызвать любую стандартную функцию async nodejs, как если бы это была функция синхронизации.

используя wait.for, ваш код может быть:

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();
};

... или если вы хотите быть менее подробным (а также добавить ошибку)

//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); 
  }

};

Во всех случаях getSomeDate, getSomeOtherDate и getMoreData должны быть стандартными асинхронными функциями с последним параметром функции обратного вызова (err, data)

как в:

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);
        });
}

Ответ 12

Чтобы решить эту проблему, я написал nodent (https://npmjs.org/package/nodent), который невидимо предварительно обрабатывает вашу JS. Ваш примерный код станет (асинхронно, действительно - прочитайте документы).

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();
});

Очевидно, что существует много других решений, но предварительная обработка имеет то преимущество, что у них мало или вообще нет избыточных затрат времени, а благодаря поддержке исходной карты ее легко отлаживать.

Ответ 13

callback hell можно легко избежать в чистом javascript с закрытием. нижеприведенное решение предполагает, что все обратные вызовы следуют за сигналом функции (ошибка, данные).

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();
});

Ответ 14

У меня была та же проблема. Я видел основные libs для node для запуска асинхронных функций, и они представляют такую ​​неприродную цепочку (вам нужно использовать три или более методов confs и т.д.) Для создания вашего кода.

Я потратил несколько недель на разработку решения, которое было простым и облегчало чтение. Попробуйте EnqJS. Все мнения будут оценены.

Вместо:

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();
      });
    });
  });

с 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();
    });
  });
});

Обратите внимание, что код выглядит больше, чем раньше. Но он не вложен, как раньше. Чтобы выглядеть более естественным, цепи называются сразу:

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

И чтобы сказать, что он вернулся, внутри вызываемой функции:

this.return(response)

Ответ 15

Я делаю это довольно примитивно, но эффективно. Например. Мне нужно получить модель со своими родителями и детьми, и пусть мне нужно сделать для них отдельные запросы:

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]);
}

Ответ 16

Использовать волокна https://github.com/laverdet/node-fibers, что делает асинхронный код похожим на синхронный (без блокировки)

Я лично использую эту небольшую обертку http://alexeypetrushin.github.com/synchronize Пример кода из моего проекта (каждый метод на самом деле асинхронный, работающий с IO-асинхронным файлом). Я даже боюсь представить, какой беспорядок он будет с помощью вспомогательных библиотек обратного вызова или асинхронного управления.

_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"

Ответ 17

Task.js предлагает вам следующее:

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);
    }
});

Вместо этого:

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);
}

Ответ 18

После того, как остальные ответили, вы заявили, что ваша проблема связана с локальными переменными. Кажется, простой способ сделать это - написать одну внешнюю функцию, чтобы содержать эти локальные переменные, а затем использовать кучу названных внутренних функций и получить к ним доступ по имени. Таким образом, вы только когда-либо сможете развить две глубины, независимо от того, сколько функций вам нужно объединить.

Вот моя попытка новичков в использовании модуля mysql Node.js с вложением:

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);
        });
    });
}

Ниже приведена перепись с использованием названных внутренних функций. Внешняя функция with_connection также может использоваться как держатель для локальных переменных. (Здесь у меня есть параметры sql, bindings, cb, которые действуют аналогичным образом, но вы можете просто определить некоторые дополнительные локальные переменные в 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);
}

Я думал, что возможно было бы создать объект с переменными экземпляра и использовать эти переменные экземпляра в качестве замены локальных переменных. Но теперь я нахожу, что вышеупомянутый подход с использованием вложенных функций и локальных переменных проще и понятнее. Требуется некоторое время, чтобы освободить OO, кажется: -)

Итак, вот моя предыдущая версия с переменными объекта и экземпляра.

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); });
}

Оказывается, что bind можно использовать для некоторого преимущества. Это позволяет мне избавиться от несколько уродливых анонимных функций, которые я создал, которые ничего не делали, кроме как переслать вызов метода. Я не мог передать метод напрямую, потому что он был бы связан с неправильным значением this. Но с bind я могу указать значение this, которое я хочу.

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);
}

Конечно, ничто из этого не является правильным JS с кодировкой Node.js - я просто потратил пару часов на это. Но, может быть, с небольшой полировкой эта техника может помочь?

Ответ 19

async.js хорошо работает для этого. Я столкнулся с этой очень полезной статьей, в которой объясняется необходимость и использование async.js с примерами: http://www.sebastianseilund.com/nodejs-async-in-practice

Ответ 20

Если вы не хотите использовать "шаг" или "seq", попробуйте "строка", которая является простой функцией для уменьшения вложенного обратного вызова асинхронного вызова.

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

Ответ 21

С#-like asyncawait - это еще один способ сделать это

https://github.com/yortus/asyncawait

async(function(){

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

}

Ответ 22

Используя wire, ваш код будет выглядеть следующим образом:

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();
    });
});

Ответ 23

для вашего знания рассмотрите Jazz.js https://github.com/Javanile/Jazz.js/wiki/Script-showcase

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

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