5

I have a function that returns a Promise. When I am done using that Promise in the .then() or .catch() blocks, I always want to execute the same cleanup code. My current setup is this:

const promiseWithFinally = () => {
  return new Promise((resolve, reject) => {
    // resolve or reject at some point
  }).finally(() => console.log('finally done'))
}

promiseWithFinally()
  .then(() => console.log('then done'))
  .catch(() => console.log('catch done'))

What I want to happen is that either then done or catch done get logged first and then finally done. However it seems to get executed in the exact opposite order - when I resolve the Promise after a timeout of 5 seconds finally done gets logged first after 5 seconds and then then done immediately afterwards.

What am I doing wrong or is it possible to do this in general? I know I could just append the .finally() to each individual function call, but since it's always the same I'd like to put it in the function definition.

DaDo
  • 443
  • 1
  • 6
  • 20

3 Answers3

1

No it's not possible. Finally is for cleaning up after a given promise, not for its then or catch methods.

What you can do is pass then and catch methods to the function which will be appended before finally:

const promiseWithFinally = (chain) => {
  return new Promise((resolve, reject) => {
    // resolve or reject at some point
    setTimeout(resolve, 1000);
  }).then(chain.then, chain.catch).finally(() => console.log('finally done'))
}

promiseWithFinally({
  then: () => console.log('then done'),
  catch: () => console.log('catch done')
})
marzelin
  • 10,790
  • 2
  • 30
  • 49
1

Short answer

No it is not possible as you cannot rely on when finally is being ran.

Longer answer and possible solution

Code

const cleanupFunc = () => {
  console.log('Cleaning up.');
  };

const someAsyncMethod = (thenFunc, catchFunc) => {
  new Promise(
    (resolve, reject) => {
      setTimeout(() => resolve(), 5000);
    },
  )
    .then((...args) => {
      try {
        thenFunc(...args);
      } catch (err) {
      }
      cleanupFunc();
    })
    .catch((...args) => {
      try {
        catchFunc(...args);
      } catch (err) {
      }
      cleanupFunc();
    });
};

someAsyncMethod((result) => console.log('Then done'), (err) => console.log('Catch done'));

Explanation

Al though it is not possible to rely on finally, what you can do is write a function that needs to do some asynchronous operation returning a promise. In my example this operation is waiting on a 5 second timeout but his can also, for example, be an asynchronous api call that returns a promise.

The next step would be to add a then and a catch call to the promise the asynchronous operation returns that both begin with a try clause in which you call the callback parameter that belongs to the type of resolve (thenFunc for then, catchFunc for catch) followed by a catch which doesn't do anything and ends by calling the cleanup function. in this way you are certain that whatever happens during running the then or catch callback, the cleanup function is being called.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Wilco Bakker
  • 527
  • 1
  • 7
  • 18
0

Assuming you know the rest of the handlers to that promise are going to be attached synchronously, and all actions in the handler are synchronous, it is possible, albeit a little bit hacky.

Simply have the finally handler re-attach itself at the end:

const promiseWithFinally = () => {
  const thisPromise = new Promise((resolve, reject) => {
    // Rejection example
    setTimeout(reject, 200);
  }).finally(() => {
    setTimeout(() => {
      thisPromise.finally(() => console.log('finally done')).catch(() => {});
    }, 0);
  });
  return thisPromise;
};

promiseWithFinally()
  .then(() => console.log('then done'))
  .catch(() => console.log('catch done'));
  
angleKH
  • 123
  • 2
  • 7