Exception handling and jQuery promises

I’ve been using jQuery promises for a while now. They are very powerful and most of the time allow for writing asynchronous code that is easy to understand. However recently I stumbled upon promises related problem that took me over half an hour to debug and to understand so I decided to share my findings.

Please take a look at following code:

function loadDataAsync( params ) {
var deferred = $.Deferred();
    $.getJSON( ‘/api.php’, params, function( response ) {
try {
deferred.resolve( response.data.items );
} catch ( e ) {
deferred.reject();
}
} );
    return deferred.promise();
}

Call to “resolve” method is wrapped in a “try…catch” statement because it is not guaranteed that response from the api.php is going to contain “.data.items”. I was planning to take care of it later with proper error handling.

Now let’s take a look at this code:

loadDataAsync()
.then( function( items ) {
console.log( 'items', items );
callToNotExistingMethod();
} )
.fail( function ( err ) {
console.log( 'err', err );
} );

After running this code (assuming that “.data.items” exists) I was expecting in the JavaScript console to see items being printed and then an exception being thrown because of the call to not existing method. However I didn’t see the exception and I wasn’t quite sure why.

As it turned out exception was thrown in the callback and it was caught inside the loadDataAsync in the “try…catch” statement around the call to resolve and it makes perfect sense when you realize how the call stack look like:

* loadDataAsync
** $.getJSON
*** anonymous (ajax success callback)
**** resolve <- this is wrapped in try...catch
***** anonymous (loadDataAsync then callback) <- this throws an exception

So there are two interesting things to notice:

  • call to our callback is caused by the call to resolve method — hence it appears it the same call stack
  • calling reject on already resolved promise does not cause fail callback to be called (so we didn’t see error in the console)

Well, this is definitely not a rocket science — it’s just the nature of asynchronous programming, which sometimes is way less intuitive and harder to debug than synchronous programming.