Rejecting Javascript Promises And Error Handling - javascript

I'm trying to wrap my head around the correct way to indicate a failure within a .then().
If the promise doesn't fail, (I.e. the operation that returns the promise does it' job properly, such as an AJAX request that returns a status 200), but I decide that the result isn't valid, usually I'd do a popup, explaining the issue to the user, and do a "return false;" to exit the method early.
However, with promises, if from within the .then(), I want to do something similar, I've been lead to believe that what I should do is throw an error instead, and presumably let this get caught by the .catch() that I've chained on.
My concern is that I want to distinguish between an operation within a promise that succeeded, but which I don't like the result of, and a failed operation within a promise.
For example, if I perform an AJAX call and it fails with a 404, that's not really recoverable, so it seems appropriate to reject with a popup saying something like "something went wrong".
However, if the AJAX request is successful (returns a status 200), but the response indicates that something isn't right (Like the user didn't fill out a field with a correct value), then I'd like to handle that a certain way, which might involve not just a popup with a message (e.g. maybe DOM manipulations, red text etc, things that I might not want to do if it's a 404).
Below are 2 examples to better explain what I mean.
The first being the original implementation with callbacks and the second being with promises (Wrapping the ajax call with the Q promise library to make it a proper promise).
Callback version:
$.ajax({
url: "/cars/1",
type: "GET",
contentType: "application/json; charset=utf-8",
dataType: "json"
})
.done(function (data) {
if (!data.IsSuccessful) {
//The request was successful (status 200), but the server is returning IsSuccessful=false
alert(data.Message);//message says something like "we have that car in our catalogue but we don't have it in stock"
return false;//early exit from .done()
}
//if it gets here, everything is good and I can do something with the result
})
.fail(function (data) {
//The request actually failed due to a generic status 500 error which has something I don't necessarily want to expose in a popup to the user
alert("Something went wrong");
});
Promise version:
var myPromise = Q(
$.ajax({
url: "/cars/1",
type: "GET",
contentType: "application/json; charset=utf-8",
dataType: "json"
})
);
myPromise.then(function (data) {
if (!data.IsSuccessful) {
throw new Error(data.Message);
}
//all good, lets do something with the result
})
.catch(function (error) {
//what is error?
//How do I know if it's one that I want to show to the user or not?
}).done();
In the promise version, if the request returns a 404 it will end up in the .catch() immediately right?
If data.IsSuccessful==false, then it will also end up in the .catch()?
What if I want to treat both failures differently, how would I go about that?
I'm not calling resolve or reject anywhere, is that problematic?
I'd like to make sure I'm following best practices as much as possible.

TL;DR: You can use rejections for control flow, but in general they are for exceptional cases only
In the promise version, if the request returns a 404 it will end up in the .catch() immediately right?
Yes.
If data.IsSuccessful==false, then it will also end up in the .catch()?
Yes.
I'm not calling resolve or reject anywhere, is that problematic?
Not at all. You're not using the Promise constructor (which you don't need, as you already have a promise for the ajax result).
What if I want to treat both failures differently, how would I go about that?
Throw different kinds of errors so that you can distinguish them. Give them names, add special properties, do subclassing (in ES6 especially), or just look at the message.
with promises, if from within the .then(), I want to do something similar, I've been lead to believe that what I should do is throw an error instead
Not necessarily. You can do exactly the same as you did without promises - put an if-else in the callback (or an if with an early return if you prefer).
In terms of control flow and their result values,
.then(function(res) {
if (!res.isOK) {
// do something
return false;
}
// do something else
}).catch(function(err) {
// handle something
})
and
.then(function(res) {
if (!res.isOK)
throw new MyError(res);
// do something else
}).catch(function(err) {
if (err instanceof MyError) {
// do something
return false;
}
// handle something
})
are pretty much equivalent (except for exceptions in do something and with code other than this throwing MyErrors). The major difference is when you want to chain additional .then(…) invocations between the then and catch. If you don't, just choose whatever you like better.

Related

promise error/exception handling design

