Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TypeScript type inference issue

I'm using TypeScript with the MongoDB node.js driver. Note, this is not a Mongo question, its just the particular use case of this issue I'm having.

Pretty much every Mongo call uses a pattern of (arg1, arg2, ..., argn, callback) where callback is a function that accepts (err, res). However, I want to use promises. I'm trying to simplify my usage by writing a helper wrapper function, like so:

function mongoWrap<T>(action: (callback: (err: Error, res: T) => void) => void) : q.IPromise<T>
{
    var promise = q.Promise<T>((resolve, reject) => {
       action((err, res) => {
           if (err) reject(err);
           else resolve(res);
       });
    });

    return promise;
}

This works great, except that for some reason the compiler cannot infer the type of T. I have to make calls such as:

var dbPromise = mongoWrap<mongodb.Db>(cb => mongoClient.connect("mongodb://localhost:27017/database", cb));

If I omit the <mongodb.Db> part, the result is Promise<{}> and I lose type safety. However, the compiler clearly knows that the callback argument to the Mongo call is (err: Error, db: Db) => void.

What can I do to get the compiler to correctly infer the type of T?

like image 459
David Pfeffer Avatar asked Dec 30 '15 14:12

David Pfeffer


People also ask

Does TypeScript have type inference?

TypeScript infers types of variables when there is no explicit information available in the form of type annotations. Types are inferred by TypeScript compiler when: Variables are initialized. Default values are set for parameters.

What does infer mean in TypeScript?

The infer keyword allows types to be extracted from conditions in conditional types. This is often used within Typescript's standard utility types. In the next lesson, we will gain a deep understanding of some of TypeScripts standard conditional utility types.

What would be the best common type inferred by TypeScript?

The best common type algorithm In this case, TypeScript selects the number array type ( number[] ) as the best common type. In this example, TypeScript infers the type for arr to be (RegExp | Date)[] .

How does type inference work?

It involves analyzing a program and then inferring the different types of some or all expressions in that program so that the programmer does not need to explicitly input and define data types every time variables are used in the program.


1 Answers

Typescript is able to infer the type of some generic functions but it has some limitations.

Since there isn't any information in the generic section of the handbook I decided to make some tests and see where it breaks down.

  1. a simple function that takes 1 parameter.
function genericFunction<T>(value: T): T {
    return value;
}

// type of val is Window
let val = genericFunction(window); 

This works, there's no need to specify the type of T manually.

  1. a function with 2 generic parameters.
function genericFunction2<T>(value: T, anotherValue: T) : T {
    return value;
}

// type of val is String
let val = genericFunction2("b", "5"); 

// compilation error type of T can't be inferred from usage
let anotherVal = genericFunction2("b", 5); 

This works, there's no need to specify the type of T manually.

  1. a function that receives a callback and a value.
function callBackAndValue<T>(action: (value: T) => T, value: T): T {
    return action(value);
}

// type of val is string
let val = callBackAndValue((value: string) => value + "5", "abc "); 

This works, there's no need to specify the type of T manually.

  1. a function that receives a callback and a value but returns a promise.
function callBackAndValueWithPromise<T>(action: (value: T) => T, value: T): Promise<T> {
    return new Promise<T>((resolve, reject) => {
        resolve(action(value));
    });
}

// type of val is Promise<string>
let val = callBackAndValueWithPromise((value: string) => value + "5", "abc "); 

This works, there's no need to specify the type of T manually.

  1. a function that receives only a function from T to T
function onlyCallback<T>(action: () => T) : T {
    return action();
}

// type of val is string
let val = onlyCallback(()=> "abc"); 

This works, there's no need to specify the type of T manually.

  1. A function that receives a function from nothing to T that returns a promise.
function onlyCallbackWithPromise<T>(action: () => T): Promise<T> {
    return new Promise<T>((resolve, reject) => { 
        resolve(action());
    });
}

// the type of val is Promise<string>
let val = onlyCallbackWithPromise(()=> "abc"); 

This works, there's no need to specify the type of T manually.

  1. A function that receives the a function that takes a function. The case in the question.
function typeFromCallbackOfCallback<T>(action: (callback: (value: T) => void) => void): Promise<T> {
    return new Promise<T>((resolve, reject) => {
        action((value) => {
            resolve(value);
        });
    });
}

// here the compiler fails to infer the type cb should take as a parameter and it seems to default to object({})
// type of Val is Promise<{}>
let val = typeFromCallbackOfCallback(cb => cb("abc")); 

This no longer works and needs the type to be specified manually.

Since the compiler is limited at the moment I guess you are stuck having to specify the type for this case. That is the solution given in the handbook as well for the cases when type inference fails.

Adding another parameter of type T fixes it, but It doesn't quite match your case.

function lastOne<T>(action: (callback: (value: T) => void) => void, b: T): Promise<T> {
    return new Promise<T>((resolve, reject) => {
        action((value) => {
            resolve(value);
        });
    });
}

// type of var is Promise<string>
let var = lastOne(cb => cb("abc"), "a");

This works, there's no need to specify the type of T manually.

like image 76
toskv Avatar answered Sep 23 '22 13:09

toskv