是否可以捕获 JavaScript 异步回调中抛出的异常?

Posted

技术标签:

【中文标题】是否可以捕获 JavaScript 异步回调中抛出的异常?【英文标题】:Is it possible to catch exceptions thrown in a JavaScript async callback? 【发布时间】:2011-04-10 07:38:21 【问题描述】:

有没有办法在 javascript 回调中捕获异常?有没有可能?

Uncaught Error: Invalid value for property <address>

这里是 jsfiddle:http://jsfiddle.net/kjy112/yQhhy/

try 
    // this will cause an exception in google.maps.Geocoder().geocode() 
    // since it expects a string.
    var zipcode = 30045; 
    var map = new google.maps.Map(document.getElementById('map_canvas'), 
        zoom: 5,
        center: new google.maps.LatLng(35.137879, -82.836914),
        mapTypeId: google.maps.MapTypeId.ROADMAP
    );
    // exception in callback:
    var geo = new google.maps.Geocoder().geocode( 'address': zipcode , 
       function(geoResult, geoStatus) 
          if (geoStatus != google.maps.GeocoderStatus.OK) console.log(geoStatus);
       
    );
 catch (e) 
    if(e instanceof TypeError)
       alert('TypeError');
    else
       alert(e);
​

【问题讨论】:

【参考方案1】:

在您的示例中它不会捕获任何内容的原因是因为一旦调用了geocode() 回调,try/catch 块就结束了。因此,geocode() 回调在try 块的范围之外执行,因此无法被它捕获。

据我所知,不可能捕获 JavaScript 回调中抛出的异常(至少,不是以任何直接的方式)。

【讨论】:

@anewb,给丹尼尔斯回答一个√ 问题是“有可能吗?”这是一个很好的答案,@BlakeRegalia。 合乎逻辑,正是我开始研究这个问题时所担心的。我遇到了一种情况,即我无法控制的代码引发了我需要捕获的异常,但我不能,因为它是异步的......【参考方案2】:

是的,您可以覆盖window.onerror 的默认行为:

window.onerror = function(message, file, lineNumber) 
  // all errors will be caught here
  // you can use `message` to make sure it's the error you're looking for
  // returning true overrides the default window behaviour
  return true; 
;

【讨论】:

注:特定于浏览器环境。不能在服务器端(NodeJS)工作。【参考方案3】:

您确实可以捕获在 JavaScript 回调函数中触发的异常。

关键是在回调代码中设置try/catch 块,因为回调代码之外的任何try/catch 块在执行回调代码时已经退出。因此,尽管上面的 try/catch 块无法捕获调用回调函数时引发的任何异常,但您仍然可以执行以下操作:

// this will cause an exception ing google.maps.Geocoder().geocode() 
// since it expects a string.
var zipcode = 30045; 
var map = new google.maps.Map(document.getElementById('map_canvas'), 
    zoom: 5,
    center: new google.maps.LatLng(35.137879, -82.836914),
    mapTypeId: google.maps.MapTypeId.ROADMAP
);
// exception in callback:
var geo = new google.maps.Geocoder().geocode( 'address': zipcode , 
   function(geoResult, geoStatus) 
      try 
          if (geoStatus != google.maps.GeocoderStatus.OK) console.log(geoStatus);
       catch(e)
          alert("Callback Exception caught!");
      
   
);

并且您将能够在抛出异常时捕获它。我不是 100% 确定是否会这样,所以我写了一些测试代码来验证。在 Chrome 19.0.1055.1 dev 上按预期捕获了异常。

【讨论】:

如果异常不是在回调代码中抛出而是由异步函数抛出会发生什么。 async 函数在其处理过程中的某个时刻抛出错误,这意味着我们的 try catch 都不会捕获它。我目前正面临这个问题。让我知道你认为可以做什么。 这个答案是错误的。抛出由异步线程中的a.b.c.f() 函数完成。换句话说,您的回调甚至不会被调用,因为在您的回调之前有一个错误。只有当您的回调函数通过调用将向您的回调函数抛出错误的已实现函数进行合作时,实施者决定将该错误传递给您的回调函数,这个答案才是正确的。 @Pacerier 的问题是您能否在实际回调中捕获异常。 if 回调没有被调用,那么这超出了这个问题的范围。 @Pacerier 接受的答案在说“无法捕获 JavaScript 回调中抛出的异常”方面也是错误的。因此,这是一个有用的补充。【参考方案4】:

我通过猴子修补控制台日志检测到错误。

if(window.console && console.error)
    var old = console.error;
    console.error = function()
        if(arguments[0].indexOf('Google Maps API error')!=-1)
            alert('Bad Google API Key '+ arguments[0]);
        
        Array.prototype.unshift.call(arguments);

        old.apply(this, arguments);
    

【讨论】:

【参考方案5】:

如果可以使用 Promises 和 async/await,则可以通过下面的示例代码解决:

async function geocode(zipcode) 

  return new Promise((resolve, reject) => 

    const g = new google.maps.Geocoder().geocode( 'address': zipcode ,  function(geoResult, geoStatus) 
      if (geoStatus != google.maps.GeocoderStatus.OK) 
        reject(new Error("Callback Exception caught"));
       else 
        resolve(g);
      ;

    );
  );


try 
  // ...

  // g will be an instance of new google.maps.Geocoder().geocode..
  // you can resolve with desired variables
  const g = await geocode(zipcode);

  // ...
 catch( e ) 
  console.log(e);

【讨论】:

【参考方案6】:

这是我的方法:

// the purpose of this wrapper is to ensure that any
// uncaught exceptions after a setTimeout still get caught
function callbackWrapper(func) 
    return function() 
        try 
            func();
         catch (err) 
            // callback will reach here :)
            // do appropriate error handling
            console.log("error");
        
    


try 
    setTimeout(callbackWrapper(function() throw "ERROR";), 1000);
 catch (err) 
    // callback will never reach here :(

【讨论】:

【参考方案7】:

根据所有答案,try/catch + 回调是在不同的上下文中设置的,但是 - 你如何解释这段代码 try/catch 的工作原理?

function doSomeAsynchronousOperation(cb) 
  cb(3);


function myApiFunc() 
  /*
   * This pattern does NOT work!
   */
  try 
    doSomeAsynchronousOperation((err) => 
      if (err) 
        console.log('got here');
        throw err;
      

    );
   catch (ex) 
    console.log(ex);
  


myApiFunc();

【讨论】:

之所以有效,是因为您没有异步调用回调。如果你这样做(例如使用setTimeout()),它将失败。 jsbin.com/vobokuriwu/edit?html,js,console,output

以上是关于是否可以捕获 JavaScript 异步回调中抛出的异常?的主要内容,如果未能解决你的问题,请参考以下文章

捕获回调函数引发的错误

我们如何在 JavaScript 中抛出和捕获 RangeError、ReferenceError、TypeError?

不能从异步承诺执行器函数中抛出错误

捕获在不同线程中抛出的异常

如何捕获ctypes中抛出的异常?

如何捕获 node_modules 中抛出的异常