2010-11-20 95 views
145

我想创建一个显示数据库中某些数据的页面,所以我创建了一些从我的数据库中获取数据的函数。我只是在Node.js的新手,所以据我了解,如果我想用所有的人都在一个单一页面(HTTP响应)我不得不窝他们都:如何避免Node.js中异步函数的长嵌套

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个级别的缩进? – 2010-11-20 20:32:56

+0

此链接可能会有所帮助。 http://stackoverflow.com/a/4631909/290340 – 2012-01-24 22:28:47

+0

另一个问题:在'getSomeDate'和'getSomeOtherDate'之间插入另一个函数最终会改变许多行的缩进,这使得git历史难以阅读('git blame'甚至在此之后无用),并且在手动执行此操作时可能会产生错误 – 2014-12-30 02:57:45

回答

69

有趣的观察。请注意,在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在下面的注释中注明,如果您正在访问内部函数内的闭包变量,上述内容将不是一个简单的翻译。在这种情况下,使用内联匿名函数更为可取。

+24

但是,(并且实际上仅仅是为了理解折衷)在非嵌套时,变量*上的某些闭包语义可能会丢失,因此它不是直接翻译。在上面的例子中,getMoreData中'res'的访问会丢失。 – 2010-11-20 20:08:17

+0

@pst:是的好点。让我在我的答案中澄清一下。 – 2010-11-20 20:10:54

+2

我认为你的解决方案已经坏了:'someDataParser'确实解析了所有数据,因为它也调用'getMoreData'。从这个意义上讲,函数名称是不正确的,很明显我们并没有真正移除嵌套问题。 – 2013-10-02 15:49:13

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

Queue.execute()将一个接一个地执行部分,而不等待异步调用的结果。 – ngn 2010-11-22 05:30:25

+0

现货,谢谢。我已经更新了答案。这里是一个测试:http://jsbin.com/ebobo5/edit(带有可选的'last'函数) – galambalazs 2010-11-22 20:36:42

+0

嗨galambalazs和谢谢你的答案!在我的情况下,每个缩进都可以访问内联闭包变量。因此,例如,函数的工作方式如下所示:获取HTTP req/res,从数据库获取用于cookie的用户标识,为稍后的用户标识获取电子邮件,为以后的电子邮件获取更多数据,...,为以后的Y获取X ...如果我没有弄错,那么你建议的代码只能确保异步函数按照正确的顺序执行,但是在每个函数体中都没有办法获得由我原始代码中的闭包自然提供的变量。是这样吗? – 2010-12-05 20:01:41

3

你做了什么有采取非同步模式,它适用于3个函数调用序列,每个实施一个在开始前等待前一个完成 - 也就是说你已经使它们同步。关于异步编程的一点是,您可以同时运行多个函数,而无需等待每个函数完成。如果getSomeDate()没有提供任何getSomeOtherDate(),它没有提供任何getMoreData(),那么你为什么不像js允许的那样异步地调用它们,或者它们是相互依赖的(而不是异步的)把它们写成一个单一的函数?

你并不需要使用嵌套控制流量 - 例如,了解各种函数调用,用于确定何时所有3个已完成,然后发送响应的共同作用来完成。

18

大多数情况下,我会同意Daniel Vassallo。如果你可以将一个复杂的深层嵌套函数分解成单独的命名函数,那么这通常是一个好主意。对于在单个函数中执行它的意义时,您可以使用许多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(); 
    }); 
}); 

的一个好处是,你可以快速改变你的代码通过改变“系列”功能“水货”平行获取数据。更重要的是,async.js将 也可以在浏览器中运行,所以如果遇到任何棘手的异步代码,您可以使用与node.js中相同的方法。

希望有用!

+0

Hi Caolan,谢谢你的回答!在我的情况下,每个缩进都可以访问内联闭包变量。因此,例如,函数的工作方式如下所示:获取HTTP req/res,从数据库获取用于cookie的用户标识,为稍后的用户标识获取电子邮件,为以后的电子邮件获取更多数据,...,为以后的Y获取X ...如果我没有弄错,那么你建议的代码只能确保异步函数按照正确的顺序执行,但是在每个函数体中都没有办法获得由我原始代码中的闭包自然提供的变量。是这样吗? – 2010-12-05 20:03:50

+3

您试图实现的是从体系结构上称为数据管道。对于这种情况你可以使用异步瀑布。 – 2012-06-27 09:06:13

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

您只需要执行链(),使其部分每个功能适用于下一个,并立即调用只有第一个功能:

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

