Behavior of Promise.finally
Recently, we discussed the behavior of Promise.finally
in our development team. Even after reading the official documentation, we weren't unanimous on what will happen in certain cases. In the end, I wrote a couple of test cases to make sure about it.
Even before the argument started, we were all (correctly) convinced that the finally
callback will be invoked both when the underlying promise is resolved or rejected:
test('finally is invoked for a resolved promise', () => {
expect.assertions(1);
let invoked = false;
const promise = Promise.resolve().finally(() => {
invoked = true;
});
return promise.then(() => {
expect(invoked).toBe(true);
});
});
test('finally is invoked for a rejected promise', () => {
expect.assertions(1);
let invoked = false;
const promise = Promise.reject().finally(() => {
invoked = true;
});
return promise.catch(() => {
expect(invoked).toBe(true);
});
});
Successful execution of the callback will not affect the outcome of the underlying promise. Most importantly, a rejected promise will remain rejected even if the finally
callback succeeds (unlike the catch
callback):
test("finally doesn't affect the outcome of a resolved promise", () => {
expect.assertions(1);
const promise = Promise.resolve(42).finally(() => 1);
return expect(promise).resolves.toBe(42);
});
test("finally doesn't affect the outcome of a rejected promise", () => {
expect.assertions(1);
const promise = Promise.reject(42).finally(() => 1);
return expect(promise).rejects.toBe(42);
});
However, if the finally
callback fails for whatever reason (by throwing an unhandled error or returning a rejected promise), it will change the outcome of the underlying promise to be rejected:
test('finally rejects the promise if it throws an unhandled error', () => {
expect.assertions(1);
const promise = Promise.resolve().finally(() => {
throw new Error('Promise.finally failed');
});
return expect(promise).rejects.toMatchObject({
message: 'Promise.finally failed'
});
});
test('finally rejects the promise if it returns a rejected promise', () => {
expect.assertions(1);
const promise = Promise.resolve().finally(() => {
return Promise.reject('Promise.finally failed');
});
return expect(promise).rejects.toBe('Promise.finally failed');
});
I have a suspicion that these tests might come in handy again in the future.