it-roy-ru.com

Как избежать длительного вложения асинхронных функций в 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();
      });
    });
  });

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

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

153
Kay Pale

Интересное наблюдение. Обратите внимание, что в 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 отмечено в комментарии ниже, если вы обращаетесь к переменным замыкания во внутренних функциях, приведенное выше не будет простым переводом. В таких случаях использование встроенных анонимных функций еще более предпочтительно. 

72
Daniel Vassallo

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

Это превратит это:

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

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

Гораздо проще на глазах.

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

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

Приятной особенностью этого подхода является то, что вы можете быстро изменить свой код для параллельного извлечения данных, изменив функцию 'series' на'rallel '. Более того, async.js будет Также работать в браузере, поэтому вы можете использовать те же методы, что и в node.js, если столкнетесь с любым хитрым асинхронным кодом.

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

18
Caolan

Мне нравится 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)

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

15
Grant Li

То, что вам нужно, это немного синтаксического сахара. Проверьте это:

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 стал массивом. Это отчасти потому, что строки неизменяемы, поэтому лучше буферизовать вывод в массиве, чем отбрасывать все большие и большие строки. Другая причина - в другом синтаксисе Nice с 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);
  }
};
11
gblazex

Я влюблен 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();
    });
});
7
Salman Abbas

Самый простой синтаксический сахар, который я видел, это обещание узла.

npm установить узел-обещание || git clone https://github.com/kriszyp/node-promise

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

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

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

6
Nikhil Ranjan

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

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

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

3
Nick Tulett

ада обратного вызова можно легко избежать в чистом 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();
});
2
kai zhu

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

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

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

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

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

https://github.com/luciotato/waitfor

Используя wait.for, вы можете вызвать любую стандартную асинхронную функцию 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 Должны быть стандартными асинхронными функциями с последним параметром a обратный вызов функции (ошибка, данные) )

как в:

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);
        });
}
1
Lucio M. Tato

Чтобы решить эту проблему, я написал 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();
});

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

1
MatAtBread

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

0
learner_19

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);
}
0
Janus Troelsen

C # -подобный asyncawait - это еще один способ сделать это

https://github.com/yortus/asyncawait

async(function(){

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

}
0
Artur Stary

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

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

 // ультракомпактный стек 
 jj.script ([
 a => ProcessTaskOneCallbackAtEnd (a), 
 b => ProcessTaskTwoCallbackAtEnd (b), 
 c => ProcessTaskThreeCallbackAtEnd (c), 
 d => ProcessTaskFourCallbackAtEnd () __. e => ProcessTaskFiveCallbackAtEnd (e), 
]); 

0
cicciodarkast

Используя 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();
    });
});
0
Dan Key

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

Вот попытка моего новичка использовать модуль 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);
}

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

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

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 - я просто потратил пару часов на это. Но, может быть, с небольшой полировкой эта техника может помочь?

0
hibbelig

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

Я потратил несколько недель на разработку простого и удобного для чтения решения. Пожалуйста, попробуйте 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)
0
Thadeu de Paula

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

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

_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"
0
Alexey Petrushin

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

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

0
Kevin

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

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