【问题标题】:How to promisify this Mongoose code?如何承诺这个猫鼬代码?
【发布时间】:2016-12-03 13:30:14
【问题描述】:

我正在尝试使用 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})
            })
        })
    }
})
}

【问题讨论】:

  • 你确定第一个exec 回调不需要err 参数吗?
  • .then(err => {if (err) throw err}) 看起来你不应该需要它来承诺

标签: javascript node.js mongodb mongoose promise


【解决方案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】:

    在英语中,这样做的方法是使用由exec() 返回的承诺有then 块返回承诺,取消缩进,然后将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();
        });
    }
    

    【讨论】:

    • 确实弄错了逻辑,doStuff 后面的所有内容都应该在 if 正文中
    • 谢谢@Bergi,我想我只是错过了大括号。让我知道你说的不是这个。
    • 不,我不是说大括号,我是指以requester.save() 开头的整个动作,需要进入if 条件
    • @Bergi - 如果对象没有被弄脏,则可能是无操作,如果不是无操作,则可能是无害的。无论如何添加了一个替代方案。
    • 但您仍在将请求者添加到目标的好友请求中,即使他已经在其中!
    【解决方案3】:

    一旦你意识到.exec() 返回了一个承诺,你就可以:

    • 实现所需的扁平化并使代码更具可读性。
    • 避免处理“成功”代码中的错误。
    • 处理终端 .then() 或 .catch() 中的错误。

    作为奖励,您还可以(更容易)为每个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();
            });
        });
    }
    

    注意需要返回控制流。

    但你可以做得更好。如上所述,请求的东西可能会成功,然后目标的东西会失败,从而导致数据库差异。所以你真正想要的是一个数据库事务来保证两者都发生或都不发生。 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
    });
    

    即使您不在实时环境中使用错误消息,它们在测试/调试时也可能非常有用。请检查它们 - 我可能弄错了。

    【讨论】:

      猜你喜欢
      • 2015-05-12
      • 1970-01-01
      • 1970-01-01
      • 2012-12-14
      • 2017-06-15
      • 2019-06-04
      • 2017-08-18
      • 2015-10-16
      • 1970-01-01
      相关资源
      最近更新 更多