Почему типы типов используют типы "Like"?

Почему машинописный шрифт имеет тип, а затем "похожий тип"? Примером этого может служить Promise<T> и PromiseLike<T>. Каковы различия между этими двумя типами? Когда я должен их использовать? В этом случае, почему бы просто не использовать один тип Promise?

Ответ 1

Если вы посмотрите на файлы определения (пусть возьмите lib.es6.d.ts), то это довольно прямолинейно.

Например, интерфейс ArrayLike:

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

более ограничен, чем массив:

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

Хорошо иметь два разделенных, потому что я мог бы иметь такую функцию:

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

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

Но это не сработает:

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

Это приводит к этой ошибке:

Аргумент типа "IArguments" не присваивается параметру типа "any []".
Свойство "push" отсутствует в типе "IArguments".

Но оба будут работать, если я это сделаю:

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

(больше на ArrayLike в MDN)

То же самое с Promise и PromiseLike, если я создаю библиотеку, которая не усомнилась в реализации Promise вместо этого:

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

Я сделаю это:

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

Тогда даже если пользователь моей библиотеки использует другую реализацию (bluebird), она будет работать нормально.

Если вы заметите, что определение Promise заключается в следующем:

declare var Promise: PromiseConstructor;

Это делает его очень специфичным, другие реализации могут иметь разные свойства, например, другой прототип:

interface PromiseConstructor {
    readonly prototype: Promise<any>;

    ...
}

Я думаю, что главная причина того, что мы имеем PromiseLike что несколько реализаций были доступны прежде, чем нативный один был поддержан (например, Блюберд, Promises/A+, JQuery, и многое другое).
Для того, чтобы машинопись работала с базами кода, использующими эти реализации, должен существовать тип, отличный от Promise, иначе было бы много противоречий.