简体   繁体   English

如何避免Node.js中异步函数的长时间嵌套

[英]How to avoid long nesting of asynchronous functions in Node.js

I want to make a page that displays some data from a DB, so I have created some functions that get that data from my DB. 我想制作一个页面来显示数据库中的一些数据,因此我创建了一些函数来从数据库中获取数据。 I'm just a newbie in Node.js, so as far as I understand, if I want to use all of them in a single page (HTTP response) I'd have to nest them all: 我只是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();
      });
    });
  });

If there are many functions like that, then the nesting becomes a problem . 如果有很多类似的功能,那么嵌套就会成为问题

Is there a way to avoid this? 有办法避免这种情况吗? I guess it has to do with how you combine multiple asynchronous functions, which seems to be something fundamental. 我想这与如何组合多个异步函数有关,这似乎是基本的东西。

Interesting observation. 有趣的观察。 Note that in JavaScript you can normally replace inline anonymous callback functions with named function variables. 请注意,在JavaScript中,通常可以将内联匿名回调函数替换为命名函数变量。

The following: 下列:

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

Could be rewritten to look something like this: 可以重写为如下所示:

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

However unless you plan to reuse to callback logic in other places, it is often much easier to read inline anonymous functions, as in your example. 但是,除非您打算在其他地方重用回调逻辑,否则读取内联匿名函数通常会更容易,如您的示例所示。 It will also spare you from having to find a name for all the callbacks. 这也将使您不必为所有回调找到名称。

In addition note that as @pst noted in a comment below, if you are accessing closure variables within the inner functions, the above would not be a straightforward translation. 另外请注意,正如@pst在下面的注释中指出的那样,如果您要在内部函数中访问闭包变量,则以上内容将不是简单的转换。 In such cases, using inline anonymous functions is even more preferable. 在这种情况下,使用内联匿名函数更为可取。

Kay, simply use one of these modules. 凯,只需使用这些模块之一即可。

It will turn this: 它将变成这样:

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

Into this: 变成这个:

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

You could use this trick with an array rather than nested functions or a module. 您可以将此技巧用于数组而不是嵌套函数或模块。

Far easier on the eyes. 在眼睛上容易得多。

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

You can extend the idiom for parallel processes or even parallel chains of processes: 您可以将成语扩展到并行流程甚至并行流程链:

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

For the most part, I'd agree with Daniel Vassallo. 在大多数情况下,我会同意Daniel Vassallo的观点。 If you can break up a complicated and deeply nested function into separate named functions, then that is usually a good idea. 如果您可以将一个复杂且深度嵌套的函数分解为单独的命名函数,那么通常是个好主意。 For the times when it makes sense to do it inside a single function, you can use one of the many node.js async libraries available. 在需要在单个函数中执行的情况下,可以使用许多可用的node.js异步库之一。 People have come up with lots of different ways to tackle this, so take a look at the node.js modules page and see what you think. 人们提出了许多不同的方法来解决此问题,因此请查看一下node.js模块页面,然后看看您的想法。

I've written a module for this myself, called async.js . 我自己为此编写了一个模块,称为async.js Using this, the above example could be updated to: 使用此,上面的示例可以更新为:

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

One nice thing about this approach is that you can quickly change your code to fetch the data in parallel by changing the 'series' function to 'parallel'. 这种方法的优点是,您可以通过将“系列”功能更改为“并行”来快速更改代码以并行获取数据。 What's more, async.js will also work inside the browser, so you can use the same methods as you would in node.js should you encounter any tricky async code. 而且,async.js还将在浏览器中运行,因此,如果遇到任何棘手的异步代码,您可以使用与node.js中相同的方法。

Hope that's useful! 希望这是有用的!

I like async.js a lot for this purpose. 为此,我非常喜欢async.js

The issue is solved by waterfall command: 该问题通过瀑布命令解决:

waterfall(tasks, [callback]) 瀑布(任务,[回调])

