Search code examples
typescriptes6-promisetypescript-typingsajv

Using Thenable Interfaces with ES6 Promises in TypeScript


Some libraries offer Thenable Interface typings f.e. AJV.
There is something I don't understand about them. Given this minimal code

const foo: Ajv.Thenable<boolean> = new Promise<boolean>((resolve, reject) => {
  if ("condition")
    resolve(true)

  reject("Nope")
})

the TypeScript compiler throws an error which I can't wrap my head around.

error TS2322: Type 'Promise<boolean>' is not assignable to type 'Thenable<boolean>'.
  Types of property 'then' are incompatible.
    Type '<TResult1 = boolean, TResult2 = never>(onfulfilled?: ((value: boolean) => TResult1 | PromiseLike<...' is not assignable to type '<U>(onFulfilled?: ((value: boolean) => U | Thenable<U>) | undefined, onRejected?: ((error: any) =...'.
      Types of parameters 'onfulfilled' and 'onFulfilled' are incompatible.
        Type '((value: boolean) => U | Thenable<U>) | undefined' is not assignable to type '((value: boolean) => U | PromiseLike<U>) | null | undefined'.
          Type '(value: boolean) => U | Thenable<U>' is not assignable to type '((value: boolean) => U | PromiseLike<U>) | null | undefined'.
            Type '(value: boolean) => U | Thenable<U>' is not assignable to type '(value: boolean) => U | PromiseLike<U>'.
              Type 'U | Thenable<U>' is not assignable to type 'U | PromiseLike<U>'.
                Type 'Thenable<U>' is not assignable to type 'U | PromiseLike<U>'.
                  Type 'Thenable<U>' is not assignable to type 'PromiseLike<U>'.
                    Types of property 'then' are incompatible.
                      Type '<U>(onFulfilled?: ((value: U) => U | Thenable<U>) | undefined, onRejected?: ((error: any) => U | ...' is not assignable to type '<TResult1 = U, TResult2 = never>(onfulfilled?: ((value: U) => TResult1 | PromiseLike<TResult1>) |...'.
                        Types of parameters 'onFulfilled' and 'onfulfilled' are incompatible.
                          Type '((value: U) => TResult1 | PromiseLike<TResult1>) | null | undefined' is not assignable to type '((value: U) => TResult2 | Thenable<TResult2>) | undefined'.
                            Type 'null' is not assignable to type '((value: U) => TResult2 | Thenable<TResult2>) | undefined'.

Where exactly does the compiler think a TypeScripts ES6 Promise will return null (if thats the actual error)?
And why do some libraries (bluebird, rsvp, ember, ...) use Thenable instead of Promise/PromiseLike?


Solution

  • Ajv's Thenable type declaration says that the second parameter to then, typically called onRejected, when called, must return the same type <U> as the first onFulfilled parameter. ES6 promises, and thus TypeScript's Promise/PromiseLike, have no such restriction.

    For example, in this code:

    const p1: PromiseLike<boolean> = /* ... */
    const p2 = p1.then(() => true, () => 123)
    

    TypeScript will (correctly) infer that p2 has type PromiseLike<number | boolean>. With Ajv's Thenable type declaration, the equivalent code will fail to compile, because 123 is not assignable to boolean.

    The actual AJV JavaScript code seems to be returning normal Promises, so it doesn't care about types. So this seems like a bug in AJV's TypeScript declarations to me... I don't know why AJV is not using TypeScript's built-in PromiseLike here...