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

158

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

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

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

Кей Пэйл
источник
12
Итак, когда у вас есть 10 асинхронных функций, у вас есть 10 уровней отступа?
Кей Пэл
Эта ссылка может помочь. stackoverflow.com/a/4631909/290340
Эван Плейс
1
Другая проблема: вставка другой функции между getSomeDateи в getSomeOtherDateконечном итоге приводит к изменению отступа многих строк, что затрудняет чтение истории git ( git blameдаже бесполезно после этого), и вы, вероятно, делаете ошибки при этом вручную
Daniel Alder

Ответы:

73

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

Даниэль Вассалло
источник
26
Однако (и действительно просто для того, чтобы понять компромисс), когда он не вложен, некоторая семантика замыкания над переменными может быть потеряна, так что это не прямой перевод. В приведенном выше примере доступ к 'res' в getMoreDataпотеряно.
2
Я думаю, что ваше решение не работает: на someDataParserсамом деле анализирует ВСЕ данные, так как он также вызывает getMoreData. В этом смысле имя функции является неправильным, и становится очевидно, что мы фактически не устранили проблему с вложенностью.
Константин Шуберт
63

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

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

dbGet('userIdOf:bobvance', function(userId) {
    dbSet('user:' + userId + ':email', 'bobvance@potato.egg', 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', 'bobvance@potato.egg', this.MULTI());
        dbSet('user:' + userId + ':firstName', 'Bob', this.MULTI());
        dbSet('user:' + userId + ':lastName', 'Vance', this.MULTI());

    },function() {
        okWeAreDone()
    }
);
Baggz
источник
9
Мы быстро взглянули на flow-js, step и async, и кажется, что они имеют дело только с порядком выполнения функции. В моем случае есть доступ к встроенным переменным замыкания в каждом отступе. Так, например, функции работают так: получить HTTP req / res, получить ID пользователя из БД для cookie, получить электронную почту для более позднего ID пользователя, получить больше данных для более поздней электронной почты, ..., получить X для более поздней Y, ... Если я не ошибаюсь, эти фреймворки только гарантируют, что асинхронные функции будут выполняться в правильном порядке, но в каждом теле функции нет способа получить переменную, предоставленную естественным образом замыканиями (?) Спасибо :)
Kay Pale
9
Что касается ранжирования этих библиотек, я проверил количество звездочек на каждой из них на Github. У async больше всего около 3000, у Step - около 1000, остальные значительно меньше. Конечно, они не все делают одно и то же :-)
kgilpin
3
@KayPale Я склонен использовать async.waterfall и иногда буду иметь свои собственные функции для каждого этапа / шага, которые будут передавать то, что нужно следующему шагу, или определять переменные перед вызовом async.METHOD, чтобы он был доступен для нижестоящих. Также будет использовать METHODNAME.bind (...) для моих асинхронных вызовов *, что тоже неплохо работает.
Tracker1
Небольшой вопрос: совпадают ли последние два модуля в вашем списке? То есть "async.js" и "async"
dari0h
18

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

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

Caolan
источник
Привет Caolan и спасибо за ответ! В моем случае есть доступ к встроенным переменным замыкания в каждом отступе. Так, например, функции работают так: получить HTTP req / res, получить ID пользователя из БД для cookie, получить электронную почту для более позднего ID пользователя, получить больше данных для более поздней электронной почты, ..., получить X для более поздней Y, ... Если я не ошибаюсь, предлагаемый вами код только гарантирует, что асинхронные функции будут выполняться в правильном порядке, но в каждом теле функции нет способа получить переменную, предоставленную естественным образом замыканиями в моем исходном коде. Это тот случай?
Кей Пэл
3
То, что вы пытаетесь достичь, архитектурно называется конвейером данных. Вы можете использовать асинхронный водопад для таких случаев.
Рудольф Мейеринг
18

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

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

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()();
Guido
источник
15

Мне очень нравится 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 универсально обработан. Это значительно ускоряет весь процесс кодирования.

Грант Ли
источник
11

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

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);
  }
};
gblazex
источник
1
Queue.execute () просто выполнит партиалы один за другим, не ожидая результатов от асинхронных вызовов.
нг
Пятно, спасибо. Я обновил ответ. Вот тест: jsbin.com/ebobo5/edit (с дополнительной lastфункцией)
gblazex
Привет галамбалаз и спасибо за ответ! В моем случае есть доступ к встроенным переменным замыкания в каждом отступе. Так, например, функции работают так: получить HTTP req / res, получить ID пользователя из БД для cookie, получить электронную почту для более позднего ID пользователя, получить больше данных для более поздней электронной почты, ..., получить X для более поздней Y, ... Если я не ошибаюсь, предлагаемый вами код только гарантирует, что асинхронные функции будут выполняться в правильном порядке, но в каждом теле функции нет способа получить переменную, предоставленную естественным образом замыканиями в моем исходном коде. Это тот случай?
Кей Пэл
1
Ну, вы определенно теряете замыкания во всех ответах. Что вы можете сделать, это создать объект в глобальной области для общих данных. Так, например, ваша первая функция добавляет, obj.emailа ваша следующая функция использует, а obj.emailзатем удаляет ее (или просто присваивает null).
gblazex
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();
    });
});
Салман фон Аббас
источник
6

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

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

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

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

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

Нихил Ранджан
источник
3

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

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

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

Ник Тулетт
источник
2

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

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();
}
СПП
источник
Привет ngn и спасибо за ответ! В моем случае есть доступ к встроенным переменным замыкания в каждом отступе. Так, например, функции работают так: получить HTTP req / res, получить ID пользователя из БД для cookie, получить электронную почту для более позднего ID пользователя, получить больше данных для более поздней электронной почты, ..., получить X для более поздней Y, ... Если я не ошибаюсь, предлагаемый вами код только гарантирует, что асинхронные функции будут выполняться в правильном порядке, но в каждом теле функции нет способа получить переменную, предоставленную естественным образом замыканиями в моем исходном коде. Это тот случай?
Кей Пэл
2

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

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

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

как в:

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

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

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

MatAtBread
источник
0

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

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

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

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
источник
0

Используйте 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

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

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

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

hibbelig
источник
0

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

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

Kevin
источник
0

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

https://github.com/yortus/asyncawait

async(function(){

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

}
Артур Старый
источник
0

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

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

для вашего ознакомления рассмотрим 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 (d),
        e => ProcessTaskFiveCallbackAtEnd (e),
    ]);

cicciodarkast
источник