Implementing Promise.finally in JavaScript



What is Promise.finally?

Promise.finally is a method that allows you to specify a callback that will be executed when the promise is settled (either resolved or rejected). The callback does not receive any arguments and the promise’s value or reason will be passed through unchanged.

Real Interview Insights

Interviewers might ask you to:

  • Implement Promise.finally from scratch.
  • Explain the behavior of Promise.finally with both resolved and rejected promises.
  • Discuss how Promise.finally can be used for cleanup tasks in asynchronous operations.

Implementing Promise.finally

Here’s a basic implementation of Promise.finally:

if (!Promise.prototype.finally) {
  Promise.prototype.finally = function(onFinally) {
    const P = this.constructor;
 
    return this.then(
      value => P.resolve(onFinally()).then(() => value),
      reason => P.resolve(onFinally()).then(() => { throw reason; })
    );
  };
}
Explanation:
  • Check for Native Support: Only add the finally method if it is not already present.
  • Callback Execution: Use the then method to execute the onFinally callback when the promise is resolved or rejected.
  • Chaining: Ensure the original promise’s value or reason is passed through after the onFinally callback is executed.

Practical Example

Consider an example with both resolved and rejected promises:

const promise1 = Promise.resolve('Success');
const promise2 = Promise.reject('Error');
 
promise1.finally(() => console.log('Cleanup after success'))
  .then(result => console.log(result)) // Output: 'Cleanup after success', 'Success'
  .catch(error => console.error(error));
 
promise2.finally(() => console.log('Cleanup after error'))
  .then(result => console.log(result))
  .catch(error => console.error(error)); // Output: 'Cleanup after error', 'Error'

In this example:

  • The finally method runs the cleanup code regardless of whether the promise was resolved or rejected.

Advanced Use Case: Logging and Cleanup

Promise.finally is particularly useful for logging and cleanup operations:

function fetchData() {
  return new Promise((resolve, reject) => {
    // Simulate an asynchronous operation
    setTimeout(() => resolve('Fetched data'), 1000);
  });
}
 
fetchData()
  .then(data => console.log(data))
  .catch(error => console.error(error))
  .finally(() => console.log('Operation complete'));

In this scenario:

  • The finally method ensures that "Operation complete" is logged after the promise settles, regardless of the outcome.

Performance Considerations

When using Promise.finally, consider:

  • Execution Time: The finally callback is executed as soon as the promise is settled, which ensures timely cleanup.
  • Side Effects: Ensure that the finally callback does not have side effects that affect the promise chain.

Coding Challenge: Extending Promise.finally with Context

Challenge: Enhance the Promise.finally implementation to pass context information to the onFinally callback.

if (!Promise.prototype.finally) {
  Promise.prototype.finally = function(onFinally) {
    const P = this.constructor;
 
    return this.then(
      value => P.resolve(onFinally('resolved')).then(() => value),
      reason => P.resolve(onFinally('rejected')).then(() => { throw reason; })
    );
  };
}
 
// Example usage with context
const promise = Promise.resolve('Success');
 
promise.finally((context) => console.log(`Cleanup after ${context}`))
  .then(result => console.log(result))
  .catch(error => console.error(error)); // Output: 'Cleanup after resolved', 'Success'

In this challenge:

  • Modify the finally method to pass the context ('resolved' or 'rejected') to the onFinally callback.