Why does typescript use “Like” types?

2019-03-22 16:53发布

问题:

Why does typescript have a type and then a "like type"? An example of this is Promise<T> and PromiseLike<T>. What are the differences between these two types? When should I use them? In this case why not just have one Promise type?

回答1:

If you look at the definition files (let's take lib.es6.d.ts) then it's pretty straight forward.

For example the ArrayLike interface:

interface ArrayLike<T> {
    readonly length: number;
    readonly [n: number]: T;
}

is more limited than the Array one:

interface Array<T> {
    length: number;
    toString(): string;
    toLocaleString(): string;
    push(...items: T[]): number;
    pop(): T | undefined;
    concat(...items: T[][]): T[];
    concat(...items: (T | T[])[]): T[];
    join(separator?: string): string;
    reverse(): T[];
    shift(): T | undefined;
    slice(start?: number, end?: number): T[];
    sort(compareFn?: (a: T, b: T) => number): this;
    splice(start: number, deleteCount?: number): T[];
    splice(start: number, deleteCount: number, ...items: T[]): T[];
    unshift(...items: T[]): number;
    indexOf(searchElement: T, fromIndex?: number): number;
    lastIndexOf(searchElement: T, fromIndex?: number): number;

    // lots of other methods such as every, forEach, map, etc

    [n: number]: T;
}

It's good to have the two separated because I might want to have a function like this:

function getSize(arr: Array<any>): number {
    return arr.length;
}

console.log(getSize([1, 2, 3])); // works

But it won't work with this:

function fn() {
    console.log(getSize(arguments)); // error
}

It results with this error:

Argument of type 'IArguments' is not assignable to parameter of type 'any[]'.
Property 'push' is missing in type 'IArguments'.

But both will work if I do this:

function getSize(arr: ArrayLike<any>): number {
    return arr.length;
}

(more on ArrayLike in MDN)

The same with Promise and PromiseLike, if I'm building a library which isn't opinionated about the implementation of the Promise then instead of doing this:

function doSomething(promise: Promise<any>) { ... }

I'll do this:

function doSomething(promise: PromiseLike<any>) { ... }

Then even if the user of my library is using a different implementation (bluebird) it will work just fine.

If you'll notice the definition of Promise is this:

declare var Promise: PromiseConstructor;

Which makes it very specific, other implementations might have different properties, for example a different prototype:

interface PromiseConstructor {
    readonly prototype: Promise<any>;

    ...
}

I guess that the main reason that we have PromiseLike is that several implementations were available before the native one was supported (such as bluebird, Promises/A+, jQuery, and more).
In order for typescript to work with code bases that are using those implementations there must be a type other than Promise, otherwise there would be a lot of contradictions.