如何避免在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); } };
在所有情况下, getSomeDate , getSomeOtherDate和getMoreData应该是标准的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代码看起来像同步(没有阻塞);
_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的持有者。 (在这里,我得到了参数sql
, bindings
, cb
,它们的作用类似,但是你可以在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。
类似于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), ]);