您好,感谢您的回答!在我的情况下,每个缩进都可以访问内联闭包变量。因此,例如,函数的工作方式如下所示:获取HTTP req/res,从数据库获取用于cookie的用户标识,为稍后的用户标识获取电子邮件,为以后的电子邮件获取更多数据,...,为以后的Y获取X ...如果我没有弄错,那么你建议的代码只能确保异步函数按照正确的顺序执行,但是在每个函数体中都没有办法获得由我原始代码中的闭包自然提供的变量。是这样吗? – 2010-12-05 20:02:04

62

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

它会变成这样:

dbGet('userIdOf:bobvance', function(userId) { 
    dbSet('user:' + userId + ':email', '[email protected]', function() { 
     dbSet('user:' + userId + ':firstName', 'Bob', function() { 
      dbSet('user:' + userId + ':lastName', 'Vance', function() { 
       okWeAreDone(); 
      }); 
     }); 
    }); 
}); 

进入这个:

flow.exec(
    function() { 
     dbGet('userIdOf:bobvance', this); 

    },function(userId) { 
     dbSet('user:' + userId + ':email', '[email protected]', this.MULTI()); 
     dbSet('user:' + userId + ':firstName', 'Bob', this.MULTI()); 
     dbSet('user:' + userId + ':lastName', 'Vance', this.MULTI()); 

    },function() { 
     okWeAreDone() 
    } 
); 
+9

快速浏览一下flow-js,step和async,看起来他们只处理函数执行的顺序。在我的情况下,每个缩进都可以访问内联闭包变量。因此,例如,函数的工作方式如下所示:获取HTTP req/res,从数据库获取用于cookie的用户标识,为稍后的用户标识获取电子邮件,为以后的电子邮件获取更多数据,...,为以后的Y获取X ...如果我没有弄错,这些框架只能确保异步函数按照正确的顺序执行,但是在每个函数体中都没有办法获得由闭包自动提供的变量(?)Thanks :) – 2010-12-05 20:07:05

+8

对这些图书馆进行排名,我在Github上查看了每个“星级”的数量。异步拥有最多约3000个,步骤接下来约有1000个,其他则明显更少。当然,他们并不都做同样的事情:-) – kgilpin 2012-08-09 20:19:16

+3

@KayPale我倾向于使用async.waterfall,并且有时会为每个阶段/步骤分配我自己的函数,这些函数将传递下一步需要的内容或定义async.METHOD调用之前的变量,以便下线可用。也将使用METHODNAME.bind(...)进行我的异步。*调用,这也可以很好地工作。 – Tracker1 2013-01-04 23:26:38

0

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

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

观察该代码似乎比以前更大。但它不像以前一样嵌套。 使其显得更自然,链被称为imediately:

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

并说,这回,我们调用函数中:我做一个相当原始而有效的方法

this.return(response) 
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]); 
} 
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(); 
    }); 
}); 
0

使用纤维https://github.com/laverdet/node-fibers它使异步代码看起来像同步(不阻塞)

我个人使用的小包装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" 
17

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

在眼睛上更容易。

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

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

npm install node-promise ||混帐克隆https://github.com/kriszyp/node-promise

使用这个你可以链异步方法为:

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

每个返回值是可作为下一个参数。

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 and getMoreData 应该是随着最后一个参数回调函数(ERR,数据)

标准异步功能,如:

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); 
     }); 
} 
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也可以用作局部变量的持有者。(在这里,我已经得到了参数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); 
} 

我一直在想,也许这将是可以使用实例变量创建一个对象,并使用这些实例变量来替代局部变量。但是现在我发现使用嵌套函数和局部变量的上述方法更简单,更易于理解。它需要一些时间来忘掉面向对象,它看起来:-)

所以这里是我的以前版本的对象和实例变量。

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编码 - 我只是花了几个小时就可以了。但也许有点抛光这种技术可以帮助?

13

我很喜欢async.js这个用途。

的问题是由瀑布命令解决:

瀑布(任务,[回调])

运行的功能的阵列中的系列,每个传递它们的结果到阵列中的下一个。但是,如果任何函数向回调传递错误,则不执行下一个函数,并立即调用主回调并返回错误。

参数

任务 - 的功能来运行的数组,每个函数传递一个回调(ERR,RESULT1,结果2,...),则必须在完成呼叫。第一个参数是一个错误(可以为null),并且任何其他参数都将作为参数传递给下一个任务。 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呼叫。

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

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得到普遍的处理。这加快了整个编码过程。

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

显然,还有许多其他的解决方案,但前处理具有源地图的支持很容易调试过少或没有运行时开销和感谢的优势。

0

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

你知道考虑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), 
    ]);