在nodeJs中避免callback地狱/将variables传递给内部函数

这里有一个我想要简化的例子:

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

我已经看到其他的答案,他们告诉你使用命名函数,但不要说如何处理你想要传入的variables或使用jQuery的队列。 我也没有奢侈。

我明白,我可以用名称函数replace我的匿名函数,但是我需要传递variables。 如果函数是在其他地方定义的,我的内部函数如何访问res

你的问题的核心是:

我明白,我可以用名称函数replace我的匿名函数,但是我需要传递variables。 如果函数是在其他地方定义的,我的内部函数如何访问res?

答案是使用一个function工厂。

一般来说,这个:

 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”)。 在我自己的代码中,我使用命名约定.._makergenerate_..来表示我正在调用一个函数工厂。 但是那只是我自己,这个公约并不是标准的,也不是被广泛采用的。

所以对于你的代码,你可以重构它:

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

这使嵌套的callback变平了。 作为一个额外的好处,你得到正确的名称是什么function应该做的。 我认为这是很好的做法。

它还具有额外的优势,它比使用匿名callback(嵌套callback或承诺,但如果您将名为函数传递给promise.then()而不是匿名函数)快得多,那么您将获得相同的加速福利)。 之前的一个SO问题(我的google-fu今天失败了)发现,命名函数的速度是node.js中匿名函数的两倍以上(如果我没记错的话,速度会提高5倍以上)。

使用承诺。 使用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); }); }; 

命名函数将在与匿名函数相同的范围内执行,并且可以访问当前使用的所有variables。 这种方法会使你的代码更less嵌套,更可读(这是好的),但在技术上仍然在“callback地狱”。 避免这种情况的最好方法是将你的asynchronous库(假设他们还没有提供承诺)包装到一个类似Q的承诺库中。 海事组织,承诺提供了一个更清晰的执行path图。

您可以通过使用bind参数绑定到命名函数来避免不知道variables来自哪里的困境,例如:

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