-
-
Save jish/e9bcd75e391a2b21206b to your computer and use it in GitHub Desktop.
// A thing I want to do | |
// This flow only involves **one** promise, for example an ajax call | |
// None of the subsequent `then` or `catch` calls, return new promises. | |
var explode = false; | |
var promise = new Promise(function(resolve, reject) { | |
if (explode) { | |
reject(); | |
} else { | |
resolve(); | |
} | |
}).then(function() { | |
console.log('Thing is done. Do followup task.'); | |
return 'my argument'; | |
}).then(function(arg1) { | |
console.log('Thing is done. Do followup task 2. argument: ' + arg1); | |
}).catch(function() { | |
console.log('Thing failed'); | |
}).then(function() { | |
console.log('Always do this'); | |
}); | |
// => "Thing is done. Do followup task." | |
// => "Thing is done. Do followup task 2. argument: my argument" | |
// => "Always do this" | |
// Set explode to true | |
var explode = true; | |
var promise = new Promise(function(resolve, reject) { | |
if (explode) { | |
reject(); | |
} else { | |
resolve(); | |
} | |
}).then(function() { | |
console.log('Thing is done. Do followup task.'); | |
return 'my argument'; | |
}).then(function(arg1) { | |
console.log('Thing is done. Do followup task 2. argument: ' + arg1); | |
}).catch(function() { | |
console.log('Thing failed'); | |
}).then(function() { | |
console.log('Always do this'); | |
}); | |
// => "Thing failed" | |
// => "Always do this" |
This behavior was for me admittedly somewhat unexpected. I had assumed that a rejection anywhere in the chain stays rejected permanently, so in case of rejection the last then() would never get called. But it turns out that by calling catch() the rejection is considered to have been "handled", so a resolved promise is returned. Thus any then() that is chained after a catch() will always be called.
Actually this makes sense when one thinks of it as being equivalent to synchronous code - everything that comes after a catch block is always executed. Thus as seen in this gist "always" can be simulated via an empty catch followed by a then.
Note that unlike the jQuery Deferred .always(), any additional catch() calls chained after the last then() will not be executed. In this way the gist code is semantically more like "finally" than "always", since it simultaneously ends the error-handling block. If subsequent catch calls should continue to work it would be necessary to throw an error in the above catch function.
In the end, both versions - finally and always - can easily be added to the Promise prototype as follows:
Promise.prototype.finally = function(onResolveOrReject) {
return this.catch(function(reason){
return reason;
}).then(onResolveOrReject);
};
Promise.prototype.always = function(onResolveOrReject) {
return this.then(onResolveOrReject,
function(reason) {
onResolveOrReject(reason);
throw reason;
});
};
The trick is to think of it in terms of try...catch
. So this code:
someActionThatMightFail()
.then(..)
.catch(..)
is effectively equivalent to:
try {
someActionThatMightFail()
// code here is equivalent to `then`
}
catch() {
// code here is equivalent to `catch`
}
Now, when you chain promises after the catch
handler, like this:
someActionThatMightFail()
.then(..)
.catch(..)
.then(someOtherRiskyAction)
.catch(..)
you are effectively creating this pattern:
try {
try {
someActionThatMightFail()
// code here is equivalent to the first `then`
}
catch() {
// code here is equivalent to the first `catch`
}
// code here is equivalent to the second `then`
}
catch() {
// code here is equivalent to the second `catch`
}
I find that when I think of it this way, it makes a lot more sense.
One other thing to keep in mind:
no return statement == return undefined
== return Promise.resolve()
In other words, if your catch handler does not return, it implicitly returns undefined
, which is automatically converted to Promise.resolve()
. To reject, you should either return Promise.reject()
or throw new Error(..)
.
Thanks, y'all. That clarified some things for me.
Thanks for this. Nice and clear.
Top! was looking for exactly this transition away from
.always
your to
.then(function(){}).catch(function(){}).then
is a great solution for me. Thx!
@ArndBrugman
You can avoid .catch Identifier by using:
.then(function(){}, function(){}).then(function(){ /* this will be always */ });
Thanks for taking the time!