I want to create a function that performs async operation and returns a promise. The action is:
downloading external content via AJAX
if the content can't be downloaded (e.g. invalid URL) -> reject
if the content was downloaded successfully, but I can't parse it -> reject
otherwise (content was downloaded and parsed successfully) -> resolve
The code I've got now is pretty sttraightforward:
function fetch(hash){
return $.ajax({
method: "GET",
url: baseURL + "/gists/" + hash
}).then(function(response) {
return JSON.parse(response.files['schema.json'].content);
});
}
it's just a jQuery AJAX call that fetches data from github gist. Currently:
when the AJAX can't be resolved -> promise gets rejected - OK
when the AJAX was resolved and content was parsed correctly -> promise gets resolved - OK
however, if the AJAX was resolved, but the content was invalid, the JSON.parse line throws an error and the promise doesn't get rejected and it should - FAIL
The question is - what is the right design to do that? I could wrap entire thing with a Deferred and handle it manually, but I think there might be an easier solution. Should I do a try-catch? Or something else?
I could wrap entire thing with a Deferred and handle it manually
That sounds like the deferred antipattern. Don't wrap anything that already uses promises.
I think there might be an easier solution. Should I do a try-catch?
Yes:
.then(function(response) {
try {
return JSON.parse(response.files['schema.json'].content);
} catch (e) {
return $.Deferred().reject(e);
}
})
See also Throwing an Error in jQuery's Deferred object.
Or something else?
Actually your original code does work in any reasonable promise implementation, where exceptions become rejections implicitly. It's just jQuery's fail. You might simply want to dodge jQuery's then implementation.
As has already been described, jQuery 1.x and 2.x promises are not compliant with the promise spec and .then() handlers in those version are not "throw-safe". With a proper promise, the exception will be automatically turned into a rejection.
Bergi has already shown how to manually catch the exception and manually turn it into a rejection in jQuery. But, you could also cast the jQuery promise into a real promise and then get that throw-safe behavior for free. Here's one way to do that:
function fetch(hash){
return Promise.resolve($.ajax({
method: "GET",
url: baseURL + "/gists/" + hash
})).then(function(response) {
return JSON.parse(response.files['schema.json'].content);
});
}
Or, perhaps even better, make a new version of $.ajax() that returns a standard promise and then use that:
$.ajaxStd = function() {
return Promise.resolve($.ajax.apply($, arguments));
}
function fetch(hash){
return $.ajaxStd({
method: "GET",
url: baseURL + "/gists/" + hash
}).then(function(response) {
return JSON.parse(response.files['schema.json'].content);
});
}
Both of these options mean that your fetch() function now returns a standard promise, not a jQuery promise so it won't have some of the jQuery specific things on it like .abort(), but it will have standards-compliant promise behavior.
As you clearly mentioned, $.ajax fails only for one of your other cases, hence you can easily handle that case alone using some custom validation. You could possibly include the parse in a try catch block

How to have multiple success callbacks for 1 promise?

I have a function loadItems() that loads something async, and then fails or succeeds. Both fail and success callbacks must do something in the library, and then pass it on to the implementer, which can choose to implement fail, or success, or both, or none.
The problem I now have is if the library implements both the success and fail handler, it will always return a new promise that resolves to success.
This is what I have:
// The library:
function loadItems() {
return store.get('todo').then(function(rsp) {
// Save these locally
items = rsp.value || [];
}, function(rsp) {
// Do nothing, but let the user know
alert(rsp.error);
});
}
store.get('todo') (from yet another library) returns a promise. loadItems() has no control over it.
// The library implementer:
function init() {
loadItems().then(showItems);
}
What I want, and expected to happen:
loadItems() runs the async code from the library (store.get())
It implements success and fail because they have mandatory actions
It passes the success/failure on to the implementer
The implementer only implements success, because it doesn't care about errors, because the library handled it
So it uses .then(onSuccess) to 'add another success callback'
What happens instead (with an error):
The library's failure callback is executed
A new promise is passed on to the implementer
The new promise always resolves with success
The implementer's success callback is fired, with broken result, because the library's success callback didn't fire
Are Promises seriously too cool to have multiple, sync success handlers??
I can imagine even the source library (that defines store.get()) wants to handle the error (for logging sneakily), and then pass success/failure on.
My 'solutions':
Have loadItems()' failure callback throw an error. Unfortunately, that means init() has to catch it (or the browser whines about it). Not cool.
Use a simple callback to talk back to init(), instead of a promise, which only fires after a success. That's not good, because init() might choose to handle the error some day.
I'm sure I'm doing something wrong, but I don't see it.
If you want to have a reject handler to do something based on the error, but want the returned promise to still be rejected, you just rethrow the error (or return a rejected promise) after your reject handling code. This will allow the reject to propagate back through the returned promise.
// The library:
function loadItems() {
return store.get('todo').then(function(rsp) {
// Save these locally
items = rsp.value || [];
}, function(rsp) {
// Do nothing, but let the user know
alert(rsp.error);
// rethrow the error so the returned promise will still be rejected
throw(rsp);
});
}
Supplying a reject handler that does not throw or return a rejected promise tells the promise system that you have "handled" the error and the return value of your reject handler becomes the new fulfilled value of the promise. So, if you want the promise to "stay" rejected, but want to have a handler to do something based on the rejection (logging the rejection is very common), then you have to either rethrow the error in your reject handler or return a rejected promise.
While this may initially seem counter-intuitive, it gives you the most flexibility because you can either completely handle the error and let the returned promise be resolved "successfully" OR you can choose to tell the promise system that you want to propagate an error and you can even choose which error you want that to be (it does not have to be the same error).
The part of your question about multiple success handlers is a bit confusing to me. You can easily have multiple success handlers with any promise:
var p = someFuncThatReturnsSuccessfulPromise();
p.then(someSuccessHandler);
p.then(someOtherSuccessHandler);
If p is a successfully resolved promise, then these two success handlers will both be called in the order they are attached and what happens in someSuccessHandler will have no impact on whether someOtherSuccessHandler is called or not. If the original promise is resolved successfully, then both handlers will always be called.
If you chain your success handlers, then it is a completely different use case. This is completely different:
var p = someFuncThatReturnsSuccessfulPromise();
p.then(someSuccessHandler).then(someOtherSuccessHandler);
Because the second .then() handler is not attached to p, but is attached to p.then(someSuccessHandler) which is a different promise whose outcome is potentially influenced by what happens in someSuccessHandler.
The code in your question is chained. You are returning:
return store.get().then(...)
So, when the caller then chains onto that, the full chain is:
return store.get().then(yourhandler).then(theirhandler)
In this way, yourhandler can influence the outcome that is passed to theirhandler.
In addition to my first recommendation of just rethrowing the error, you could also have done this:
// The library:
function loadItems() {
var p = store.get('todo');
p.then(function(rsp) {
// Save these locally
items = rsp.value || [];
}, function(rsp) {
// Do nothing, but let the user know
alert(rsp.error);
});
return p;
}
Here you ware making sure that your handlers don't affect what is being returned. This can be done if you don't have any async operations in your handlers and you aren't trying to change the resolved value or rejected error of the original store.get() promise. I generally don't recommend this because it is a cause for problems if your handlers are doing other async things or want to influence the return values, but it can also be used in appropriate circumstances.

Can a promise resolver/rejecter trigger its opposite?

If we have a promise like the following, what are the answers to the questions in its comments?
p.then(function ok () {
// Can we get err() to trigger from inside here?
}, function err () {
// Can we get ok() to trigger from inside here?
});
I know that one can attach a new then which can wait for the results or reverse the results of p, but I'm wondering, assuming that p is constant, whether the conditions can call each other recursively also (and without assigning the functions to variables and invoking them by name)...
UPDATE
My use case is as follows.
In IndexedDB, when opening a database, you can listen for the following:
db.onsuccess = ...
db.onerror = ...
db.onblocked = ...
db.js, a library I'm expanding to meet my needs, adds these events with a Promise API, such that a success will resolve the promise, and errors or blocking will reject it.
A common use case for listening for blocking would be to close the database connection which is causing the blocking and IndexedDB will thereupon automatically call onsuccess. The problem is that if we treat onblocked as a rejection, it apparently has no way to retrigger the resolve condition (i.e., the onsuccess). In order to get around this, I can have the blocking be supplied instead as a callback, but I was wondering whether there were any way to do it exclusively using the Promises approach since technically, the error would no longer be an error and would like to give a chance for the original resolve callback to resume with its handling of success.
Can we get err() to trigger from ok?
Can we get ok() to trigger from err?
No. The promises spec mandates that at most one of the two then handlers gets called. If the promise fulfills the first gets called, if the promise rejects the second will, and a promise can't do both things.
So while you could easily create two named functions and call each other manually, it is not programmatically possible to trigger the promise into calling the other.
My use case is as follows […]
What you actually seem to be looking for is
db.open().catch(function(err) {
if (err.isBlocking)
return db.closeAndWaitForNextSuccess(err.blocker); // get another promise
else
throw err;
}).then(function ok(res) {
…
});
Because you're specifying the error handler as a second parameter to .then, what it semantically means is that it will only trigger if the promise "p" got rejected. So the two paths are mutually exclusive. It also means that if inside your "ok" handler you throw an error, it will NOT be caught by the "err" function.
By contrast, if your code used a .catch, it would catch both an error bubbling up from the promise's rejection, and from within the success handler. So if you had this:
p.then(function () {
throw new Error("Within the .then");
}).catch(function (error) {
console.log(error);
});
It would always log the error to the console, regardless of whether p had resolved or rejected.
So for your first question: the ok handler could trigger err function, IF instead of doing .then with two arguments you do .then(successHandler).catch(errorHandler). But for the second question it's "no" no matter what -- there is no logical path from either an error handler or a "catch" to a path that was explicitly skipped due to the rejection.
Update based on updated question description:
Assuming that you get some sort of indicator in the "catch" on why the error occurred (and whether you should really reject or whether you should continue as if it's a success), there's no reason why you couldn't call the same function as you would for success. It's just that it would have two entry points:
p.then(successHandler, failureHandler)
.then( /* other stuff if you want to chain */ )
.catch(function(e) {
// catch-all handler just in case
})
function successHandler(data) {
// Do something. Note that to avoid
// "breaking the promise chain", the code here
// should either be all-synchronous or return another promise.
}
function failureHandler(error) {
if (error.wasDueToBlocking()) {
return successHandler(error.partialData);
} else {
// handle the true error. Again, to avoid
// "breaking the promise chain", the code here
// should either be all-synchronous or return another promise.
}
}
Update 2 based on requirement to not create standalone named function/variables
I'm not entirely sure why you wouldn't want named functions, and the approach I'm about to show is perhaps a little strange. But conceivably you could do this, instead, with the success handler not doing anything other than chaining through to an actual common handler.
p.then(function(data) {
return {goOn: true, data: data};
}, function(error) {
if (error.wasDueToBlocking()) {
return {goOn: true, data: error.partialData};
} else {
// Do some errorHandling logic
return {goOn: false};
}
}
.then(function(passedThroughData) {
if (passedThroughData.goOn) {
// Do something with it.
// Otherwise ignore, since error handler
// already took care of whatever it needed above
}
})
.then(function() {
// whatever you wanted to happen next
})
.catch(function(e) {
// catch-all handler just in case
})

javascript promises onSuccess handler

Is there a handler method, the counterpart of fail, something like success to really get out of that async queue and continue with your normal function calls.
Let me elaborate more. let's say
getConnection()
.then(function(connection){
return self.getRecords() //some async routine, returns promise, does reject/resolve
})
.then(function(data){
return self.getDetail() //some async routine, returns promise, does reject/resolve
})
.then(function(data){ //I'm done here calling onResult, but this onResult may call several
self.onResult(data); //other functions down the road resulting in .fail() call
}) //I want to get out of this promise queue and continue with normal functions calls
.fail(function(info){
self.onFault(info); //any error onFault causes down the road shouldn't be q problem.
})
.done(function(){ //but this gets called all the time at end no matter success or fail
//release system resource like connection etc.
})
I've tried to explain the problem in comments, basically I'm done at self.getDetail() call, once it's success, I want to get out of promise queue, reason, if self.onResult(data) had a problem way afterwards, the .fail() is gonna get triggered too as it's sort of it dependency there.
I tried putting my call in the .done() method but done() gets called no matter what, success or fail.
I've a failed routine that gets called by .fail() function but don't know if there's a success handler.
Any lateral thinking welcome.
Edit - after Barmar's comments, can we do like this. (getConnection returns a promise and does reject/resolve
connections.getConnection(function(c){
return self.getMaster(c) //async routine, returns promise, does reject/resolve
}, function(info){
self.onFault(info) //any failures in getMaster, any error in onFault shouldn't be q business
})
.then(function(data){
return self.getDetail() //async routine, returns promise, does reject/resolve
}), function(info){
self.onFault(info)} //any failures in getDetail, any error in onFault shouldn't be q business
})
.fail(function(info){ //btw any errors, onFault causes down the road shouldn't be q problem- same for above onFault calls
self.onFault(info) //do I need this after above fail routines for each call?
})
.done(function(){ //ok, once everything's done, get out of promise queue
self.onResult(self.data) //any problem onResult causes down the road, should be it's own business
}) //release routine
//two independent functions out of async queue, chain of promises etc. any error in these functions should not affect the chain of promises or call it's fail handler. chain of promises should have been done up there.
onResult: function(data) {
console.log('do something with the data');
}
onFault: function(info) {
console.log('wonder what went wrong');
}
please suggests for the edit above
My Main Main requirement, anything happen after onResult, onFault shouldn't be q library business (fail), they are supposed to handle it on their own now (afterwards)
The first function you pass into a then is in itself a success handler.
The return value of an operation like:
doSomething().then(function() { ... }) is a new promise, which you can always store inside a variable, and then use multiple, independent then calls on:
var promise = someOperation().then(function(x) {
return doSomethingWith(x);
});
promise.then(function(processedX) {
// processedX is the return value of the function used to construct `promise`
// now for something completely different
}
promise.then(someOtherFunction);
You don't need to chain indefinitely, you can always "get out of the promise chain" by storing intermediate new promises into variables and using them somewhere else, possibly multiple times, and create several independent chains.
With that, you can attach a fail handler to one and the same promise in one chain, and have none attached to it in another. In your case, you'd want to store the entire chain up to the handler calling self.onResult in a variable, use the fail handler on that variable, and continue with the rest of the code using the same variable.

When should I reject a promise?

I'm writing some JS code that uses promises. For example, I open a form pop-up and I return a jQuery Deferred object. It works like this:
If the user clicks OK on the form, and it validates, the Deferred resolves to an object representing the form data.
If the user clicks Cancel, then the Deferred resolves to a null.
What I'm trying to decide is should the Deferred instead reject, instead of resolve? More generally, I'm wondering when should I resolve to something like a null object, and when should I reject?
Here's some code demonstrating the two positions:
// Resolve with null.
var promise = form.open()
.done(function (result) {
if (result) {
// Do something with result.
} else {
// Log lack of result.
}
});
// Reject.
var promise = form.open()
.done(function (result) {
// Do something with result.
})
.fail(function () {
// Log lack of result.
});
The semantics of your two strategies are not really the same. Explicitly rejecting a deferred is meaningful.
For instance, $.when() will keep accumulating results as long as the deferred objects it is passed succeed, but will bail out at the first one which fails.
It means that, if we rename your two promises promise1 and promise2 respectively:
$.when(promise1, promise2).then(function() {
// Success...
}, function() {
// Failure...
});
The code above will wait until the second form is closed, even if the first form is canceled, before invoking one of the callbacks passed to then(). The invoked callback (success or failure) will only depend on the result of the second form.
However, that code will not wait for the first form to be closed before invoking the failure callback if the second form is canceled.
Since it's user-controlled, I wouldn't treat it as a "failure". The first option seems cleaner.
Well, in both cases you would do something different, so i would say always either resolve it, or reject it. Do your form post on resolve, and on reject do nothing. Then, on always, close the form.
var promise = form.open()
.done(function (result) {
// Do something with result.
})
.fail(function () {
// Log lack of result.
})
.always(function() {
// close the form.
})
If you aren't rejecting on cancel, when are you ever rejecting at all? at that point, why use a deferred object? You could reject on input error, but then you would have to generate a whole new promise if you wanted to allow them to fix it.
Deferreds don't really seem like the right thing to use here. I'd just use events.

Categories