如何宣传这个Mongoose代码?

时间:2016-07-28 23:57:34

标签: javascript node.js mongodb mongoose promise

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

3 个答案:

答案 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()返回承诺,您就可以:

  • 实现所需的展平并使代码更具可读性。
  • 避免在成功"成功之间处理错误。代码。
  • 处理终端.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();
        });
    });
}

请注意需要返回来控制流程。

但你可以做得更好。如上所述,请求的东西可能成功然后目标东西失败,导致数据库差异。所以你真正想要的是一个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
});

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