Promise.catch():如何识别操作拒绝和程序性抛出之间的差异

Pet*_*erT 5 javascript node.js promise es6-promise

经过大量的谷歌搜索,我找不到一个明确的例子,如何避免编程每个捕获,以确定Promise拒绝错误是程序化还是可操作的.将此与提供回调的节点回调模式(error,params ...)进行比较,其中在error参数中干净地提供操作错误,并且通过throw链处理编程错误.

请告诉我,我正在犯一个noob错误,这是我错过的一个简单的答案.


EDIT Node v10.0.0现在通过添加错误代码解决了这个问题.

感谢RisingStack将此信息发送到我的收件箱:

https://blog.risingstack.com/node-js-10-lts-feature-breakdown

......而且正式而且简洁(一如既往):

https://nodejs.org/api/errors.html#errors_error_code


考虑一个常见的例子:

function logMeIn (email, password, login_token) {
    selectFromDbByEmailAndCheckPwAndReturnId (email, password)
    .then(id => { return updateUserIdLoginToken(id, login_token); })
    .catch(error => {
        // all rejects and throws end up here
        console.log(error);
    })
})

function selectFromDbByEmailAndCheckPwAndReturnId (email, password) {
   return new Promise((resolve, reject) => {
      db.sql.query( /* params */, (error, results) => {
          blarg = 1; // <-- reference error, programmatic
          // do your SELECT * FROM Users where email=? ... etc.
          if (error) {
               return reject(error); // <-- operational sql error
          :
          :
          if (resultsRowsFromQuery.length === 0) {
             // vvvvv operational error: user not found
             return reject(new Error("User not in database"));
          }
          :
          // hash password & salt, etc etc etc ...
          :
          return resolve(resultRowsFromQuery[0].id);
      });
   });
}
// no need to code out updateUserIdLoginToken...
Run Code Online (Sandbox Code Playgroud)

在这个示例中,catch将捕获程序错误和两个操作错误,并且我必须编程catch以确定哪个.如果我想向用户返回他们的电子邮件未找到的事实,我不能只使用该消息,因为我可能会意外地返回引用错误消息.(尴尬!)

但是,与sql.query模式进行比较,很明显错误是可操作的,因为blarg=1如果不在promise中,它会冒泡到更高的级别.

我看到关于拒绝值应该是什么以及如何区分的文档很少.我考虑过使用resolve(new Error()),以便我的成功实现功能确定是否存在操作错误,并且为程序错误保存.catch,但这只是愚蠢的.

那里有很多不好的信息,因为它在过去的7年中经常引用bluebird,Q,A +和ES6 ......很难找到ES6 Node/7/9的例子...... [我甚至看到了声称的链接使用.then(func A(),func B()).catch()将编程错误发送给B而不是catch().大声笑.]

思考?

编辑#1:无承诺请求示例:

function logMeIn (email, password, login_token) {
  try {
    selectFromDbByEmailAndCheckPwAndReturnId (email, password, (error, id) => {
      if (error) {
        console.log("Operational error:", error)
        return;
      }
      // no error, got id, do next step...
      updateUserIdLoginToken(id, login_token, error => { 
         // do next thing, like return res.render() or something...
      });
    });
  } catch (e) {
    console.error("Programmatic error:", e);
  }
})

function selectFromDbByEmailAndCheckPwAndReturnId (email, password, callback) {
  db.sql.query( /* params */, (error, results) => {
      blarg = 1; // <-- reference error, programmatic
      // do your SELECT * FROM Users where email=? ... etc.
      if (error) {
         return callback(error, null);
      }
      :
      :
      if (resultsRowsFromQuery.length === 0) {
         // vvvvv operational error: user not found
         return callback(new Error("User not in database"), null);
      }
      :
      // hash password & salt, etc etc etc ...
      :
      return callback(null, id);
  });
}
Run Code Online (Sandbox Code Playgroud)

Tam*_*dus 3

您对节点样式和基于 Promise 的代码期望过高。这两种异步函数都区分操作错误和编程错误的概念,您实际上可以抛出/拒绝任何内容,这就是为什么您没有找到太多关于它的文档。这两种模式都是异步代码流的基元,仅此而已。节点样式版本有点尴尬,因为它允许传播同步和异步错误(您需要同时使用try-catchif(error)来处理所有错误)。尽管他们应该只使用异步版本。在单个函数中使用两个“错误通道”并不是一个功能,它只是行为不当的代码。

节点样式或基于 Promise 的异步代码都不应该抛出常规同步错误。 因此,不要使用这两种不同的错误传播渠道来区分程序错误和操作错误。

那么要回答这个问题,你如何区分它们?就像处理常规同步代码一样,您必须引入自己的抽象:

  • 要么让每个服务函数返回某种 Result 类型,该类型将有一个用于操作错误的字段(请参阅 rust 的错误处理:https ://doc.rust-lang.org/book/first-edition/error-handling.html )
  • 或者创建一个OperationalError类,使用任意数量的子类,并使顶级代码区分OperationalError-s 和任何其他类型的错误。这就是我的推荐。
  • 或使用您的框架提供的内容,尽管我没有找到任何好的例子