DaDo
DaDo

Reputation: 503

Can you return a Promise with a predefined finally?

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.

Upvotes: 5

Views: 978

Answers (4)

Henry Filosa
Henry Filosa

Reputation: 13

There is a solution that involves promises. The caveat is that it will not work if users of the returned promise object perform a similar "hack".

const promise = new Promise((resolve, reject) => resolve());
promise.finally(() => promise.finally(() => console.log("cleanup")));
promise.then(() => console.log("do me first!"));

The concept is that the first callback executed in the promise chain can append whatever it wants to the end of the promise chain. Of course, if the next callback does the same thing you're out of luck, which is the limitation of this approach.

Upvotes: 0

angleKH
angleKH

Reputation: 151

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'));
  

Upvotes: 0

Wilco Bakker
Wilco Bakker

Reputation: 569

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.

Upvotes: 1

marzelin
marzelin

Reputation: 11600

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')
})

Upvotes: 1

Related Questions