我正在尝试使用Mongoose的内置承诺支持为用户向另一个发送朋友请求编写一些干净的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})
})
})
}
})
}
答案 0 :(得分:1)
在英语中,这样做的方法是使用exec()
返回的promises then
块返回promises,un-indent,然后将then
添加到那些。在代码中更容易说出来......
编辑再次感谢@Bergi让我阅读并理解应用程序逻辑。 @Bergi是正确的,必须有一点点筑巢来完成工作,但真正的重点不是减少嵌套,而是提高清晰度。
更好的清晰度可以来自于逻辑部分,包括一些在承诺中回归的部分。
这几个函数隐藏了逻辑所需的承诺嵌套。这没有指定(因为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();
});
}
答案 1 :(得分:1)
你将需要一些嵌套来在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 => {
…
})
答案 2 :(得分:0)
一旦您意识到.exec()
返回承诺,您就可以:
作为奖励,您还可以(更容易)为每个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();
});
});
}
请注意需要返回来控制流程。
但你可以做得更好。如上所述,请求的东西可能成功然后目标东西失败,导致数据库差异。所以你真正想要的是一个db事务来保证两者都发生或两者都不发生。 Mongoose无疑提供交易,但是你可以做一些客户端的事情,给你一些交易,比如部分利益。
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
});
即使您没有在实时环境中使用错误消息,它们在测试/调试时也非常有用。请检查一下 - 我可能弄错了。