【问题标题】:Implementation of simple request chain logic with promises使用 Promise 实现简单的请求链逻辑
【发布时间】:2018-11-22 15:54:37
【问题描述】:

我正在尝试实现以下逻辑的虚拟仿真:

但我不确定我是否完全了解如何做到这一点的最佳做法。 此任务的要点是避免触发冗余的catch 块回调。 IMO 如果第一次请求失败,那么所有以下代码都应该停止。

我的意思是:如果第一次请求失败,那么我们不会发出第二次请求,也不会调用catch 第二次请求承诺块。

简而言之,我正在寻找非常干净和简单的解决方案,如下所示:

firstRequest()
    .then(r => {
        console.log('firstRequest success', r);
        return secondRequest();
    }, e => console.log('firstRequest fail', e))
    .then(r => {
        console.log('secondRequest success', r);
        // Should I return something here? Why?
    }, e => console.log('secondRequest fail', e));

我已经编写了以下实现。如果两个请求都成功,并且第二个请求失败,它会按预期工作。但是如果第一个请求失败,它就会出错(正如您所看到的 catch 块正在触发)。您可以使用isFirstSucceedisSecondSucceed 标志来检查它。

var ms = 1000;
var isFirstSucceed = false;
var isSecondSucceed = true;

var getUsersId = () => new Promise((res, rej) => {
  console.log('request getUsersId');
  setTimeout(() => {
    if (isFirstSucceed) {
      return res([1,2,3,4,5]);
    } else {
      return rej(new Error());
    }
  }, ms);
});

var getUserById = () => new Promise((res, rej) => {
  console.log('request getUserById');
  setTimeout(() => {
    if (isSecondSucceed) {
      return res({name: 'John'});
    } else {
      return rej(new Error());
    }
  }, ms);
});

getUsersId()
.then(r => {
  console.info('1st request succeed', r);
  return getUserById();
}, e => {
  console.error('1st request failed', e);
  throw e;
})
.then(
  r => console.info('2nd request succeed', r), 
  e => {
    console.error('2nd request failed', e);
    throw e;
});

我可以将第二个请求的then 移动到第一个请求的then,但它看起来很难看。

var ms = 1000;
var isFirstSucceed = false;
var isSecondSucceed = true;

var getUsersId = () => new Promise((res, rej) => {
  console.log('request getUsersId');
  setTimeout(() => {
    if (isFirstSucceed) {
      return res([1,2,3,4,5]);
    } else {
      return rej(new Error());
    }
  }, ms);
});

var getUserById = () => new Promise((res, rej) => {
  console.log('request getUserById');
  setTimeout(() => {
	if (isSecondSucceed) {
      return res({name: 'John'});
    } else {
      return rej(new Error());
    }
  }, ms);
});

getUsersId()
.then(r => {
  console.info('1st request succeed', r);
    getUserById().then(
      r => console.info('2nd request succeed', r), 
      e => {
        console.error('2nd request failed', e);
        throw e;
      });
}, e => {
    console.error('1st request failed', e);
    throw e;
})

问题:

  • 如何根据所有 Promise 最佳实践来实现所描述的逻辑?
  • 是否可以在每个 catch 块中避免 throw e
  • 我应该使用 es6 Promise 吗?还是使用一些 Promise 库更好?
  • 还有其他建议吗?

