I'm using the RSVP library distributed inside Ember.js and I'm trying to figure out the correct pattern for reporting fatal errors inside a promise -- particularly I want to inform of something that's almost certainly the result of a programming error due to api misuse and I want to do it in a LOUD way. I'm new to promises and javascript in general, hopefully this question makes sense
Here's a simple example (in coffeescript):
doAsync = (arg, callback) ->
throw new Error('you gave me a way bad arg, I fail for you!')
promiseReturningApi = (data) ->
return new Ember.RSVP.Promise (resolve, reject) ->
callback = (err, resp) ->
if err then reject(err)
else resolve(resp)
doAsync(data, callback)
Now lets say I've identified an error that there's no possible way to recover from which occurred inside doAsync -- I want to make sure this error gets reported even if the caller neglected to attach an error handler because it almost certainly only resulted because the caller invoked the api function in an incorrect way
I came across the idea of using setTimeout within a rejection handler to ensure the error gets raised from somewhere even if the caller doesn't attach an error handler to the promise
failLoud = (err) ->
if err.isProgrammerError
setTimeout () ->
throw err
throw err
promiseReturningApi = (data) ->
promise = new Ember.RSVP.Promise (resolve, reject) ->
callback = (err, resp) ->
if(err) then reject(err)
else resolve(resp)
doAsync(data, callback)
return promise.then(null, failLoud)
Is it considered a reasonable practice to attach such a default error handler to a promise before returning it from my promiseReturningApi? This would allow me to force a stacktrace when the caller does something that can't possibly work -- even though the stacktrace would be a little odd it could make things a bit easier to get started with ...
Even though I called the example promise returning function an 'api' call -- I should add that I'm not writing framework code -- this is rather all within an application. If doAsync were a real-world function, then in my versio of the real-world its pretty likely to be coming from an external party with a new-to-me api -- so it seems pretty likely that I'll misuse it while I'm getting to know it... Meaning I might want to make the pattern something like this
failLoud = (err) ->
if err?.isProgrammerError
setTimeout () ->
throw err
throw err
promiseReturningApi = (data) ->
promise = new Ember.RSVP.Promise (resolve, reject) ->
callback = (err, resp) ->
if(err) reject(err)
resolve(resp)
try
doAsync(data, callback)
catch err
err.isProgrammerError = true
throw err
return promise.then(null, failLoud)
I think what this is doing is forcing an exception to be thrown from somewhere any time that my asynchronous function call invocation itself raises an exception -- such an exception would almost certainly be raised during the argument validation phase of the async call which is most commonly going to be the result of my application code passing in something which doesn't make any sense -- and I want to find out about that as soon as I can. Does this seem like a reasonable pattern to follow to aid in debugging promises used in application code in this context?
Nice question!
You could get away with the
setTimeout
within a dev environment. But for production where you log these errors for reporting for instance, You will want to avoidsetTimeout
as it's non-deterministic. You could end up seeing errors that aren't really accurate, but occurred due to some order in which thesetTimeout
fired.You can do this by moving your checks to the first
then
of the promise, and then returning thatthenable
instead of deferred directly.I prefer to use
Ember.Deferred
instead ofEmber.RSVP.Promise
.Deferred
is a layer overRSVP
that has a nicer API. It avoids needing to nest the whole thing inside a callback toEmber.RSVP.Promise
.Ember.Deferred
providesresolve
andreject
as methods.Throwing an error at any point in the promise/API will automatically reject the promise. So you don't need to catch and rethrow anything.
This allows for 3 different types of responses.
successCallback with response data.
successCallback with error in response.
errorCallback with message.
See this jsbin to try this out.
You could clean this up a bit if the API that you call uses promises instead of callbacks. Then you can just chain the
thens
.