Runs an array of functions in series, each passing their results to the next in the array. 依次运行一个函数数组,每个函数将其结果传递给数组中的下一个函数。 However, if any of the functions pass an error to the callback, the next function is not executed and the main callback is immediately called with the error. 但是,如果有任何函数将错误传递给回调,则不会执行下一个函数,并且会立即以错误调用主回调。

Arguments 争论

tasks - An array of functions to run, each function is passed a callback(err, result1, result2, ...) it must call on completion. 任务-要运行的函数数组,每个函数都传递一个必须在完成时调用的回调(err,result1,result2,...)。 The first argument is an error (which can be null) and any further arguments will be passed as arguments in order to the next task. 第一个参数是错误(可以为null),任何其他参数将作为参数传递给下一个任务。 callback(err, [results]) - An optional callback to run once all the functions have completed. callback(err,[results])-在所有功能完成后运行的可选回调。 This will be passed the results of the last task's callback. 这将传递上一个任务的回调结果。

Example

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

As for the req,res variables, they will be shared within the same scope as function(req,res){} which enclosed the whole async.waterfall call. 至于req,res变量,它们将在与包围整个async.waterfall调用的function(req,res){}相同的范围内共享。

Not only so, async is very clean. 不仅如此,异步非常干净。 What I means is that I change a lot of cases like this: 我的意思是,我改变了很多这样的情况:

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

To first: 首先:

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

Then to this: 然后到此:

function2(o,cb);

Then to this: 然后到此:

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

It also allows many premade functions prepared for async to be called from util.js very fast. 它还允许非常快地从util.js中调用为异步准备的许多预制函数。 Just chain up what you want to do, make sure o,cb is universally handled. 只需将您想做的事情链接起来,确保可以普遍处理o,cb。 This speeds up the whole coding process a lot. 这大大加快了整个编码过程。

What you need is a bit of syntactic sugar. 您需要的是一些语法糖。 Chek this out: 看看这个:

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

Pretty neat , isn't it? 整齐漂亮,不是吗? You may notice that html became an array. 您可能会注意到html变成了数组。 That's partly because strings are immutable, so you better off with buffering your output in an array, than discarding larger and larger strings. 部分原因是字符串是不可变的,因此与丢弃越来越大的字符串相比,将输出缓冲在数组中更好。 The other reason is because of another nice syntax with bind . 另一个原因是因为bind的另一个不错的语法。

Queue in the example is really just an example and along with partial can be implemented as follows 示例中的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);
  }
};

Am in love Async.js ever since I found it. 自从发现Async.js以来,我一直深爱它。 It has a async.series function you can use to avoid long nesting. 它具有async.series函数,可用于避免长时间嵌套。

Documentation:- 文档:


series(tasks, [callback]) 系列(任务,[回调])

Run an array of functions in series, each one running once the previous function has completed. 依次运行一系列功能,每个功能在上一个功能完成后即可运行。 [...] [...]

Arguments 争论

tasks - An array of functions to run, each function is passed a callback it must call on completion. tasks -要运行的函数数组,每个函数都传递了一个回调,必须在完成时调用它。 callback(err, [results]) - An optional callback to run once all the functions have completed. callback(err, [results]) -在所有功能完成后运行的可选回调。 This function gets an array of all the arguments passed to the callbacks used in the array. 此函数获取传递给数组中使用的回调的所有参数的数组。


Here's how we can apply it to your example code:- 这是我们将其应用于您的示例代码的方法:-

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

The most simple syntactical sugar i have seen is node-promise. 我见过的最简单的语法糖是节点承诺。

npm install node-promise || npm安装节点承诺|| git clone https://github.com/kriszyp/node-promise git clone https://github.com/kriszyp/node-promise

Using this you can chain async methods as: 使用此方法,可以将异步方法链接为:

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

The return value of each is available as argument in the next. 每个参数的返回值都可以用作下一个参数。

What you have done there is take an asynch pattern and apply it to 3 functions called in sequence, each one waiting for the previous one to complete before starting - ie you have made them synchronous . 在此完成的工作采用了异步模式,并将其应用于依次调用的3个函数,每个函数在启动前都等待上一个函数完成-即,使它们同步 The point about asynch programming is that you can have several functions all running at once and not have to wait for each to complete. 关于异步编程的要点是,您可以同时运行多个功能,而不必等待每个功能都完成。