【问题讨论】:

    标签: javascript promise es6-promise


    【解决方案1】:

    您的流程图是您想要实现的逻辑,但它并不是 Promise 的工作方式。问题是没有办法告诉一个承诺链就在这里“结束”并且不要在链的后面调用任何其他.then().catch() 处理程序。如果您在链中收到拒绝并​​使其被拒绝,它将调用链中的下一个 .catch() 处理程序。如果您在本地处理拒绝并且不重新抛出它,那么它将调用链中的下一个 .then() 处理程序。这些选项都不完全符合您的逻辑图。

    因此,您必须从心理上改变对逻辑图的看法,以便可以使用承诺链。

    最简单的选择(可能用于 90% 的 Promise 链)是在链的末尾放置一个错误处理程序。链中任何地方的任何错误都会跳到链末尾的单个 .catch() 处理程序。仅供参考,在大多数情况下,我发现使用 .catch() 的代码比 .then() 的第二个参数更具可读性,所以这就是我在这里展示的方式

    firstRequest().then(secondRequest).then(r => {
        console.log('both requests successful');
    }).catch(err => {
        // An error from either request will show here 
        console.log(err);
    });
    

    如果您提供了一个 catch 块并且您既没有返回被拒绝的 Promise 也没有重新抛出错误,那么 Promise 基础架构会认为您已经“处理”了该 Promise,因此链会继续解决。如果您重新抛出错误,则下一个 catch 块将触发,并且任何介入的 .then() 处理程序都将被跳过。

    您可以利用它在本地捕获错误,执行某些操作(例如记录它),然后重新抛出它以保持 Promise 链被拒绝。

    firstRequest().catch(e => {
         console.log('firstRequest fail', e));
         e.logged = true;
         throw e;
    }).then(r => {
        console.log('firstRequest success', r);
        return secondRequest();
    }).then(r => {
        console.log('secondRequest success', r);
    }).catch(e => {
        if (!e.logged) {
            console.log('secondRequest fail', e));
        }
    });
    

    或者,使用调试消息标记错误对象的版本,然后重新抛出,然后只能在一个地方记录错误:

    firstRequest().catch(e => {
         e.debugMsg = 'firstRequest fail';
         throw e;
    }).then(r => {
        console.log('firstRequest success', r);
        return secondRequest().catch(e => {
            e.debugMsg = 'secondRequest fail';
            throw e;
        });
    }).then(r => {
        console.log('secondRequest success', r);
    }).catch(e => {
        console.log(e.debugMsg, e);
    });
    

    我什至遇到过一些情况,其中一个小辅助函数为我节省了一些代码和一些视觉复杂性,特别是如果链中有一堆这样的情况:

    function markErr(debugMsg) {
        return function(e) {
            // mark the error object and rethrow
            e.debugMsg = debugMsg;
            throw e;
        }
    }
    
    firstRequest()
      .catch(markErr('firstRequest fail'))
      .then(r => {
        console.log('firstRequest success', r);
        return secondRequest().catch(markErr('secondRequest fail'));
    }).then(r => {
        console.log('secondRequest success', r);
    }).catch(e => {
        console.log(e.debugMsg, e);
    });
    

    单独回答每个问题:

    如何根据所有 promises 最佳实践来实现描述的逻辑?

    如上所述。我想说最简单和最好的做法是我展示的第一个代码块。如果您需要确保在到达最终 .catch() 时您有一个唯一可识别的错误,以便您知道是哪一步导致它,然后将每个单独函数中的被拒绝错误修改为唯一的,以便您可以分辨出它来自哪个最后一个 .catch() 块。如果你不能修改这些函数,那么你可以用一个捕获并标记它们的错误的包装器来包装它们,或者你可以使用我展示的markErr() 类型的解决方案来内联。在大多数情况下,您只需要知道存在错误而不是错误发生的确切步骤,因此通常这对于链中的每个步骤都不是必需的。

    是否可以避免在每个 catch 块中都抛出 e ?

    这取决于。如果错误对象已经是唯一的,那么您可以只在最后使用一个.catch()。如果错误对象不是唯一的,但您需要知道哪个确切步骤失败,那么您必须在每个步骤中使用 .catch() 以便您可以唯一地标记错误,或者您需要修改链中的每个函数以具有一个独特的错误。

    我应该使用 es6 Promise 吗?

    是的。没有比我知道的更好的方法了。

    还是用一些promise库比较好?

    我不知道 Promise 库中的任何功能会使这变得更简单。这实际上只是关于您希望如何报告错误以及每个步骤是否定义了一个独特的错误。 Promise 库无法真正为您做到这一点。

    还有其他建议吗?

    继续学习更多关于如何将承诺转化为每个问题的解决方案。

    【讨论】:

      【解决方案2】:

      IMO,您可以使用 async/await... 仍然可以使用 Promise,但看起来更简洁。这是我对上述逻辑的示例方法。

      function firstRequest() {
         return new Promise((resolve, reject) =>  {
             // add async function here
             // and resolve("done")/reject("err")
          });
       } 
      
      function secondRequest() {
         return new Promise((resolve, reject) =>  {
             // add async function here
             // and resolve("done")/reject("err")
          });
      }
      
      async function startProgram() { 
         try { 
             await firstRequest();
             await secondRequest();
         } catch(err) { 
             console.log(err); 
             goToEndFn();
         }
      }
      
      startProgram(); // start the program
      

      【讨论】:

      • 这并不能真正回答有关 OP 如何分别记录和处理每个单独错误的问题。这只是将所有错误扔到一个位置,您也可以使用常规的 Promise 链很好地完成,最后使用一个 .catch()。我认为OP知道这一点。这不是他们的问题。
      【解决方案3】:

      https://github.com/xobotyi/await-of

      $ npm i --save await-of 
      

      import of from "await-of";
      
      async () => {
          let [res1, err1] = await of(axios.get('some.uri/to/get11'));
          let [res2, err2] = await of(axios.get('some.uri/to/get22'));
      
          if (err1) {
             console.log('err1', err1)
          }
          if (err2) {
             console.log('err2', err2)
          }
          console.log('res1', res1)
          console.log('res2', res2)
      
      };
      

      【讨论】:

        【解决方案4】:

        可能是异步/等待?

        async function foo() {
            try {
                const firstResult = await firstRequest();
                const secondResult = await secondRequest();
            } catch(e) {
                // e = either first or second error
            }
        }
        

        在此代码中,第一个请求的错误将控制权转移到 catch 块,第二个请求不会启动

        我应该使用 es6 Promises 吗?

        可能是的,直到您非常确定您的代码在过时的环境中使用。它们已经不是那么新鲜和浮华了

        【讨论】:

          【解决方案5】:

          你不需要为每个承诺处理错误

          您只需将错误作为常见错误处理

          这样做:

          var ms = 1000;
          var isFirstSucceed = false;
          var isSecondSucceed = true;
          
          var getUsersId = () => new Promise((res, rej) => {
            console.log('request getUsersId');
            setTimeout(() => {
              if (isFirstSucceed) {
                return res([1,2,3,4,5]);
              } else {
                return rej();
              }
            }, ms);
          });
          
          var getUserById = () => new Promise((res, rej) => {
            console.log('request getUserById');
            setTimeout(() => {
              if (isSecondSucceed) {
                return res({name: 'John'});
              } else {
                return rej(new Error());
              }
            }, ms);
          });
          
          getUsersId()
          .then(r => {
            console.info('1st request succeed', r);
            return getUserById();
          })
          .then(r => {
            console.info('2st request succeed', r);
            return;
          })
          .catch((e) => {
              console.error('request failed', e);
              throw new Error(e);
          })
          

          【讨论】:

          • 谢谢,但您提供了不同的逻辑。正如我所描述的,我需要处理每个请求失败。在您的示例中,所有失败的逻辑都放在最后一个 catch 块中,但我想将该逻辑拆分为几个部分。
          【解决方案6】:

          您可以使用鸭式打字技术来停止使用return { then: function() {} }; 的承诺链。我在这行之后修改了你的代码console.error('1st request failed', e);

          var ms = 1000;
              var isFirstSucceed = false;
              var isSecondSucceed = true;
          
              var getUsersId = () => new Promise((res, rej) => {
                console.log('request getUsersId');
                setTimeout(() => {
                  if (isFirstSucceed) {
                    return res([1,2,3,4,5]);
                  } else {
                    return rej(new Error());
                  }
                }, ms);
              });
          
              var getUserById = () => new Promise((res, rej) => {
                console.log('request getUserById');
                setTimeout(() => {
                  if (isSecondSucceed) {
                    return res({name: 'John'});
                  } else {
                    return rej(new Error());
                  }
                }, ms);
              });
          
              getUsersId()
              .then(r => {
                console.info('1st request succeed', r);
                return getUserById();
              }, e => {
                console.error('1st request failed', e);
                return { then: function() {} };
              })
              .then(
                r => console.info('2nd request succeed', r), 
                e => {
                  console.error('2nd request failed', e);
                  throw e;
              });

          【讨论】:

            猜你喜欢
            • 2010-10-10
            • 1970-01-01
            • 2019-11-09
            • 2019-08-27
            • 2016-11-10
            • 1970-01-01
            • 2018-07-13
            • 1970-01-01
            • 2016-12-19
            相关资源
            最近更新 更多