105

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?

BinaryButterfly
  • 18,137
  • 13
  • 50
  • 91
Stewart
  • 3,023
  • 2
  • 24
  • 40
  • What would you answer if I'd ask you about Arrays and ArrayLike objects and how they differ? – Thomas May 01 '17 at 00:22
  • 1
    Have you looked at the [definition in the lib](https://github.com/Microsoft/TypeScript/blob/master/lib/lib.es6.d.ts#L1323)? – Nitzan Tomer May 01 '17 at 00:25
  • 2
    Thanks for the link. It's good to see the types defined however this question is more focused on the semantic difference between the two types rather than just their interfaces. What the purpose of having these two types defined? Why has the author chosen to do that? I think @Thomas is hinting that there are orthogonal API design connotations here. What are they? An answer that covers some of these bigger design questions is what I'm after. I can update the wording of the question if you like. – Stewart May 01 '17 at 00:31
  • 6
    The reason is that there are other implementations for `Promise` other than the native one (bluebird, etc), and those are `PromiseLike`. The same with arrays, for example the `arguments` is `ArrayLike` but not an `Array`. If you return a `Promise` it should work if the definition says `PromiseLike`. Please add your actual code to the question, you probably have another problem. – Nitzan Tomer May 01 '17 at 00:36
  • @NitzanTomer thanks for your comments. I have updated the question to be more inline with what I should have asked in the first place. – Stewart May 01 '17 at 00:40
  • "In this case why not just have one Promise type" --- how? Would you order people to not implement them anymore? – zerkms May 01 '17 at 00:44
  • `var promiseLike = {then(a,b){ setTimeout(a, 0, "foo"); return this; }}, promise = Promise.resolve(promiseLike); promise.then(console.log); promiseLike.then(console.log);` – Thomas May 01 '17 at 00:52

1 Answers1

107

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.

Nitzan Tomer
  • 155,636
  • 47
  • 315
  • 299
  • 1
    Great answer, but in this special case I'm still confused. Why should be there a problem to have `catch()` in `PromiseLike`? – rekire Jan 02 '18 at 06:27
  • 6
    @rekire I don't know why they chose to include only `then` in `PromiseLike`, but my guess is that some promise implementations do not have `catch` (for example it looks like [Promises/A+](https://promisesaplus.com/) don't). – Nitzan Tomer Jan 02 '18 at 09:01