Search code examples
typescriptes6-promise

Typed Promise changes return type to unknown when `finally` is added


I have a regular promise with a statically typed return type:

export const hasActiveSubscription = (whatEver: string): Promise<string> => 
  new Promise((resolve, reject) => {
    if (true){
      return resolve('imastring')
    }
    return reject(new Error('nope'))
  })

So far so good, but if I add a finally block, it changes the return type to unknown and fails to deliver that string type, e.g.

export const hasActiveSubscription = (whatEver: string): Promise<string> => 
  new Promise((resolve, reject) => {
    if (true){
      resolve('imastring')
    }
    reject(new Error('nope'))
  }).finally(console.info)

Type 'Promise' is not assignable to type 'Promise'.
Type 'unknown' is not assignable to type 'string'.

How can I retain the original return type while keeping the finally block?

If it helps, my actual code has a setTimeout (to be sure this function doesn't take too long to return) and I want to clear the timeout on finally, instead of clearing a timeout on 5 different locations.


Solution

  • If you remove type annotation from the return position of the function signature, you will notice that the actual return type is Promise<unknown>, not the Promise<string> as you expected, which constitutes the first part of the error:

    const hasActiveSubscription: (whatEver: string) => Promise<unknown>;
    

    Promise is a generic interface, and its finally method uses the type parameter of the interface in its return type annotation (the sample is from the ES2018 lib):

    interface Promise<T> {
        finally(onfinally?: (() => void) | undefined | null): Promise<T>
    }
    

    All you need to do is to specify the type of the constructed Promise, and everything will be a-ok:

    export const hasActiveSubscription = (whatEver: string) => 
      new Promise<string>((resolve, reject) => {
        if (true){
          resolve('imastring')
        }
        reject(new Error('nope'))
      }).finally(console.info) //no error, Promise<string> is inferred
    

    Playground


    Small note - your example has an unmatched bracket in the reject call