if getSomeDate() doesn't provide anything to getSomeOtherDate(), which doesn't provide anything to getMoreData() then why don't you call them asynchronously as js allows or if they are interdependent (and not asynchronous) write them as a single function? 如果getSomeDate()没有为getSomeOtherDate()提供任何内容,而没有为getMoreData()提供任何内容,那么为什么不按照js的要求异步调用它们,或者如果它们是相互依赖的(而不是异步的),则将它们写为单功能?

You don't need to use nesting to control the flow - for instance, get each function to finish by calling a common function that determines when all 3 have completed and then sends the response. 您无需使用嵌套来控制流程-例如,通过调用一个公共函数来确定每个函数的完成时间,该函数确定所有3个函数何时完成,然后发送响应。

callback hell can be easily avoided in pure javascript with closure. 在纯Javascript中使用闭包可以轻松避免回调地狱。 the solution below assumes all callbacks follow the function(error, data) signature. 下面的解决方案假定所有回调都遵循function(error,data)签名。

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

Suppose you could do this: 假设您可以这样做:

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

You only need to implement chain() so that it partially applies each function to the next one, and immediately invokes only the first function: 您只需要实现chain()即可将每个函数部分地应用到下一个函数,并立即仅调用第一个函数:

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

I've recently created simpler abstraction called wait.for to call async functions in sync mode (based on Fibers). 我最近创建了一个名为wait.for的更简单的抽象,以在同步模式下(基于Fibre)调用异步函数。 It's at an early stage but works. 它处于早期阶段,但可以正常工作。 It is at: 它是在:

https://github.com/luciotato/waitfor https://github.com/luciotato/waitfor

Using wait.for , you can call any standard nodejs async function, as if it were a sync function. 使用wait.for ,您可以调用任何标准的nodejs异步函数,就好像它是同步函数一样。

using wait.for your code could be: 使用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();
};

...or if you want to be less verbose (and also add error catching) ...或者如果您不想那么冗长(并添加错误捕获)

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

};

In all the cases, getSomeDate , getSomeOtherDate and getMoreData should be standard async functions with the last parameter a function callback(err,data) 在所有情况下, getSomeDategetSomeOtherDategetMoreData应该是标准异步函数,最后一个参数是函数callback(err,data)

as in: 如:

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

To solve this problem I wrote nodent ( https://npmjs.org/package/nodent ‎) which invisibly pre-processes your JS. 为了解决这个问题,我写了nodent( https://npmjs.org/package/nodent ),它无形地预处理了您的JS。 Your example code would become (async, really - read the docs). 您的示例代码将变为(异步,实际上-请阅读文档)。

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

Clearly, there are many other solutions, but pre-processing has the advantage of having little or no run-time overhead and thanks to source-map support it's easy to debug too. 显然,还有许多其他解决方案,但是预处理的优点是运行时开销极少或没有,而且得益于源映射的支持,调试也很容易。

I do it in a pretty primitive but effective way. 我以一种非常原始但有效的方式来做。 Eg I need to get a model with its parents and children and let's say I need to do separate queries for them: 例如,我需要与父母和孩子一起建立一个模型,假设我需要为他们做单独的查询:

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

Use Fibers https://github.com/laverdet/node-fibers it makes asynchronous code looks like synchronous (without blocking) 使用Fibers https://github.com/laverdet/node-fibers,它使异步代码看起来像同步代码(无阻塞)

I personally use this little wrapper http://alexeypetrushin.github.com/synchronize Sample of code from my project (every method is actually asynchronous, working with async file IO) I even afraid of imagine what a mess it would be with callback or async-control-flow helper libraries. 我个人使用这个小包装程序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"

Task.js offers you this: 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);
    }
});

Instead of this: 代替这个:

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

