如何避免在Node.js中嵌套asynchronous函数

我想创build一个页面来显示数据库中的一些数据,所以我创build了一些从我的数据库中获取数据的函数。 我只是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(); }); }); }); 

如果有这样的function,那么嵌套就成了一个问题

有没有办法避免这种情况? 我想这与你如何结合多个asynchronous函数有关,这似乎是一些基本的东西。

有趣的观察。 请注意,在JavaScript中,通常可以用命名的函数variablesreplace内联的匿名callback函数。

下列:

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

但是,除非您打算在其他地方重新使用callback逻辑,否则读取内联匿名函数通常要容易得多,如您的示例所示。 它也将免除你必须find所有callback名称。

另外请注意,如下面的注释中所指出的@ pst ,如果您正在访问内部函数内的闭包variables,上述内容将不是直接的翻译。 在这种情况下,使用内联匿名函数更为可取。

凯,只需使用这些模块之一。

  • streamJS
  • 恐惧
  • 期货
  • 追星族
  • 节点continuables
  • 滑动
  • 节点stream入
  • async.js
  • asynchronous

它会变成这样:

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

大多数情况下,我会同意Daniel Vassallo。 如果你能把一个复杂的深层嵌套函数分解成单独的命名函数,那么这通常是一个好主意。 对于在单个函数内部执行的时候,可以使用许多可用的node.jsasynchronous库之一。 人们已经想出了很多不同的方法来解决这个问题,所以看看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(); }); }); 

这种方法的一个好处是你可以快速地改变你的代码,通过改变'并行'的'系列'function并行获取数据。 更重要的是,async.js也可以在浏览器中使用,所以你可以使用和node.js相同的方法,如果你遇到任何棘手的asynchronous代码。

希望这是有用的!

你可以用数组而不是嵌套函数或模块来使用这个技巧。

在眼睛上更容易。

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

我非常喜欢async.js 。

这个问题是通过瀑布命令解决的:

瀑布(任务,[callback])

运行一系列函数,每个函数都将结果传递给数组中的下一个。 但是,如果任何函数将错误传递给callback,则不会执行下一个函数,并立即调用主callback并返回错误。

参数

任务 – 要运行的函数的数组,每个函数传递一个callback(err,result1,result2,…),它必须调用完成。 第一个参数是一个错误(可以是null),任何进一步的参数将作为parameter passing给下一个任务。 callback(err,[results]) – 一个可选的callback函数,一旦所有的函数完成运行。 这将传递最后一个任务callback的结果。

 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,resvariables,它们将在与函数(req,res){}相同的范围内被共享,这个函数包含了整个async.waterfall调用。

不仅如此,asynchronous非常干净。 我的意思是我改变了很多这样的情况:

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

首先:

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

然后对此:

 function2(o,cb); 

然后对此:

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

它还允许为util.js非常快地调用为async准备的许多预制函数。 只要把你想要做的事情链接起来,确保o,cb得到普遍的处理。 这加快了整个编码过程。

你需要的是一些语法糖。 嘿嘿:

 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成为一个数组。 这部分是因为string是不可变的,所以你最好把数组中的输出缓冲起来,而不是放弃更大更大的string。 另一个原因是因为另一个很好的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); } }; 

自从我find它以来,就爱上了Async.js 。 它有一个async.series函数可以用来避免长时间的嵌套。

文档: –


系列(任务,[callback])

运行一系列串联的函数,每个函数在前一个函数完成后运行。 […]

参数

tasks – 要运行的函数的数组,每个函数都传递一个callback,它必须在完成时调用。 callback(err, [results]) – 一个可选的callback函数,一旦所有的函数完成运行。 该函数获取传递给数组中使用的callback的所有参数的数组。


以下是我们如何将其应用于您的示例代码:

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

我见过的最简单的语法糖是节点承诺。

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

使用这个你可以链接asynchronous方法:

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

每个的返回值都可以作为下一个参数。

你在那里做的是采取一个asynchronous模式,并将其应用到3个顺序调用的函数中,每个函数在开始之前等待前一个函数完成,即使它们同步 。 关于asynchronous编程的一点是,您可以同时运行几个函数,而不必等待每个函数完成。

如果getSomeDate()不提供任何getSomeOtherDate(),getMoreData()不提供任何东西,那么为什么不像js允许的那样asynchronous地调用它们,或者它们是相互依赖的(而不是asynchronous的)把它们写成单一function?

您不需要使用嵌套来控制stream,例如,通过调用一个常用函数来确定每个函数的完成情况,该函数确定所有3个函数何时完成,然后发送响应。

假设你可以这样做:

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

我最近创build了一个叫wait.for的简单抽象,在同步模式下调用asynchronous函数(基于Fibers)。 这是在早期阶段,但工程。 它在:

https://github.com/luciotato/waitfor

使用wait.for ,你可以调用任何标准的nodejsasynchronous函数,就好像它是一个同步函数。

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

…或者如果你想减less冗长(也添加错误捕捉)

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

在所有情况下, getSomeDategetSomeOtherDategetMoreData应该是标准的asynchronous函数,最后一个参数是函数callback(err,data)

如下所示:

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

为了解决这个问题,我写了一个nodent( https://npmjs.org/package/nodent ),它隐式地预处理了你的JS。 你的示例代码将成为(asynchronous,真正阅读文档)。

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

显然,还有很多其他的解决scheme,但是预处理的好处是很less或者没有运行时间的开销,并且得益于源图支持,也很容易debugging。

callback地狱可以很容易地避免在纯JavaScript与封闭。 下面的解决scheme假设所有的callback都遵循函数(错误,数据)签名。

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

我有同样的问题。 我已经看到了节点运行asynchronous函数的主要库,并且它们呈现非自然的链接(您需要使用三个或更多方法confs等)来构build您的代码。

我花了几个星期的时间来开发一个简单易读的解决scheme。 请尝试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) 

我以一种非常原始但有效的方式来做。 例如,我需要与父母和孩子build立一个模型,假设我需要为他们做单独的查询:

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

使用Fibre https://github.com/laverdet/node-fibers它使asynchronous代码看起来像同步(没有阻塞);

我个人使用这个小封装http://alexeypetrushin.github.com/synchronize我的项目中的代码示例(每个方法实际上是asynchronous的,使用asynchronous文件IO),我甚至害怕想象什么是callback的混乱或asynchronous控制stream助手库。;

 _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为您提供:

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

其他人回答之后,你说你的问题是局部variables。 看起来一个简单的方法是编写一个外部函数来包含这些局部variables,然后使用一堆命名的内部函数并按名称访问它们。 这样,无论你需要链接多less个函数,你都只能嵌套两个深度。

这里是我的新手尝试在嵌套使用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也可以用作局部variables的持有者。 (在这里,我得到了参数sqlbindingscb ,它们的作用类似,但是你可以在with_connection定义一些额外的局部variables。)

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

我一直在想,也许可以用实例variables创build一个对象,并使用这些实例variables来替代局部variables。 但是现在我发现使用嵌套函数和局部variables的上述方法更简单,更容易理解。 看来OO需要一些时间,看来:-)

所以这里是我的以前版本的对象和实例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); }); } 

事实certificate, bind可以用于一些优势。 它允许我摆脱我创build的有些丑陋的匿名函数,除了将自己转向方法调用之外,没有做任何其他的事情。 我不能直接传递这个方法,因为它会涉及到这个错误的值。 但是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编码 – 我只是花了几个小时。 但也许有点抛光这种技术可以帮助?

async.js适用于此。 我遇到这个非常有用的文章,解释了async.js的需求和使用的例子: http : //www.sebastianseilund.com/nodejs-async-in-practice

如果你不想使用“step”或“seq”,请尝试使用“line”这个简单的函数来减less嵌套的asynchronouscallback。

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

类似于C#的asyncawait是另一种这样做的方式

https://github.com/yortus/asyncawait

 async(function(){ var foo = await(bar()); var foo2 = await(bar2()); var foo3 = await(bar2()); } 

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

为你的知道考虑Jazz.js https://github.com/Javanile/Jazz.js/wiki/Script-showcase


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

     //超级compat堆栈
     jj.script([
         a => ProcessTaskOneCallbackAtEnd(a),
         b => ProcessTaskTwoCallbackAtEnd(b),
         c => ProcessTaskThreeCallbackAtEnd(c),
         d => ProcessTaskFourCallbackAtEnd(d),
         e => ProcessTaskFiveCallbackAtEnd(e),
     ]);