这是我想要简化的一个例子:
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
?
答案 0 :(得分:10)
您问题的核心是:
我知道我可以用名称函数替换我的匿名函数,但是我需要传递变量。如果函数在别处定义,我的内部函数如何访问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});
}}
这使得嵌套回调变得扁平化。作为额外的好处,您可以正确命名函数应该执行的操作。我认为这是一种良好的做法。
它还有一个额外的优势,它比使用匿名回调(使用嵌套回调或使用promises)要快得多,但是如果你将命名函数传递给promise.then()而不是匿名函数那么你就可以了获得相同的加速效益)。之前的一个SO问题(我的google-fu今天让我失败了)发现命名函数的速度是node.js中匿名函数的两倍以上(如果我没记错的话,速度提高了5倍以上)。
答案 1 :(得分: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);
});
};
答案 2 :(得分:1)
命名函数将在匿名函数所在的范围内执行,并且可以访问您当前使用的所有变量。这种方法可以使你的代码更少嵌套,更易读(这很好),但技术上仍然是“回调地狱”。避免这种情况的最佳方法是将异步库(假设它们尚未提供promises)与Q之类的promise库包装起来。 IMO,承诺提供了更清晰的执行路径图。
通过使用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));