After the others responded, you stated that your problem were local variables. 其他人回答后,您说您的问题是局部变量。 It seems an easy way to do this is to write one outer function to contain those local variables, then use a bunch of named inner functions and access them by name. 似乎很简单的方法是编写一个外部函数以包含那些局部变量,然后使用一堆命名内部函数并按名称访问它们。 This way, you'll only ever nest two deep, regardless of how many functions you need to chain together. 这样,无论需要链接多少个函数,您都只会嵌套两个深度。

Here is my newbie's attempt at using the mysql Node.js module with nesting: 这是我的新手尝试通过嵌套使用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);
        });
    });
}

The following is a rewrite using named inner functions. 以下是使用命名内部函数的重写。 The outer function with_connection can be used as a holder for local variables, too. 外部函数with_connection也可以用作局部变量的持有者。 (Here, I've got the parameters sql , bindings , cb that act in a similar way, but you can just define some additional local variables in with_connection .) (这里,我有类似的参数sqlbindingscb ,但是您可以在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);
}

I had been thinking that perhaps it would be possible to make an object with instance variables, and to use these instance variables as a replacement for the local variables. 我一直在想,也许可以用实例变量创建一个对象,并用这些实例变量代替局部变量。 But now I find that the above approach using nested functions and local variables is simpler and easier to understand. 但是现在我发现使用嵌套函数和局部变量的上述方法更简单易懂。 It takes some time to unlearn OO, it seems :-) 取消学习OO需要花费一些时间,看来:-)

So here is my previous version with an object and instance variables. 所以这是我先前的版本,其中包含对象和实例变量。

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

It turns out that bind can be used to some advantage. 事实证明, bind可以使用一些优势。 It allows me to get rid of the somewhat ugly anonymous functions I've created that didn't do anything much, except to forward themselves to a method call. 它使我摆脱了我创建的有些丑陋的匿名函数,除了将自己转发给方法调用之外,它们并没有做任何其他事情。 I couldn't pass the method directly because it would have been involved with the wrong value of this . 我无法直接传递该方法,因为它会涉及到this的错误值。 But with bind , I can specify the value of this that I want. 但随着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);
}

Of course, none of this is proper JS with Node.js coding -- I just spent a couple of hours on it. 当然,这些都不是带有Node.js编码的正确JS -我只是花了几个小时。 But maybe with a little polishing this technique can help? 但是,也许稍微抛光一下,这项技术就能有所帮助吗?

async.js works well for this. async.js为此很好地工作。 I came across this very useful article which explains the need and use of async.js with examples: http://www.sebastianseilund.com/nodejs-async-in-practice 我碰到了这篇非常有用的文章,其中举例说明了async.js的需求和用法: http : //www.sebastianseilund.com/nodejs-async-in-practice

If you don't want to use "step" or "seq", please try "line" which is a simple function to reduce nested async callback. 如果您不想使用“ step”或“ seq”,请尝试“ line”,这是减少嵌套异步回调的简单函数。

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

C#-like asyncawait is another way of doing this 类似于C#的asyncawait是另一种方式

https://github.com/yortus/asyncawait https://github.com/yortus/asyncawait

async(function(){

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

}

Using wire your code would look like this: 使用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();
    });
});

for your know consider Jazz.js https://github.com/Javanile/Jazz.js/wiki/Script-showcase 就您所知,请考虑使用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),
    ]);

I had the same problem. 我有同样的问题。 I've seen the major libs to node run async functions, and they presents so non-natural chaining (you need to use three or more methods confs etc) to build your code. 我已经看到节点运行异步函数的主要库,它们呈现出非自然的链接(您需要使用三个或更多方法confs等)来构建代码。

I spent some weeks developing a solution to be simple and easing to read. 我花了几周的时间来开发一种简单易读的解决方案。 Please, give a try to EnqJS . 请尝试EnqJS All opinions will be appreciated. 所有意见将不胜感激。

Instead of: 代替:

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

with EnqJS: 使用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 that the code appears to be bigger than before. 请注意,该代码似乎比以前更大。 But it isn't nested as before. 但是它不像以前那样嵌套。 To appear more natural, the chains are called imediately: 为了看起来更自然,这些链被立即称为:

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

And to say that it returned, inside the function we call: 并说它返回了,在我们调用的函数内部:

this.return(response)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM