【问题标题】:Handling specific errors in JavaScript (think exceptions)处理 JavaScript 中的特定错误(想想异常)
【发布时间】:2010-11-28 20:19:14
【问题描述】:

您将如何实现不同类型的错误,以便能够捕获特定错误并让其他错误冒出来......?

实现此目的的一种方法是修改 Error 对象的原型:

Error.prototype.sender = "";


function throwSpecificError()
{
    var e = new Error();

    e.sender = "specific";

    throw e;
}

捕获特定错误:

try
{
    throwSpecificError();
}

catch (e)
{
    if (e.sender !== "specific") throw e;

    // handle specific error
}


你们有其他选择吗?

【问题讨论】:

    标签: javascript error-handling


    【解决方案1】:

    要创建自定义异常,您可以从Error 对象继承:

    function SpecificError () {
    
    }
    
    SpecificError.prototype = new Error();
    
    // ...
    try {
      throw new SpecificError;
    } catch (e) {
      if (e instanceof SpecificError) {
       // specific error
      } else {
        throw e; // let others bubble up
      }
    }
    

    不从Error 继承的简约方法可能是抛出一个具有名称和消息属性的简单对象:

    function throwSpecificError() {
      throw {
        name: 'SpecificError',
        message: 'SpecificError occurred!'
      };
    }
    
    
    // ...
    try {
      throwSpecificError();
    } catch (e) {
      if (e.name == 'SpecificError') {
       // specific error
      } else {
        throw e; // let others bubble up
      }
    }
    

    【讨论】:

    • Error继承有问题。见stackoverflow.com/questions/1382107/…
    • 此代码的问题:} catch (e) { if (e.name == 'SpecificError') { // specific error } else { throw e; // let others bubble up } } 是它在 IE7 中不起作用,引发“抛出异常但未捕获”错误。以下是来自 msdn 的极其愚蠢(一如既往)的解释:“您包含了一个 throw 语句,但它没有包含在 try 块中,或者没有关联的 catch 块来捕获错误。从 try 块中抛出异常使用 throw 语句,并在 try 块之外使用 catch 语句捕获。"
    • 嗯,微软的 C# 确实比 Javascript 更好地处理错误:P。 Mozzilla 在 Firefox 中添加了类似的东西。虽然它不在 Ecmascript 标准中,甚至不在 ES6 中,但他们也解释了如何使其符合,尽管它不是那么简洁。与上面基本相同,但使用instanceOf。检查here
    • 在 Javascript 中,你可以抛出任何你想要的东西,无论是简单的字符串、数字(想想错误代码)还是完全限定的对象。甜!
    • @LuisNell,如果您仔细查看我的代码示例,您会发现我并没有建议使用构造函数的name 属性。我建议扔一个带有name 属性的定制对象,这不会破坏......
    【解决方案2】:

    正如下面的 cmets 所述,这是特定于 Mozilla 的,但您可以使用“条件捕获”块。例如:

    try {
      ...
      throwSpecificError();
      ...
    }
    catch (e if e.sender === "specific") {
      specificHandler(e);
    }
    catch (e if e.sender === "unspecific") {
      unspecificHandler(e);
    }
    catch (e) {
      // don't know what to do
      throw e;
    } 
    

    这提供了一些更类似于 Java 中使用的类型化异常处理的东西,至少在语法上是这样。

    【讨论】:

    • 结合CMS的回答就完美了。
    • 条件捕获是我之前不知道或忘记的东西。感谢您教育/提醒我! +1
    • 仅受 Firefox 支持(自 2.0 起)。它甚至不会在其他浏览器中解析;你只会得到语法错误。
    • 是的,这是一个仅限 Mozilla 的扩展,它甚至没有被提议用于标准化。作为一个语法级别的特性,没有办法嗅探它并选择性地使用它。
    • 另外,关于建议的解决方案是非标准的。引用 [Mozilla 的 JavaScript 参考 [(developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…): This feature is non-standard and is not on a standards track. Do not use it on production sites facing the Web: it will not work for every user. There may also be large incompatibilities between implementations and the behavior may change in the future.
    【解决方案3】:

    try-catch-finally.js

    使用 try-catch-finally.js,您可以使用匿名回调调用 _try 函数,它会调用该函数,并且您可以链接 .catch 调用以捕获特定错误,和.finally 调用以执行任一方式。

    示例

    _try(function () {
        throw 'My error';
    })
    .catch(Error, function (e) {
        console.log('Caught Error: ' + e);
    })
    .catch(String, function (e) {
        console.log('Caught String: ' + e);
    })
    .catch(function (e) {
        console.log('Caught other: ' + e);
    })
    .finally(function () {
        console.log('Error was caught explicitly');
    });
    

    现代箭头函数和模板字面量示例

    _try(() => {
      throw 'My error';
    }).catch(Error, e => {
      console.log(`Caught Error: ${e}`);
    }).catch(String, e => {
      console.log(`Caught String: ${e}`);
    }).catch(e => {
      console.log(`Caught other: ${e}`);
    }).finally(() => {
      console.log('Error was caught explicitly');
    });
    

    【讨论】:

      【解决方案4】:

      出口使用模块

      /**
       * Custom InputError
       */
      class InputError extends Error {
        /**
         * Create InputError
         * @param {String} message
         */
        constructor(message) {
          super(message);
          this.name = this.constructor.name;
          Error.captureStackTrace(this, this.constructor);
        }
      }
      
      /**
       * Custom AuthError
       */
      class AuthError extends Error {
        /**
         * Create AuthError
         * @param {String} message
         */
        constructor(message) {
          super(message);
          this.name = this.constructor.name;
          Error.captureStackTrace(this, this.constructor);
        }
      }
      
      /**
       * Custom NotFoundError
       */
      class NotFoundError extends Error {
        /**
         * Create NotFoundError
         * @param {String} message
         */
        constructor(message) {
          super(message);
          this.name = this.constructor.name;
          Error.captureStackTrace(this, this.constructor);
        }
      }
      
      module.exports = {
        InputError: InputError,
        AuthError: AuthError,
        NotFoundError: NotFoundError
      };
      

      导入脚本:

      const {InputError, AuthError, NotFoundError} = require(path.join(process.cwd(), 'lib', 'errors'));
      

      用途:

      function doTheCheck = () =>
        checkInputData().then(() => {
          return Promise.resolve();
        }).catch(err => {
          return Promise.reject(new InputError(err));
        });
      };
      

      外部调用代码:

      doTheCheck.then(() => {
        res.send('Ok');
      }).catch(err => {
        if (err instanceof NotFoundError) {
          res.status(404).send('Not found');
        } else if (err instanceof AuthError) {
          res.status(301).send('Not allowed');
        } else if (err instanceof InputError) {
          res.status(400).send('Input invalid');
        } else {
          console.error(err.toString());
          res.status(500).send('Server error');
        }
      });
      

      【讨论】:

        【解决方案5】:

        遗憾的是,在 Javascript 中实现这一基本功能没有“官方”方式。我将分享我在不同包中看到的三种最常见的解决方案,以及如何在现代 Javascript (es6+) 中实现它们,以及它们的一些优缺点。

        1。子类化错误类

        在 es6 中子类化“Error”的实例变得更加容易。只需执行以下操作:

        class FileNotFoundException extends Error {
          constructor(message) {
            super(message)
            // Not required, but makes uncaught error messages nicer.
            this.name = 'FileNotFoundException'
          }
        }
        

        完整示例:

        class FileNotFoundException extends Error {
          constructor(message) {
            super(message)
            // Not required, but makes uncaught error messages nicer.
            this.name = 'FileNotFoundException'
          }
        }
        
        // Example usage
        
        function readFile(path) {
          throw new FileNotFoundException(`The file ${path} was not found`)
        }
        
        try {
          readFile('./example.txt')
        } catch (err) {
          if (err instanceof FileNotFoundException) {
            // Handle the custom exception
            console.log(`Could not find the file. Reason: ${err.message}`)
          } else {
            // Rethrow it - we don't know how to handle it
            // The stacktrace won't be changed, because
            // that information is attached to the error
            // object when it's first constructed.
            throw err
          }
        }

        如果您不喜欢将this.name 设置为硬编码字符串,则可以将其设置为this.constructor.name,这将给出您的班级名称。这样做的好处是您的自定义异常的任何子类都不需要同时更新this.name,因为this.constructor.name 将是子类的名称。

        与某些替代解决方案相比,子类异常的优势在于它们可以提供更好的编辑器支持(例如自动完成)。您可以轻松地将自定义行为添加到特定的异常类型,例如附加函数、替代构造函数参数等。在提供自定义行为或数据时支持 typescript 也往往更容易。

        有很多关于如何正确继承Error 的讨论。例如,如果您使用的是转译器,上述解决方案可能不起作用。如果可用,一些人建议使用特定于平台的 captureStackTrace()(虽然我在使用它时没有注意到错误有任何区别 - 也许它不再那么相关了?‍♂️)。要了解更多信息,请参阅 this MDN 页面和 This Stackoverflow 答案。

        许多浏览器 API 走这条路并抛出自定义异常(可以看到 here

        2。为错误添加区分属性

        这个想法很简单。创建你的错误,为你的错误添加一个额外的属性,例如“code”,然后抛出它。

        const error = new Error(`The file ${path} was not found`)
        error.code = 'NotFound'
        throw error
        

        完整示例:

        function readFile(path) {
          const error = new Error(`The file ${path} was not found`)
          error.code = 'NotFound'
          throw error
        }
        
        try {
          readFile('./example.txt')
        } catch (err) {
          if (err.code === 'NotFound') {
            console.log(`Could not find the file. Reason: ${err.message}`)
          } else {
            throw err
          }
        }

        当然,您可以创建一个辅助函数来删除一些样板并确保一致性。

        此解决方案的优点是您无需导出程序包可能抛出的所有可能异常的列表。例如,如果您的包一直使用 NotFound 异常来指示特定函数无法找到预期的资源,您可以想象这会变得多么尴尬。您想要添加一个 addUserToGroup() 函数,该函数理想情况下会根据未找到哪个资源而引发 UserNotFound 或 GroupNotFound 异常。对于子类异常,您将面临一个棘手的决定。使用错误对象上的代码,您就可以做到。

        这是路由节点的 fs 模块处理异常。如果您尝试读取一个不存在的文件,它会抛出一个带有一些附加属性的错误实例,例如 code,对于该特定异常,它将设置为 "ENOENT"

        3。返回您的异常。

        谁说你必须扔掉它们?在某些情况下,只返回出错的内容可能最有意义。

        function readFile(path) {
          if (itFailed()) {
            return { exCode: 'NotFound' }
          } else {
            return { data: 'Contents of file' }
          }
        }
        

        在处理大量异常时,这样的解决方案可能最有意义。这很简单,并且可以帮助自我记录哪些函数给出了哪些异常,这使得代码更加健壮。缺点是它会给你的代码增加很多臃肿。

        完整示例:

        function readFile(path) {
          if (Math.random() > 0.5) {
            return { exCode: 'NotFound' }
          } else {
            return { data: 'Contents of file' }
          }
        }
        
        function main() {
          const { data, exCode } = readFile('./example.txt')
        
          if (exCode === 'NotFound') {
            console.log('Could not find the file.')
            return
          } else if (exCode) {
            // We don't know how to handle this exCode, so throw an error
            throw new Error(`Unhandled exception when reading file: ${exCode}`)
          }
        
          console.log(`Contents of file: ${data}`)
        }
        main()

        非解决方案

        其中一些解决方案感觉工作量很大。只是抛出一个对象文字很诱人,例如throw { code: 'NotFound' }不要这样做! 堆栈跟踪信息会附加到错误对象上。如果其中一个对象字面量滑过并成为未捕获的异常,则您将无法通过堆栈跟踪来了解它发生的位置或方式。一般来说,调试会困难得多。

        奖励:显式例外

        当您的包开始处理大量异常时,我建议使用上述“返回异常”方法,以帮助您跟踪哪些异常可能来自哪里(至少在包内部使用它 -你仍然可以为你的包用户扔东西)。有时即使是这样的解决方案也不够好。我整理了一些显式异常包来帮助解决这些场景(可以找到here。还有一个简单的版本,你可以复制粘贴到你的项目here)。这个想法是要求用户明确列出他们希望函数调用提供哪些异常。这使得跟踪异常路径变得非常容易。

        try {
          // readFile() will give an exception.
          // In unwrap(), you list which exceptions you except, to help with self-documentation.
          // runtime checks help ensure no other kinds of exceptions slip through.
          return unwrap(readFile('./example.txt'), ['NotFound'])
        } catch (err) {
          // Handle the exception here
        }
        

        完整示例:

        // You must have the explicit-exceptions package for this code to run
        const { Exception, wrap, unwrap } = require('explicit-exceptions')
        
        const readFile = wrap(path => {
          throw new Exception('NotFound')
        })
        
        function main () {
          try {
            return unwrap(readFile('./example.txt'), ['NotFound'])
          } catch (ex) {
            if (!(ex instanceof Exception)) throw ex // Not an exception, don't handle it
            console.assert(ex.code === 'NotFound')
            console.log('File not found')
          }
        }
        
        main()

        【讨论】:

          【解决方案6】:

          一个较老的问题,但在现代 JS(截至 2021 年底)中,我们可以通过在 catch 块中打开错误的原型构造函数来做到这一点,只需将其直接匹配到我们感兴趣的任何和所有错误类,而不是而不是进行instanceof 检查,利用instanceof 将匹配整个层次结构的事实,而身份检查则不会:

          import { SomeError } from "library-that-uses-errors":
          import MyErrors from "./my-errors.js";
          
          try {
            const thing = someThrowingFunction();
          } catch (err) {
            switch (err.__proto__.constuctor) {
              // We can match against errors from libraries that throw custom errors:
              case (SomeError): ...
          
              // or our own code with Error subclasses:
              case (MyErrors.SOME_CLASS_THAT_EXTENDS_ERROR): ..
          
              // and of course, we can check for standard built-in JS errors:
              case (TypeError): ...
          
              // and finally, if we don't know what this is, we can just
              // throw it back and hope something else deals with it.
              default: throw err;
            }
          }
          

          (当然,如果开关太多,我们也可以使用 if/elseif/else 来做到这一点“我讨厌在任何地方都使用break”,这对很多人来说都是如此)

          【讨论】:

            【解决方案7】:

            我不喜欢这些解决方案,所以我自己做了。 try-catch-finally.js 非常酷,只是如果您在尝试之前忘记了一个小下划线 (_),那么代码仍然可以正常运行,但什么都不会被捕获!呸。

            捕捉过滤器

            我在代码中添加了一个 CatchFilter:

            "use strict";
            
            /**
             * This catches a specific error. If the error doesn't match the errorType class passed in, it is rethrown for a
             * different catch handler to handle.
             * @param errorType The class that should be caught
             * @param funcToCall The function to call if an error is thrown of this type
             * @return {Function} A function that can be given directly to the `.catch()` part of a promise.
             */
            module.exports.catchOnly = function(errorType, funcToCall) {
              return (error) => {
                if(error instanceof errorType) {
                  return funcToCall(error);
                } else {
                  // Oops, it's not for us.
                  throw error;
                }
              };
            };
            

            现在我可以过滤了

            现在我可以像在 C# 或 Java 中一样进行过滤了:

            new Promise((resolve, reject => {
               <snip><snip>
            }).catch(CatchFilter.catchOnly(MyError, err =>
               console.log("This is for my error");
            }).catch(err => {
               console.log("This is for all of the other errors.");
            });
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-03-03
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-10-28
              相关资源
              最近更新 更多