【问题标题】:TypeScript type inference issueTypeScript 类型推断问题
【发布时间】:2016-04-04 12:54:57
【问题描述】:

我将 TypeScript 与 MongoDB node.js 驱动程序一起使用。请注意,这不是 Mongo 问题,它只是我遇到的这个问题的特定用例。

几乎每个 Mongo 调用都使用(arg1, arg2, ..., argn, callback) 的模式,其中callback 是一个接受(err, res) 的函数。但是,我想使用承诺。我试图通过编写一个辅助包装函数来简化我的使用,如下所示:

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;
}

这很好用,除了由于某种原因编译器无法推断T 的类型。我必须拨打以下电话:

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

如果我省略了 &lt;mongodb.Db&gt; 部分,结果是 Promise&lt;{}&gt; 并且我失去了类型安全性。但是,编译器清楚地知道 Mongo 调用的 callback 参数是 (err: Error, db: Db) =&gt; void

如何让编译器正确推断T 的类型?

【问题讨论】:

    标签: typescript type-inference type-safety


    【解决方案1】:

    Typescript 能够推断一些泛型函数的类型,但它有一些限制。

    由于generic section of the handbook 中没有任何信息,我决定进行一些测试,看看它在哪里崩溃。

    1. 一个带有 1 个参数的简单函数。
    function genericFunction<T>(value: T): T {
        return value;
    }
    
    // type of val is Window
    let val = genericFunction(window); 
    

    这样可以,不需要手动指定T的类型。

    1. 具有 2 个通用参数的函数。
    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); 
    

    这样可以,不需要手动指定T的类型。

    1. 接收回调和值的函数。
    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 "); 
    

    这样可以,不需要手动指定T的类型。

    1. 接收回调和值但返回承诺的函数。
    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 "); 
    

    这样可以,不需要手动指定T的类型。

    1. 只接收从 T 到 T 的函数的函数
    function onlyCallback<T>(action: () => T) : T {
        return action();
    }
    
    // type of val is string
    let val = onlyCallback(()=> "abc"); 
    

    这样可以,不需要手动指定T的类型。

    1. 一个从无到有的函数接收一个返回承诺的 T 的函数。
    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"); 
    

    这行得通,无需手动指定 T 的类型。

    1. 一个函数接收一个函数,该函数接受一个函数。问题中的案例。
    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")); 
    

    这不再有效,需要手动指定类型。

    由于目前编译器受到限制,我猜你不得不为这种情况指定类型。这也是手册中给出的解决方案,也适用于类型推断失败的情况。

    添加另一个 T 类型的参数可以修复它,但它与您的情况不太匹配。

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

    这行得通,无需手动指定 T 的类型。

    【讨论】:

    • 为什么没有一种方法可以确定 T 的类型? args 中只有一种用法。
    • FWIW 我来自 C#,通过类型推断可以正常工作。这不是唯一性问题。
    • 遗憾的是 TypeScript 中的类型推断不如 C# 先进,目前它需要您指定泛型函数的类型。关于唯一性,我试图说它使编译器更容易确定类型(如果您指定它,它实际上并不需要)。回头看那句话虽然措辞不是很好,但我会改变它。 :)
    • 看起来TypeScript毕竟有一些类型推断,但它似乎停止在从作为接收的函数的回调参数确定T类型的水平上完全停止工作范围。 :(
    • 只是为了完成 C# 也不能从回调中推断出它自己的类型。它至少会产生一个编译错误,并要求您手动指定泛型类型。至少如果我在这里写的是正确的。 goo.gl/nEVXo0
    【解决方案2】:

    我错了,这在 C# 中也不起作用。下面是来自 TypeScript 的相同代码示例,用 C# 编写,但不起作用:

    using System.IO;
    using System;
    using System.Threading.Tasks;
    
    class Program
    {
        static void Main()
        {
            Console.WriteLine("Hello, World!");
            var task = Wrapper(cb => FakeMongoFunctionWithCallback(5, cb));
        }
    
        static void FakeMongoFunctionWithCallback(int arg, Action<Exception, int> callback)
        {
            // just pass through the arg into the callback, pretending we went to a db and came back with that result
            callback(null, arg);
        }
    
        static Task<T> Wrapper<T>(Action<Action<Exception, T>> action) {
            var tcs = new TaskCompletionSource<T>();
            action((err, res) => {
                if (err != null) tcs.SetException(err);
                else tcs.SetResult(res);
            });
    
            return tcs.Task;
        } 
    }
    

    【讨论】:

    • 它太深了,编译器无法获取它。 :(
    猜你喜欢
    • 2018-07-03
    • 2017-08-24
    • 2019-09-17
    • 1970-01-01
    • 1970-01-01
    • 2020-10-14
    • 1970-01-01
    • 2023-03-17
    • 1970-01-01
    相关资源
    最近更新 更多