避免 nodeJs 中的回调地狱/将变量传递给内部函数
Posted
技术标签:
【中文标题】避免 nodeJs 中的回调地狱/将变量传递给内部函数【英文标题】:Avoiding callback hell in nodeJs / Passing variables to inner functions 【发布时间】:2014-11-14 11:12:39 【问题描述】:这是一个我想简化的例子:
exports.generateUrl = function (req, res)
var id = req.query.someParameter;
var query = MyMongooseModel.findOne('id': id);
query.exec(function (err, mongooseModel)
if(err)
//deal with it
if (!mongooseModel)
generateUrl(Id,
function (err, text, url)
if (err)
res.status(HttpStatus.INTERNAL_SERVER_ERROR).send(err);
return;
var newMongooseModel = new AnotherMongooseModel();
newMongooseModel.id = id;
newMongooseModel.save(function (err)
if (err)
res.status(HttpStatus.INTERNAL_SERVER_ERROR).send(err);
else
res.send(url: url, text: text);
);
);
else
//deal with already exists
);
;
我看过其他 SO 回答,他们告诉您使用命名函数,但没有说明如何处理您想要传入的变量或使用 jQuery 的队列。这两个我都没有。
我知道我可以用名称函数替换我的匿名函数,但是我需要传递变量。例如,如果函数在别处定义,我的内部函数将如何访问 res
?
【问题讨论】:
【参考方案1】:您的问题的核心是:
我知道我可以用名称函数替换我的匿名函数,但是我需要传递变量。例如,如果函数在其他地方定义,我的内部函数将如何访问 res?
答案是使用函数工厂。
一般来说是这样的:
function x (a)
do_something(function()
process(a);
);
可以转换成这样:
function x (a)
do_something(y_maker(a)); // notice we're calling y_maker,
// not passing it in as callback
function y_maker (b)
return function ()
process(b);
;
在上面的代码中,y_maker
是一个生成函数的函数(我们称该函数的用途为“y”)。在我自己的代码中,我使用命名约定.._maker
或generate_..
来表示我正在调用函数工厂。但这只是我个人的情况,而且该约定绝不是标准的,也不是在野外被广泛采用的。
因此,对于您的代码,您可以将其重构为:
exports.generateUrl = function (req, res)
var id = req.query.someParameter;
var query = MyMongooseModel.findOne('id': id);
query.exec(make_queryHandler(req,res));
;
function make_queryHandler (req, res)
return function (err, mongooseModel)
if(err)
//deal with it
else if (!mongooseModel)
generateUrl(Id,make_urlGeneratorHandler(req,res));
else
//deal with already exists
function make_urlGeneratorHandler (req, res)
return function (err, text, url)
if (err)
res.status(HttpStatus.INTERNAL_SERVER_ERROR).send(err);
return;
var newMongooseModel = new AnotherMongooseModel();
newMongooseModel.id = id;
newMongooseModel.save(make_modelSaveHandler(req,res));
function make_modelSaveHandler (req, res)
return function (err)
if (err) res.status(HttpStatus.INTERNAL_SERVER_ERROR).send(err);
else res.send(url: url, text: text);
这会使嵌套的回调变平。作为一个额外的好处,您可以正确命名该函数应该做什么。我认为这是一种很好的做法。
它还有一个额外的优势,即它比使用匿名回调(使用嵌套回调或使用承诺)要快得多,但如果您将命名函数传递给 promise.then() 而不是匿名函数,那么您将获得相同的加速好处)。之前的一个 SO 问题(我的 google-fu 今天让我失望了)发现命名函数的速度是 node.js 中匿名函数的两倍以上(如果我没记错的话,它快了 5 倍以上)。
【讨论】:
虽然这个问题得到了很好的答案,但我选择了这个答案,因为我认为它会导致最干净的代码。变量的来源很明显,而且非常平坦。谢谢! 这看起来是避免回调地狱的好方法。有没有反对使用这个的论据,或者有什么缺点?【参考方案2】:命名函数将在与匿名函数相同的范围内执行,并且可以访问您当前使用的所有变量。这种方法将使您的代码嵌套更少且更具可读性(这很好),但在技术上仍处于“回调地狱”中。避免这种情况的最好方法是用像Q 这样的promise 库来包装你的异步库(假设它们还没有提供promise)。 IMO,promise 提供了更清晰的执行路径图。
您可以通过使用bind
将参数绑定到您的命名函数来避免不知道变量来自何处的困境,例如:
function handleRequest(res, err, text, url)
if (err)
res.status(HttpStatus.INTERNAL_SERVER_ERROR).send(err);
return;
var newMongooseModel = new AnotherMongooseModel();
newMongooseModel.id = id;
newMongooseModel.save(function (err)
if (err)
res.status(HttpStatus.INTERNAL_SERVER_ERROR).send(err);
else
res.send(url: url, text: text);
);
...
generateUrl(Id, handleRequest.bind(null, res));
【讨论】:
我对命名函数的担忧是,即使它们可以访问相同的变量,但在读取这些变量来自何处的函数时并不清楚。【参考方案3】:使用承诺。使用 Q 和 mongoose-q 它会给出:类似的东西:
exports.generateUrl = function (req, res)
var id = req.query.someParameter;
var text = "";
var query = MyMongooseModel.findOne('id': id);
query.execQ().then(function (mongooseModel)
if (!mongooseModel)
return generateUrl(Id)
).then(function (text)
var newMongooseModel = new AnotherMongooseModel();
newMongooseModel.id = id;
text = text;
newMongooseModel.saveQ()
).then(function (url)
res.send(url: url, text: text);
).fail(function(err)
res.status(HttpStatus.INTERNAL_SERVER_ERROR).send(err);
);
;
【讨论】:
哦,如果我需要在then
中进行另一个 Mongoose 查询,那么我仍然“卡住”了一个额外的函数嵌套。 (编辑:删除了关于函数之间级联变量的问题)
然后你必须在 generateUrl 范围内创建一个 var。使用 var text 查看更新的答案以上是关于避免 nodeJs 中的回调地狱/将变量传递给内部函数的主要内容,如果未能解决你的问题,请参考以下文章
Nodejs 异步编程 - 为啥需要“异步”模块?啥是“回调地狱”/“末日金字塔”?
JavaScript 使用Async 和 Promise 完美解决回调地狱