ExpressJS / NodeJS / Promises:从承诺链中提前返回

时间:2016-05-08 04:47:52

标签: javascript node.js express promise es6-promise

当我在服务器上收到发布请求以创建新游戏时,我会执行几个查询。首先,我搜索用户是否已经在游戏中,如果是,则返回游戏。否则,我搜索一个开放的游戏,其中某人正在等待对手并返回该游戏,如果是这样的话。最后,如果没有找到上述状态的游戏,我会创建一个新游戏并返回。所以我的代码看起来像这样:

.post( function(req, res, next){

  ...findUsersExistingGame...

  .then(function(game){
    if(game){ return res.send(game); }
    else{
        return ...findUserWaitingForOpponentsGame...
    }
  }
  .then(function(game){
    if(game){ return res.send(game); }
    else{
        return ...createNewGame...
    }
  })
  .then(function(game){
        return res.send(game);
  })
  .catch(function(err){
       return next(err);
  });

我最终会将每个函数重构为辅助函数以提高可读性,但我需要首先弄清楚链接。我的问题是,如果我在承诺链中找到一个游戏(即用户现有的游戏或其他等待对手的用户),那么我将返回res.send(游戏);但是,第三个.then将抛出一个错误,因为我之前的.then()语句返回undefined。如果我想做一个res.send(游戏),我如何早早退出承诺链?

选项1:我已经看到了抛出错误并明确捕获错误的建议,但这种感觉从根本上说是错误的,使用错误来控制流量。

选项2:我可以做类似的事情而不是链接承诺,但这类似于“承诺/回调地狱”:

.post( function(req, res, next){

  ...findUsersExistingGame...

  .then(function(game){
    if(game){ return res.send(game); }
    else{
        ...findUserWaitingForOpponentsGame...
        .then(function(game){
            if(game){ return res.send(game); }
            else{
                return ...createNewGame...
                .then(function(game){
                    return res.send(game);
                });
            }
        })
    }
  }

还有另外一种方式(最好是在ES5中,因为我仍然试图从根本上理解承诺,但ES6也会受到欢迎)?

1 个答案:

答案 0 :(得分:7)

这里的主要问题是沿途每步都有三个可能的返回值:

  1. 找到游戏
  2. 尚未找到游戏
  3. 寻找游戏时出错
  4. 由于promises只是自然地将错误和错误分开,只要你想以不同的方式处理这三个单独的返回中的每一个,你就会添加一些自己的分支逻辑。

    要使用承诺结果干净地进行分支需要额外的嵌套级别,并且通常没有理由避免它,因为它会使您的代码最容易遵循并理解其逻辑。

    .post( function(req, res, next) {
        findUsersExistingGame(...).then(function(game) {
            if (game) return game;
            return findUserWaitingForOpponentsGame(...).then(function(game) {
                if (game) return game;
                // createNewGame() either resolves with a valid game or rejects with an error
                return createNewGame(...);
            });
        }).then(function(game) {
            res.send(game);
        }, function(err) {
            // send an error response here
        });
    });
    

    请注意这是如何简化每个阶段的返回,并返回下一个嵌套的承诺,使事物链,并集中处理将响应发送到一个地方以减少整体代码。

    现在,您可以通过让每个函数接受之前的游戏值并让他们检查是否已经存在有效游戏来隐藏其中一些逻辑,如果是,则他们什么都不做:

    .post( function(req, res, next) {
        findUsersExistingGame(args)
            .then(findUserWaitingForOpponentsGame)
            .then(createNewGame)
            .then(function(game) {
                res.send(game);
            }, function(err) {
                // send an error response here
            });
    });
    

    但是,在findUserWaitingForOpponentsGame()内,你必须接受findUsersExistingGame()解决的确切论据,你必须检查游戏是否有效。

    function findUserWaitingForOpponentsGame(args) {
        if (args.game) {
            return Promise.resolve(args);
        } else {
            return doAsyncFindUserWaitingForOpponentsGame(args);
        }
    }
    

    每个函数都将使用args对象解析,该对象上有任何公共参数,并且具有每个级别可以检查的.game属性。虽然这为您提供了一个很好的清洁控制流,但它确实在每个函数中创建了额外的代码,它强制每个函数接受作为前一个函数输出的参数(因此您可以直接链接)。你可以决定你更喜欢哪一个。