承诺通用任务

时间:2014-04-09 11:53:44

标签: javascript typescript promise

请参阅代码here(也在本文末尾列出)

我不确定为什么get函数没有强类型。

定义为get:(id:string) => IPromise<Test>;

但是,我将其指定为

    constructor(srv:Store<TestData>){
        this.get = (id) => this.getImp(srv, id);
    }

    private getImp(srv:Store<TestData>, d:string) {
        return srv.get("/test")
            .then( (d) => { //d == IPromise<getImp>
                var rep = d.reply; //rep == TestData
                //var rep:string = "" gives error
                return rep;
            });
    }

getImp返回带有类型参数TestData的prmise(get结果)。但这不应与Test兼容,因此与get兼容。为什么没有错误?

如果我将getImp的签名更改为

,则没有错误

private getImp(srv:Store<TestData>, d:string):IPromise<TestData> {

如何将getImp(srv:Store<TestData>, d:string):IPromise<TestData>分配给get:(id:string) => IPromise<Test>

IPromise的定义有什么问题吗?

感谢。

完整来源

export interface IGetResult<T> {
    reply: T
}

export class Store<T>{
    get:(r:any) => IPromise<IGetResult<T>>;
}

 export interface TestData {
    Id:         string
    Name    :   string
}
export interface ITest extends TestData{
    test():void
}

export class Test implements ITest {
    Id:string;
    Name:string;
    test(){}
}
export class service{
    get:(id:string) => IPromise<Test>;

    constructor(srv:Store<TestData>){
        this.get = (id) => this.getImp(srv, id);
    }

    private getImp(srv:Store<TestData>, d:string):IPromise<TestData> {
        return srv.get("/test")
            .then( (d) => { //d == IPromise<TestData>
                var rep = d.reply; //rep == TestData
                //var rep:string = "" gives error
                return rep;
            });
    }
}


 //==========  angular.d.ts  ===========
 // from https://github.com/borisyankov/DefinitelyTyped/blob/master/angularjs/angular.d.ts

  export interface IRequestConfig {
        method: string;
        url: string;
        params?: any;

        // XXX it has it's own structure...  perhaps we should define it in the future
        headers?: any;

        cache?: any;
        withCredentials?: boolean;

        // These accept multiple types, so let's define them as any
        data?: any;
        transformRequest?: any;
        transformResponse?: any;
        timeout?: any; // number | promise
    }

 export   interface IHttpPromiseCallback<T> {
        (data: T, status: number, headers: (headerName: string) => string, config: IRequestConfig): void;
    }


export interface IHttpPromiseCallbackArg<T> {
        data?: T;
        status?: number;
        headers?: (headerName: string) => string;
        config?: IRequestConfig;
    }

export interface IHttpPromise<T> extends IPromise<T> {
        success(callback: IHttpPromiseCallback<T>): IHttpPromise<T>;
        error(callback: IHttpPromiseCallback<T>): IHttpPromise<T>;
        then<TResult>(successCallback: (response: IHttpPromiseCallbackArg<T>) => TResult, errorCallback?: (response: IHttpPromiseCallbackArg<T>) => any): IPromise<TResult>;
        then<TResult>(successCallback: (response: IHttpPromiseCallbackArg<T>) => IPromise<TResult>, errorCallback?: (response: IHttpPromiseCallbackArg<T>) => any): IPromise<TResult>;
    }

    export interface IPromise<T> {
        then<TResult>(successCallback: (promiseValue: T) => IHttpPromise<TResult>, errorCallback?: (reason: any) => any, notifyCallback?: (state: any) => any): IPromise<TResult>;
        then<TResult>(successCallback: (promiseValue: T) => IPromise<TResult>, errorCallback?: (reason: any) => any, notifyCallback?: (state: any) => any): IPromise<TResult>;
        then<TResult>(successCallback: (promiseValue: T) => TResult, errorCallback?: (reason: any) => TResult, notifyCallback?: (state: any) => any): IPromise<TResult>;


        catch<TResult>(onRejected: (reason: any) => IHttpPromise<TResult>): IPromise<TResult>;
        catch<TResult>(onRejected: (reason: any) => IPromise<TResult>): IPromise<TResult>;
        catch<TResult>(onRejected: (reason: any) => TResult): IPromise<TResult>;

        finally<TResult>(finallyCallback: ()=>any):IPromise<TResult>;
}

1 个答案:

答案 0 :(得分:4)

TypeScript中的泛型具有不同的设计。在某些情况下,他们是双变量WRT他们的类型参数,并且你期望他们在这种特殊情况下是协变的。承诺是不变的,所以它们确实应该与他们持有的类型一致。

要看到它,改变

export interface ITest extends TestData

export interface ITest

然后从Name:string;中移除属性class Test。如果你这样做,那么打字稿最终会抱怨它“无法将IPromise<TestData>转换为IPromise<Test>”,因为Test和[{1}}都不能导出TestData来自TestData

更直接地说明了这个错误:

Test

只要Test继承自TestData TestData继承自Test,这仍然有效。


术语协变和双变量的解释

泛型类型是协变WRT的类型参数,如果它的层次结构与类型参数的层次结构一起变化。例如:

如果

  • var x: IPromise<Test> = <IPromise<TestData>><any>{} 协变 WRT类型Array<T>
  • TExtendedItem
  • 的子类型

然后

  • Item被接受为Array<ExtendedItem>
  • 的子类型

如果子类型是双向的,那么泛型类型是双变量WRT的类型参数:

如果

  • Array<Item> bivariant WRT类型Array<T>
  • TExtendedItem
  • 的子类型

然后

  • Item被接受为Array<ExtendedItem>
  • 的子类型
  • Array<Item>被接受为Array<Item>
  • 的子类型

最后注意事项:为了保证类型安全,可变数组实际上应该不变 WRT到参数。许多语言(如C#或Java)使其协变 - 但在这种情况下,它可以Array<ExtendedItem>成为push(item:OtherExtendedItem);数组而不会在编译时被捕获为错误。


编辑:“有时候是双变的”是什么意思呢?这意味着typescript实际上没有任何硬编码的方差规则。当遇到泛型类型的特化时,它只应用该类型参数。执行此操作后,它会对结果类型执行结构检查 - 如果匹配,则一切正常。请考虑以下示例:

ExtendedItem

编译器认为此代码有效。该类型是否专门用于T = AB或T = ABC不会改变结果类型的结构,这只是{x:number}

如果我们在Box中有一个返回Box的方法会怎样?让我们添加该方法,并参见:

class Box<T>{   
    public x: number;
    constructor(t:T) {
        this.x = 1;         
    }
}
interface AB  { a: number; b: number; }
interface ABC { a: number; b: number; c: number; }

var ab:Box<AB> = new Box({a: 1, b: 2});

var y:Box<ABC> = ab;

同样,如果我们应用专门化,我们会得到一个无限的递归结构:

getThis():Box<T> { return this; }

无论专业化程度如何,最终都是相同的。

但是,如果我们添加另一种方法,那就是返回类型为T的值:

{x: number, getThis: () => {x : number, getThis: () => ... }}

然后,如果我们应用专门化T = AB,则结果如下:

getValue():T { return <T>{}; }

与专业化T = ABC

不兼容
{x: number; y: () => { a: number; b number; } }

并且编译器最终会抱怨。

此时,您可能会注意到{x: number; y: () => { a: number; b: number; c: number; } } IPromise<T>不太相似。实际上,它没有返回类型T值的方法,但它有一些方法(如Box<T>then),它们采用类型为T的值的回调。让我们添加一个类似的方法到盒子

catch

现在让我们应用类型专精化then<U>(f: (val: T) => Box<U>): Box<U>;

T = AB

{x: number; then: (val: {a: number; b: number;}) => Box<{x: number; then: ...}}

T = ABC

这些不应该兼容!

这导致我们在TypeScript中遇到真正的问题:函数类型(“callbacks”)的参数是双变量WRT它们的输入参数,它们应该是逆变的。

这是一个演示问题的简单示例:

{x: number; then: (val: {a: number; b: number; c: number;}) => Box<{x: number; then: ...}}

在此示例中,采用类型IA的参数的回调无法安全地替换为采用IB类型参数的回调(当IB扩展IA时)。与示例中一样,采用IB类型参数的回调可能会尝试调用方法foo(),这意味着将类型IA的参数传递给该回调将导致类型错误


对您的问题最快的解决方法是找到一个返回类型为T的值的IPromise成员:

interface IA { bar(): void }
interface IB { bar(): void; foo(): void; }

function fn(passedFn: (a: IA) => void) {
  var a:IA = {bar: function(){}}; 
  passedFn(a); 
}

// compiles but throws TypeError at runtime!
fn((b: IB) => b.foo());

当然这会使接口与其他promise类型(或库)不兼容,所以这里真正的解决方法是让TypeScript纠正回调的行为。