I'm using axios library and using then(), catch() and finally(). Works perfectly in Chrome. However the finally() method does not work in MS Edge. I researched using polyfills or shims and I'm lost. I am not using webpack or transpiling and don't plan to add them. I need to keep this simple. How can I add a polyfill to make sure finally() works in Edge? Thanks!
-
3You might consider looking into this library: https://github.com/es-shims/Promise.prototype.finally – HighHopes Nov 15 '18 at 21:29
1 Answers
This should handle the propagation of the thenable's species
in addition to the behaviors detailed below:
Promise.prototype.finally = Promise.prototype.finally || {
finally (fn) {
const onFinally = callback => Promise.resolve(fn()).then(callback);
return this.then(
result => onFinally(() => result),
reason => onFinally(() => Promise.reject(reason))
);
}
}.finally;
This implementation is based on the documented behavior of finally()
and depends on then()
being compliant to the specification:
A
finally
callback will not receive any argument, since there's no reliable means of determining if the promise was fulfilled or rejected. This use case is for precisely when you do not care about the rejection reason, or the fulfillment value, and so there's no need to provide it.Unlike
Promise.resolve(2).then(() => {}, () => {})
(which will be resolved withundefined
),Promise.resolve(2).finally(() => {})
will be resolved with2
.Similarly, unlike
Promise.reject(3).then(() => {}, () => {})
(which will be fulfilled withundefined
),Promise.reject(3).finally(() => {})
will be rejected with3
.Note: A
throw
(or returning a rejected promise) in thefinally
callback will reject the new promise with the rejection reason specified when callingthrow()
.
And of course a demonstration of equivalent behavior:
const logger = (label, start = Date.now()) => (...values) => {
console.log(label, ...values, `after ${Date.now() - start}ms`);
};
const delay = (value, ms) => new Promise(resolve => {
setTimeout(resolve, ms, value);
});
// run test on native implementation
test('native');
// force Promise to use the polyfill implementation
Promise.prototype.finally = /* Promise.prototype.finally || */ {
finally (fn) {
const onFinally = callback => Promise.resolve(fn()).then(callback);
return this.then(
result => onFinally(() => result),
reason => onFinally(() => Promise.reject(reason))
);
}
}.finally;
// run test on polyfill implementation
test('polyfill');
function test (impl) {
const log = ordinal => state => logger(`${ordinal} ${impl} ${state}`);
const first = log('first');
// test propagation of resolved value
delay(2, 1000)
.finally(first('settled'))
.then(first('fulfilled'), first('rejected'));
const second = log('second');
// test propagation of rejected value
delay(Promise.reject(3), 2000)
.finally(second('settled'))
.then(second('fulfilled'), second('rejected'));
const third = log('third');
// test adoption of resolved promise
delay(4, 3000)
.finally(third('settled'))
.finally(() => delay(6, 500))
.then(third('fulfilled'), third('rejected'));
const fourth = log('fourth');
// test adoption of rejected promise
delay(5, 4000)
.finally(fourth('settled'))
.finally(() => delay(Promise.reject(7), 500))
.then(fourth('fulfilled'), fourth('rejected'));
}
.as-console-wrapper{max-height:100%!important}
Thanks to @Bergi for his input on this answer. Please see his implementation and upvote it as well if you found this post helpful.

- 49,224
- 10
- 102
- 153
-
Does the built-in finally function make any guarantees about being called after all `then()` and `catch()`. If not, this could be risky – Evert Nov 15 '18 at 21:09
-
2@Evert No, it does not, `.finally()` is called in the order that it is located in the chain, like any other promise method. – Bergi Nov 15 '18 at 21:43
-
-
@Bergi I notice that my polyfill is resolving at least one tick sooner. Looking at the shim linked to OP, there seem to be a few steps taken to ensure that the promise species is also propagated. It's hard to draw a line between what's necessary and what's needless overhead though... – Patrick Roberts Nov 15 '18 at 21:47
-
Afaik `finally` does wait for promises returned from the callback. [This](https://stackoverflow.com/a/32362233/1048572) would be a better polyfill. I'm surprised there is none on MDN yet. – Bergi Nov 15 '18 at 21:48
-
Ah, that makes sense. I forgot to check for that. @Bergi Though your linked implementation does not propagate the settled state of the promise in the event that the return value of `cb()` is not then-able. What do you say to that? – Patrick Roberts Nov 15 '18 at 21:48
-
@PatrickRoberts non-thenables are covered for using `Promise.resolve`. I have to admit that the propagation of the original state (`res`) is a bit... non-standard. – Bergi Nov 15 '18 at 21:53
-
Then why does chrome do it, and MDN document it? [This shim](https://github.com/es-shims/Promise.prototype.finally/blob/master/implementation.js#L23) also seems to be [doing it](https://github.com/es-shims/Promise.prototype.finally/blob/master/implementation.js#L34). – Patrick Roberts Nov 15 '18 at 21:55
-
1Thanks Patrick, I just tested your code, works perfectly! I think I was overcomplicating polyfills, I now realize they're fairly simple. – Craig Nov 15 '18 at 22:08
-
1@PatrickRoberts I don't see what's wrong with it. It's essentially the same as yours after the update, except doing the state propagation differently. My shorter approach - returning the original promise as the result - is a bit weird but I'm sure it works. It might take a different amount of promise jobs on the queue than the spec requires, but I ignore that. – Bergi Nov 15 '18 at 22:46
-
@Bergi *facepalm* I was interpreting your reference to `this` incorrectly. I retract my statement, yours is indeed correct, mine is just more explicit about which state it's propagating, while yours just unwraps the original promise again. My apologies. – Patrick Roberts Nov 15 '18 at 22:49
-
1Btw, regarding the species, I think we're doing everything right by returning what the `then` method creates. The only differences would be `Promise.resolve` instead of `this.constructor.resolve` but imo that's irrelevant – Bergi Nov 15 '18 at 22:49