是否有可能编写asynchronousNode.js代码“更清洁”?

在Node.js中进行编码的时候,遇到很多情况,很难实现一些与数据库查询(I / O)混合的详细逻辑。

考虑用python编写的例子。 我们需要迭代一个值的数组,对于我们查询数据库的每个值,然后根据结果计算平均值。

def foo: a = [1, 2, 3, 4, 5] result = 0 for i in a: record = find_from_db(i) # I/O operation if not record: raise Error('No record exist for %d' % i) result += record.value return result / len(a) 

Node.js中的相同任务

 function foo(callback) { var a = [1, 2, 3, 4, 5]; var result = 0; var itemProcessed = 0; var error; function final() { if (itemProcessed == a.length) { if (error) { callback(error); } else { callback(null, result / a.length); } } } a.forEach(function(i) { // I/O operation findFromDb(function(err, record) { itemProcessed++; if (err) { error = err; } else if (!record) { error = 'No record exist for ' + i; } else { result += record.value; } final(); }); }); } 

你可以看到这样的代码很难写/读,而且更容易出错。 我的问题:

  1. 有没有办法让上面的Node.js代码更清洁?
  2. 想象一下更复杂的逻辑。 例如,当我们从db获得一条logging时,我们可能需要根据一些条件做另一个db查询。 在Node.js,成为一场噩梦。 处理这些任务的常见模式是什么?
  3. 根据您的经验,在使用Node.js编码时,性能增益是否会损失生产力?
  4. 还有其他asynchronousI / O框架/语言更容易使用吗?

回答你的问题:

  1. 有些库是async ,它们在处理asynchronous任务时为常见场景提供了各种解决scheme。 对于“callback地狱”的担忧,还有很多方法可以避免,包括(但不限于)命名你的函数并把它们拉出来,模块化你的代码和使用promise。

  2. 或多或less你现在有一个相当普遍的模式:有一个函数数组调用计数器和函数索引variables。 同样, async可以帮助这里,因为它减less了你可能会发现自己经常重复的这种样板。 async目前没有真正允许跳过单个任务的方法,但是如果你正在编写样板(你只需要将函数索引variables2递增),你可以自己轻松地做到这一点。

  3. 从我自己的经验来看,如果你正确地devise了你的javascript代码,并且使用了很多像async这样的工具,你会发现用node来开发更容易。 在节点中写入asynchronous和同步通常总是比较复杂(尽pipe与callback/承诺相比,发生器,光纤等要less得多)。

  4. 我个人认为,基于单一方面来决定一种语言是不值得的。 你必须考虑的不仅仅是语言的devise,例如社区的规模,第三方库的可用性,性能,技术支持选项,代码debugging的简易性等。

只需更简洁地编写代码:

 // parallel version function foo (cb) { var items = [ 1, 2, 3, 4, 5 ]; var pending = items.length; var result = 0; items.forEach(function (item) { findFromDb(item, function (err, record) { if (err) return cb(err); if (!record) return cb(new Error('No record for: ' + item)) result += record.value / items.length; if (-- pending === 0) cb(null, result); }); }); } 

这与13个源代码行相比,你发布的Python的9 sloc。 但是,与您发布的python不同,此代码并行运行所有作业。

要做同样的事情,我通常会做的一个诀窍就是一个内联的next()函数,它调用自身并从数组中popup一个工作:

 // sequential version function foo (cb) { var items = [ 1, 2, 3, 4, 5 ]; var len = items.length; var result = 0; (function next () { if (items.length === 0) return cb(null, result); var item = items.shift(); findFromDb(item, function (err, record) { if (err) return cb(err); if (!record) return cb(new Error('No record for: ' + item)) result += record.value / len; next(); }); })(); } 

这一次,15行。 好的是,你可以很容易地控制行动是应该发生在平行或顺序或中间的某个地方。 在像python这样的语言中,一切都是同步的,而且你必须做很多解决方法,比如线程或者连接库来把事情恢复到asynchronous。 尝试实现你在python中的并行版本! 这肯定会比节点版本更长。

至于承诺/asynchronous路由:对于这些相对简单的任务来说,使用普通的函数并不是那么困难或不好。 在将来(或在和谐的节点0.11 +),你可以使用生成器和像co这样的库,但该function尚未广泛部署。

这里的每个人似乎都build议asynchronous ,这是一个伟大的图书馆。 但要提出另一个build议,你应该看看承诺 ,这是一种新的内置被引入语言(目前有几个非常好的polyfills)。 它允许您以看起来更加结构化的方式编写asynchronous代码。 例如,看看这个代码:

 var items = [ 1, 2, 3, 4 ]; var processItem = function(item, callback) { // do something async ... }; var values = [ ]; items.forEach(function(item) { processItem(item, function(err, value) { if (err) { // something went wrong } values.push(value); // all of the items have been processed, move on if (values.length === items.length) { doSomethingWithValues(values, function(err) { if (err) { // something went wrong } // and we're done }); } }); }); function doSomethingWithValues(values, callback) { // do something async ... } 

使用承诺,它会写这样的东西:

 var items = [ 1, 2, 3, 4 ]; var processItem = function(item) { return new Promise(function(resolve, reject) { // do something async ... }); }; var doSomethingWithValues = function(values) { return new Promise(function(resolve, reject) { // do something async ... }); }; // promise.all returns a new promise that will resolve when all of the promises passed to it have resolved Promise.all(items.map(processItem)) .then(doSomethingWithValues) .then(function() { // and we're done }) .catch(function(err) { // something went wrong }); 

第二个版本更清洁简单,甚至勉强承诺真正的力量表面。 而且,正如我所说,Promise在es6中是一种内置的新语言,所以(最终)您甚至不需要加载到库中,它就可以使用。

  1. 不要使用匿名(un-named)函数,使代码变得丑陋,而且会使debugging变得更加困难,因此请始终命名函数并在函数范围之外定义它们,而不是内联函数。
  2. 这是Node.js(它被称为callback hell或厄运的金字塔)的一个真正的问题,你可以通过使用承诺或使用async.js来解决这个问题,这些function有很多function来处理不同的情况(瀑布,并行,系列,汽车,…)
  3. 那么性能增益绝对是一件好事,并没有那么多的损失(当你开始掌握它)和Node.js社区是伟大的。
  4. 检查async.js , q 。

越多,我与asynchronous工作越多,我喜欢它,我更喜欢节点。 让我给你一个简单的例子,我有一个服务器初始化。

 async.parallel ({ "job1": loadFromCollection1, "job2": loadFromCollection2, }, function (initError, results) { if (initError) { console.log ("[INIT] Server initialization error occurred: " + JSON.stringify(initError, null, 3)); return callback (initError); } // Do more stuff with the results }); 

事实上,可以采用同样的方法,可以将不同的function传递给与各种工作相对应的不同function; 例如参见将parameter passing给node.js中的async.parallel 。

说实话,我更喜欢节点方式,也是非阻塞的。 我认为节点迫使某人有一个更好的devise,有时你花时间在数组中创build更多的定义和分组函数和对象,以便您可以编写更好的代码。 我认为的原因是,最终你想利用async一些变种,并相应地混合和合并的东西。 在我看来,如果考虑到节点是asynchronous的 ,花费一些额外的时间并考虑更多代码是非常值得的。

除此之外,我认为这是一种习惯。 越多的人为节点编写代码,越能提高编写更好的asynchronous代码。 节点上的好处在于,它强迫别人编写更健壮的代码,因为开始尊重所有函数的所有错误代码。 例如,人们多久检查一次,比如说mallocnew是否成功,并且在命令发出后没有一个NULL指针的error handling程序? 编写asynchronous代码虽然强迫人们尊重事件和事件所具有的错误代码。 我想一个显而易见的原因是人们尊重一个人编写的代码,最后我们必须编写返回错误的代码,以便调用者知道发生了什么。

我真的觉得你需要更多的时间,并开始使用asynchronous更多。 就这样。

“如果你尝试使用纯粹的node.js编写商业数据库login,你直接callback地狱”

我最近创build了一个名为WaitFor的简单抽象,以同步模式调用asynchronous函数(基于Fibers): https : //github.com/luciotato/waitfor

检查数据库的例子:

数据库示例(伪代码)

纯粹的node.js(温和的callback地狱):

 var db = require("some-db-abstraction"); function handleWithdrawal(req,res){ try { var amount=req.param("amount"); db.select("* from sessions where session_id=?",req.param("session_id"),function(err,sessiondata) { if (err) throw err; db.select("* from accounts where user_id=?",sessiondata.user_ID),function(err,accountdata) { if (err) throw err; if (accountdata.balance < amount) throw new Error('insufficient funds'); db.execute("withdrawal(?,?),accountdata.ID,req.param("amount"), function(err,data) { if (err) throw err; res.write("withdrawal OK, amount: "+ req.param("amount")); db.select("balance from accounts where account_id=?", accountdata.ID,function(err,balance) { if (err) throw err; res.end("your current balance is " + balance.amount); }); }); }); }); } catch(err) { res.end("Withdrawal error: " + err.message); } 

注意:上面的代码,虽然看起来会捕获exception, 但不会 。 捕捉exception与callback地狱增加了很多的痛苦,我不知道你是否有'RES'参数来响应用户。 如果有人想修正这个例子…做我的客人。

使用wait.for

 var db = require("some-db-abstraction"), wait=require('wait.for'); function handleWithdrawal(req,res){ try { var amount=req.param("amount"); sessiondata = wait.forMethod(db,"select","* from session where session_id=?",req.param("session_id")); accountdata= wait.forMethod(db,"select","* from accounts where user_id=?",sessiondata.user_ID); if (accountdata.balance < amount) throw new Error('insufficient funds'); wait.forMethod(db,"execute","withdrawal(?,?)",accountdata.ID,req.param("amount")); res.write("withdrawal OK, amount: "+ req.param("amount")); balance=wait.forMethod(db,"select","balance from accounts where account_id=?", accountdata.ID); res.end("your current balance is " + balance.amount); } catch(err) { res.end("Withdrawal error: " + err.message); } 

注意:例外情况将如预期那样捕捉。 db方法(db.select,db.execute)将用this = db来调用

你的代码

为了使用wait.for,你必须标准化你的callback函数(err,data)

如果您标准化您的callback ,您的代码可能如下所示:

 var wait = require('wait.for'); //run in a Fiber function process() { var a = [1, 2, 3, 4, 5]; var result = 0; a.forEach(function(i) { // I/O operation var record = wait.for(findFromDb,i); //call & wait for async function findFromDb(i,callback) if (!record) throw new Error('No record exist for ' + i); result += record.value; }); return result/a.length; } function inAFiber(){ console.log('result is: ',process()); } // run the loop in a Fiber (keep node spinning) wait.launchFiber(inAFiber); 

看到? 更接近python,没有callback地狱