[英]How to promisify this Mongoose code?
我正在尝试使用Mongoose的内置Promise支持为用户发送好友请求到另一个用户编写一些干净的Javascript代码。 但是,当我尝试确保正确的错误处理和顺序时,我仍然遇到了(比正常情况小一点)的厄运金字塔。
在这里,我首先确保好友请求有效,然后将目标的ID保存到请求者的已发送请求中,然后,如果保存成功,则将请求者的ID保存到目标的朋友请求中。
我是否需要使用像q这样的第三方库才能尽可能简洁地执行此操作? 如何构造这种结构,以便最终可以使用传统的单一错误处理程序?
function _addFriend (requesterId, targetId) {
// (integer, integer)
User.findById(requesterId)
.exec((requester) => {
if (!(targetId in requester.friends
|| targetId in requester.sentfriendRequests
|| targetId in requester.friendRequests)) {
requester.sentfriendRequests = requester.sentfriendRequests.concat([targetId])
requester.save()
.then((err) => {
if (err) throw err;
User.findById(targetId)
.exec((err, target) => {
if (err) throw err;
target.friendRequests = target.friendRequests.concat([requesterId])
target.save().then(err => {if (err) throw err})
})
})
}
})
}
在英语中,要做到这一点的方法是使用返回的承诺exec()
有then
块返回的承诺,取消缩进,再加入then
对这些。 用代码说起来容易多了...
编辑 (再次)感谢@Bergi,让我阅读并理解了应用程序的逻辑。 @Bergi是正确的,必须完成一些嵌套才能完成工作,但真正的意义不在于减少嵌套,而在于提高清晰度。
更好的清晰度可以来自于逻辑部分的分解,其中包括承诺的回报。
这几个函数隐藏了逻辑所需的promise嵌套。 这没有指定(因为OP并未指示应用程序应如何处理)当由于现有请求而拒绝这样做时,addFriend应该返回什么……
function _addFriend (requesterId, targetId) {
// note - pass no params to exec(), use it's returned promise
return User.findById(requesterId).exec().then((requester) => {
return canAddFriend(requester, targetId) ? addFriend(requester, targetId) : null;
});
}
function canAddFriend(requester, targetId) {
return requester && targetId &&
!(targetId in requester.friends
|| targetId in requester.sentfriendRequests
|| targetId in requester.friendRequests);
}
function addFriend(requester, targetId) {
requester.sentfriendRequests = requester.sentfriendRequests.concat([targetId]);
return requester.save().then(() => {
return User.findById(targetId).exec();
}).then((target) => {
target.friendRequests = target.friendRequests.concat([requesterId]);
return target.save();
});
}
您将需要一些嵌套来在promise代码中执行条件操作,但不如基于回调的代码那么多。
您似乎弄乱了if (err) throw err;
东西,您永远都不需要承诺。 只需始终使用.then(result => {…})
,不要再将回调传递给exec
。
如果你总是正确return
从你的异步功能的承诺(包括then
的链接回调),你可以在结尾处添加单个错误处理。
function _addFriend (requesterId, targetId) {
// (integer, integer)
return User.findById(requesterId).exec().then(requester => {
if (targetId in requester.friends
|| targetId in requester.sentfriendRequests
|| targetId in requester.friendRequests) {
return;
}
requester.sentfriendRequests = requester.sentfriendRequests.concat([targetId])
return requester.save().then(() => {
return User.findById(targetId).exec()
}).then(target => {
target.friendRequests = target.friendRequests.concat([requesterId])
return target.save()
});
});
}
_addFriend(…).catch(err => {
…
})
一旦意识到.exec()
返回一个诺言,就可以:
另外,您还可以(更容易地)为x in y
条件下的每个x in y
抛出有意义的错误。
直接地,您可以编写:
function _addFriend(requesterId, targetId) {
return User.findById(requesterId).exec().then(requester => {
if (targetId in requester.friends) {
throw new Error('target is already a friend');
}
if (targetId in requester.sentfriendRequests) {
throw new Error('friend request already sent to target');
}
if (targetId in requester.friendRequests) {
throw new Error('target already sent a friend request to requester');
}
requester.sentfriendRequests = requester.sentfriendRequests.concat([targetId]); // or just .push()?
return requester.save();
}).then(() => {
return User.findById(targetId).exec().then(target => {
target.friendRequests = target.friendRequests.concat([requesterId]); // or just .push()?
return target.save();
});
});
}
注意需要返回控制流。
但是您可以做得更好。 如上所述,请求的填充可能会成功,然后目标填充会失败,从而导致数据库差异。 因此,您真正想要的是一个数据库事务,以确保两者均不会发生。 猫鼬无疑提供了交易,但是您可以在客户端做一些事情来给您一些类似交易的好处。
function _addFriend(requesterId, targetId) {
return Promise.all([User.findById(requesterId).exec(), User.findById(targetId).exec()]).then(([requester, target]) => { // note destructuring
if (targetId in requester.friends) {
throw new Error('target is already a friend');
}
if (targetId in requester.sentfriendRequests) {
throw new Error('friend request already sent to target');
}
if (targetId in requester.friendRequests) {
throw new Error('target already sent a friend request to requester');
}
requester.sentfriendRequests = requester.sentfriendRequests.concat([targetId]);
target.friendRequests = target.friendRequests.concat([requesterId]);
return requester.save().then(() => {
return target.save();
});
});
}
在这里,您仍然可以得到(不太可能)第一次保存成功而第二次保存失败的情况,但是至少您可以确保除非请求者和目标都存在,否则绝对不会发生任何事情。
在这两种情况下,请致电:
_addFriend(requesterId, targetId).then(function() {
// do whatever on success
}, function(error) {
// do whatever on error
});
即使您不在实时环境中使用错误消息,在测试/调试时它们也可能非常有用。 请检查它们-我可能弄错了。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.