Is it possible to catch exceptions thrown in a JavaScript async callback?

Viewed 34983

Is there a way to catch exceptions in JavaScript callbacks? Is it even possible?

Uncaught Error: Invalid value for property <address>

Here is the 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);
}​
7 Answers

You can indeed catch exceptions that fire within a JavaScript callback function.

The key is to set up the try/catch block within the callback code, as any try/catch blocks outside the callback code will have already exited by the time the callback code is executed. So while your try/catch block above won't be able to catch any exceptions that get thrown when the callback function is called, you can still do something like this:

// 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!");
      }
   }
);

and you'll be able to capture the exception when it is thrown. I wasn't 100% sure whether that would be the case or not, so I wrote some test code to verify. The exception is captured as expected on Chrome 19.0.1055.1 dev.

If you can use Promises, it can be solved as shown in sample code below:

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);
      };
    });
  });
}

Executing the function and catching the error using catch():

geocode(zipcode)
  // g will be an instance of new google.maps.Geocoder().geocode..
  .then( g => console.log ( g ) )
  .catch( error => console.log( error ) );

Executing the function and catching the error using async/await:

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);
}

Here's my approach:

// 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 :(
}

According to all answers, try/catch + callback is set on a different context but then - how would you explain this code try/catch working?

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();
Related