在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();
});
});
}
你可以看到这样的代码更难写/读,而且更容易出错。 我的问题:
答案 0 :(得分:5)
回答你的问题:
有async
等库可以在处理异步任务时为常见场景提供各种解决方案。对于“回调地狱”问题,有很多方法可以避免这种情况,包括(但不限于)命名功能并将其拉出来,模块化代码和使用承诺。
或多或少你现在拥有的是一个相当常见的模式:拥有一个带有要调用的函数数组的计数器和函数索引变量。同样,async
可以在这里提供帮助,因为它可以减少您可能会经常重复的这种样板。 async
目前没有真正允许跳过单个任务的方法,但是如果你正在编写样板文件,你可以自己轻松地做到这一点(例如,只需将函数索引变量增加2)。
根据我自己的经验,如果你正确设计你的javascript代码并考虑异步并使用很多工具如async
,你会发现用节点开发更容易。在节点中编写异步与同步通常总是会更复杂(尽管与回调/承诺相比,生成器,光纤等更少)。
我个人认为根据单一方面决定一种语言是不值得的。您必须考虑的不仅仅是语言的设计,例如社区的大小,第三方库的可用性,性能,技术支持选项,代码调试的简易性等。
答案 1 :(得分:4)
只需更紧凑地编写代码:
// 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);
});
});
}
与你发布的python的9个sloc相比,它在13个源代码行中显示。但是,与您发布的python不同,此代码并行运行所有作业。
为了在系列中做同样的事情,我通常做的一个技巧是内联定义的next()
函数调用自身并从数组中弹出一个作业:
// 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这样的语言中,这并不是那么容易,因为一切都是同步的,你必须做大量的工作,比如线程或事件库,以使事情恢复到异步。尝试在python中实现你所拥有的并行版本!它肯定会比节点版本更长。
对于promise / async路由:对于这些相对简单的任务,使用普通函数实际上并不是那么难或坏。在将来(或在带有--harmony的节点0.11 +中),您可以使用生成器和类似co的库,但该功能尚未广泛部署。
答案 2 :(得分:3)
这里的每个人似乎都在建议async,这是一个很棒的图书馆。但是要给出另一个建议,你应该看一下Promises ,这是一种新的内置语言(目前有几种非常好的polyfill)。它允许您以看起来更结构化的方式编写异步代码。例如,看看这段代码:
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 ...
}
使用promises,会写成这样的东西:
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中,所以(最终)你甚至不需要加载到库中,它只是可用。
答案 3 :(得分:2)
答案 4 :(得分:1)
我使用async的次数越多,我就越喜欢它,我更喜欢节点。让我举一个关于服务器初始化的简单示例。
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
});
实际上,可以遵循这种相同的方法,并且可以将不同的参数传递给与各种作业相对应的不同的函数;例如,见Passing arguments to async.parallel in node.js。
对你说实话,我更喜欢节点方式,它也是非阻塞的。我认为node强迫某人拥有更好的设计,有时您会花时间创建更多定义并将数组中的函数和对象分组,以便您可以编写更好的代码。我认为最终你想要利用async
的一些变体并相应地混合和合并内容。在我看来,当你考虑到节点是异步时,花一些额外的时间和思考代码也是值得的。
除此之外,我认为这是一种习惯。为节点编写代码的人越多,就越能改进并编写更好的异步代码。在节点上有什么好处是它真的迫使某人编写更健壮的代码,因为人们开始更多地关注来自所有函数的所有错误代码。例如,人们检查的频率,比如malloc
或new
是否成功,并且在命令发出后没有NULL
指针的错误处理程序?编写异步代码虽然强制一个人尊重事件和事件所具有的错误代码。我想一个明显的原因是,一个人尊重一个人编写的代码,最后我们必须编写返回错误的代码,以便调用者知道发生了什么。
我真的认为你需要给它更多时间并开始使用异步更多。这就是全部。
答案 5 :(得分:0)
"如果您尝试使用纯node.js对bussiness db login进行编码,则直接回调地狱"
我最近创建了一个名为WaitFor的简单抽象,以同步模式调用异步函数(基于Fibers):https://github.com/luciotato/waitfor
检查数据库示例:
pure node.js(温和的回调地狱):
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);
}
注意:上面的代码,虽然它看起来会捕获异常,它不会。 用回调地狱捕捉异常会增加很多痛苦,而且我不确定你是否会有这样的反应。参数 回应用户。如果有人喜欢修复这个例子......请成为我的客人。
使用 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);
}
注意:将按预期捕获异常。 将使用this = db
调用db方法(db.select,db.execute)为了使用wait.for,您必须标准化您的CALLBACKS才能运行(错误,数据)
如果您标准化您的回复,您的代码可能如下所示:
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很近,没有回调地狱