如何捕获JavaScript错误?

3
在以下代码中,我有意抛出错误,但在Chrome(仅用于测试目的)中,它没有滚动到catch。如何将错误滚动到父级范围?
try {
  setTimeout(function() {
    console.log("Throwing Error...");
    throw({message:"Ouch!"});
  }, 500);
} catch(e) {
  console.log(e.message);
}

Chrome的回复如下:
Uncaught #<Object>
  (anonymous function)

以下是我正在处理的完整示例;当我需要“bob”时,它(有意地)超时。我想捕获requirejs错误,以便我可以使用我的应用程序的错误系统通知学习者。

(function() {
try {
  var scriptVersion = "1.0.0.1"
  window.onload = function() {
    var script = document.createElement("script");
    script.type = "text/javascript";
    script.src = "//content.com/pkg/" + scriptVersion + "/require-jquery.js";
    script.async = false;
    script.done = false;
    // OnReadyStateChange for older IE browsers
    script.onload = script.onreadystatechange = function() {
      if(!(this.done) && (!this.readyState || this.readyState == "loaded" || this.readyState == "complete")) {
        this.done = true;
        require.config({
          baseUrl: "//content.com/pkg/" + scriptVersion
        });
        require(["bob"]);
      }
    }
    document.getElementsByTagName("head")[0].appendChild(script);
  }
} catch(e) {
  console.log(e);
}
})();

我建议你在标题/描述中使用try-catch和setTimeout。这样当其他人搜索类似的问题时,他们可能会找到你的问题并受益。 - pilavdzice
4个回答

6
请看下面的编辑,了解如何使用requireJS解决实际问题。
问题在于setTimeout()函数在父级作用域中运行,并且没有错误。它安排(与系统一起)一个将来的回调事件,但当该回调在未来发生时,父级执行范围已经完成,并且回调被从系统顶层启动,就像一个新的系统事件(例如click事件处理程序)一样。
虽然父闭包仍然存在,因为setTimeout()内部的匿名函数仍然可以引用那些变量,但是父作用域的实际执行已经完成,因此try/catch的作用域也已经完成。
setTimeout()匿名函数的执行上下文是顶层的(由系统启动),因此没有父上下文可以在其中放置try/catch。您可以在匿名函数内放置try/catch,但是从那里抛出的内容将返回到调用setTimeout()回调的系统。
为了使您自己的代码捕获在setTimeout()回调内发生的任何异常,您需要在回调内放置一个try/catch。
  setTimeout(function() {
    try {
        console.log("Throwing Error...");
        throw({message:"Ouch!"});
    } catch(e) {
        console.log(e.message);
    }
  }, 500);

如果您能解释一下您试图解决的真正问题(而不是这个制造出来的测试案例),我们可能能够提供一些有用的选项。
编辑:现在您已经展示了您真正试图解决的问题。require.js库通过调用onError方法来启动每个错误。 onError方法的默认实现会抛出异常。 您可以分配自己的onError处理程序,并在回调中处理错误,而不是使用异常。 这听起来是正确的方法。
从requirejs源代码中:
/**
 * Any errors that require explicitly generates will be passed to this
 * function. Intercept/override it if you want custom error handling.
 * @param {Error} err the error object.
 */
req.onError = function (err) {
    throw err;
};

我想要一个“无论发生什么错误都能捕获”的方法。例如,我正在使用requirejs,如果脚本请求超时会抛出错误,但我希望捕获该错误以更好地为我的应用程序格式化该错误。 - scader
我已更新问题以包含完整脚本。感谢您提醒我关于通用测试用例 - 我将牢记这一点并在今后注意。 - scader
我认为Javascript没有能力做到“捕获所有”try/catch。来自系统的事件或异步回调(定时器回调、ajax回调等)本质上不在try/catch块中,除非你将每个事件都放入其中。你可以在自己的编码中建立一个约定,即每个系统回调或事件处理程序都通过一个外壳函数进行try/catch,然后再调用实际的回调,但即使如此,也无法保护你免受库执行其自己的事件处理或回调处理的影响。 - jfriend00
@scader - 我在我的回答中添加了更多细节,包括如何覆盖 requireJS 错误。 - jfriend00

1
前面的回答者解释得很正确。
另一种思考方式是,它之所以不起作用是因为setTimeout完成了,并且在最初运行时没有抛出异常。然后在你不再在try-catch块中时执行。
如果你像这样将try catch放在setTimeout函数内部,它就会起作用:
  setTimeout(function() {
     try {
         console.log("Throwing Error...");
         throw({message:"Ouch!"});
     } catch(e) {
       console.log(e.message);
     }
      }, 500);

如果你还有问题,请告诉我。


1
使用包装函数,如下所示。
// wrapper function
var tryable = function(closure, catchCallback) {
    closure(function(callback) {
        return function() {
            try {
                callback();
            } catch(e) {
                catchCallback(e);
            }
        };
    });
};


function throwException() {
    throw new Error("Hi");
}
tryable(function(catchable) {
    setTimeout(catchable(throwException), 1000);
}, function(e) {
    console.log("Error:)", e);
});

1

你的throw发生在catch块之后的某个时间点,当浏览器调用setTimeout回调函数时。

(catch使用逻辑作用域而非词法作用域)


我很希望这种情况能够奏效,以至于我完全忽略了显而易见的问题。 - scader

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接