繁体   English   中英

如何使这个猫鼬代码更加合理?

[英]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()返回一个诺言,就可以:

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

另外,您还可以(更容易地)为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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM