Reputation: 23
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()
)success
and fail
because they have mandatory actionssuccess
, because it doesn't care about errors, because the library handled it.then(onSuccess)
to 'add another success callback'What happens instead (with an error):
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':
loadItems()
' failure callback throw an error. Unfortunately, that means init()
has to catch it (or the browser whines about it). Not cool.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.
Upvotes: 2
Views: 3094
Reputation: 707198
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.
Upvotes: 3