-
Notifications
You must be signed in to change notification settings - Fork 391
Description
Right now, promise.always() is weird for a few reasons, several of which were discussed in this google group thread. For instance:
- It receives either a fulfilled value, or a rejection reason and can't easily distinguish, except by inspecting the value/reason itself. This makes the function passed to
always()trickier to implement and potentially more error prone. - It is easy to accidentally "handle" a rejection simply by returning. For example, passing the identity function or a function that returns undefined, like
console.logtoalways()will always squelch errors.
One idea would be to try to make always() work more like synchronous finally. Consider the following code:
try {
return doSomething(x);
} catch(e) {
return handleError(e);
} finally {
cleanup();
}Notice that this finally clause does not have access to the return value unless we do some extra work above and capture it in a variable. So, the "default" is for finally NOT to have access to the return value or to the exception. While cleanup can throw an exception, thus transforming an originally-successful return into a failure, it cannot turn a failure into a successful return by not throwing.
Now consider how the current always() implementation works in this code:
var fn = require('when/function');
fn.call(doSomething, x)
.otherwise(handleError)
.always(cleanup);There are two obvious differences:
cleanuphas access to the result or the error.cleanupcan modify the outcome of the operation in a way that it can't above: it can transform a failure into success simply by returning (even if it simply returns its input, i.e. the identity function!).
Here is a proposed version of always that I think behaves more like (but still not exactly like) finally:
Promise.prototype.always = function(callback) {
return this.then(
function(value) {
return when(callback(), function() {
return value;
});
},
function(error) {
return when(callback(), function() {
return when.reject(error)
});
});
)
}Notice that callback is not given access to either the fulfillment value or the rejection reason. It can still turn a success into a failure by throwing, but it cannot turn a failure into a success simply by returning. It also cannot change the ultimate fulfillment value.
I'm becoming a fan of this, but want to see what other folks think.
One potential variant of this would be to pass the value/reason to callback, but still to disallow the case of turning a failure into success. That would look like:
Promise.prototype.always = function(callback) {
return this.then(
function(value) {
return when(callback(value), function() {
return value;
});
},
function(error) {
return when(callback(error), function() {
return when.reject(error)
});
});
)
}Thoughts? Alternatives?