Javascript 避免nodeJs中的回调地狱/将变量传递给内部函数
下面是一个我想简化的例子:Javascript 避免nodeJs中的回调地狱/将变量传递给内部函数,javascript,node.js,express,callback,mongoose,Javascript,Node.js,Express,Callback,Mongoose,下面是一个我想简化的例子: 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
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的队列。这两样我都没有
我知道我可以用names函数替换匿名函数,但是我需要传递变量。例如,如果函数在别处定义,我的内部函数将如何访问
res
?使用承诺。使用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);
});
};
命名函数将在匿名函数所在的相同范围内执行,并且可以访问当前使用的所有变量。这种方法可以减少代码的嵌套,提高代码的可读性(这很好),但在技术上仍然是“回调地狱”。避免这种情况的最好方法是使用类似的承诺库包装异步库(假设它们还没有提供承诺)。在国际海事组织,承诺提供了一个更清晰的执行路径图 通过使用
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));
你问题的核心是: 我知道我可以用names函数替换匿名函数,但是我需要传递变量。例如,如果函数在别处定义,我的内部函数将如何访问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\u maker
是一个生成函数的函数(我们将该函数的用途称为“y”)。在我自己的代码中,我使用命名约定。\u 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倍多)。我对命名函数的担心是,即使它们可以访问相同的变量,读取这些函数时不清楚这些变量来自何处。哦,如果我需要在
然后中进行另一个Mongoose查询,那么我仍然“卡在”额外的函数嵌套中。(编辑:删除了关于函数间级联变量的问题)然后您必须在generateUrl范围内创建一个变量。查看var text更新的答案虽然这个问题得到了很好的答案,但我选择这个答案是因为我认为它可以得到最干净的代码。变量从何而来,这一点很清楚,而且非常平坦。谢谢这看起来是避免回调地狱的一个很好的解决方案。是否有任何反对使用此方法的理由,或存在任何缺点?