【问题标题】:ExpressJS / NodeJS / Promises: Return early from promise chainExpressJS / NodeJS / Promises:从承诺链提前返回
【发布时间】:2016-05-08 23:29:50
【问题描述】:

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

.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(game);但是,第三个 .then 将引发错误,因为我之前的 .then() 语句返回未定义。如果我想做一个 res.send(game),如何尽早退出承诺链?

选项 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 中,因为我仍在尝试从根本上理解 Promise,但也欢迎 ES6 回答)?

【问题讨论】:

  • 听起来您正在寻找 ES7 提出的 async/await 语法 (github.com/yortus/asyncawait#1-introduction)。如果你现在想要它,你可能会为它找到一个转译器......
  • 回调地狱/抛出错误是我唯一的选择吗?
  • @PDN yield 和 (ES6) 怎么样?还是event-driven architecture
  • @PDN 如果你可以使用后端,我可以给你一个很好的解决方案。
  • @stdob 你能提交一个答案吗?

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


【解决方案1】:

这里的主要问题是您在此过程中的每一步都有三个可能的返回值:

  1. 找到游戏
  2. 尚未找到游戏
  3. 寻找游戏时出错

由于 promises 只会自然地分离错误和没有错误,只要你想以不同的方式处理这三个不同的返回,你就会添加一些你自己的分支逻辑。

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

.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
    });
});

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


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

.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 属性。虽然这为您提供了一个非常干净的控制流,但它确实在每个函数中创建了额外的代码,并且它强制每个函数接受作为前一个函数输出的参数(因此您可以进行直接链接)。你可以决定你更喜欢哪个。

【讨论】:

  • 在第一个示例中,使用.catch 会更好,这样我也可以在findUsersExistingGamefindUserWaitingForOpponentsGame 中捕获错误吗?
  • @PDN - 这取决于您希望这些错误的行为是什么。如果您想自定义处理错误并可能继续链,那么“是的”您需要一个本地拒绝处理程序,以便您可以将执行此操作的代码放在那里。如果您希望整个.post 以错误响应,当链中的任何位置出现错误时,那么现在就是这样编写的。错误将传播回顶层,上面写着“在此处发送错误响应”。在我的实现中,我尝试集中响应处理,包括成功和错误。
猜你喜欢
  • 2017-02-21
  • 2023-01-27
  • 2016-05-18
  • 2015-03-14
  • 2018-02-15
  • 1970-01-01
  • 2021-09-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多