TypeScript: похоже, не может понять использование этого универсального кода для обещания - PullRequest
0 голосов
/ 04 августа 2020

Вот функция, которая принимает обещание и число для тайм-аута и возвращает другое обещание. Если обещание в качестве первого аргумента не разрешается до истечения времени ожидания, обещание, возвращенное функцией, немедленно отклоняется. Или он разрешит значение при разрешении первого обещания.

function resolveOrTimeout<T>(promise: Promise<T>, timeout: number): Promise<T> {
  return new Promise<T>((resolve, reject) => {
    // start the timeout, reject when it triggers
    const task = setTimeout(() => reject("time up!"), timeout);

    promise.then(val => {
      // cancel the timeout
      clearTimeout(task);

      // resolve with the value
      resolve(val);
    });
  });
}
resolveOrTimeout(fetch(""), 3000);

Я понимаю лог c функции и хорошо знаю, как работает Promise в JavaScript. Но я просто не понимаю здесь аннотаций типов, в частности, как здесь используются дженерики. Я понимаю, что универсальные типы параметризуют типы, такие как функции, параметризуют значение, но зачем нам в первую очередь параметризовать типы? А также почему даже при вызове функции не предоставляются переменные типа, такие как T в универсальных шаблонах, компилятор не сообщает об ошибках?

Ответы [ 3 ]

2 голосов
/ 04 августа 2020

Promise не интересен сам по себе, как и пустой массив интересен сам по себе. Это просто заполнитель для конечного значения, поле, которое нужно содержать, и это значение имеет тип. Мы могли бы написать одну и ту же функцию 15 раз для различных типов, которые могут go в поле, или мы можем сказать компилятору, что либо вызывающий объект предоставляет тип, либо может попытаться вывести тип из использования .

Чтобы дать интуицию, давайте напишем функцию, которая возвращает последний элемент массива, сначала в обычном формате JS:

function last(arr) {
  return arr[arr.length - 1];
}

и добавляем типы:

function last<T>(arr: T[]): T {
  return arr[arr.length - 1];
}

Нам все равно какой тип, и мы хотим, чтобы это работало для массивов всех видов:

const x = last(['a', 'b']); // compiler can infer x is a string
const y = last([1, 2]);     // compiler can infer y is a number
const z = last<number>(['a', 3]); // heterogenous array, explicit

Никто не хочет сидеть и записывать перегрузки для всех разных вещей, которые могут go в массиве, так что ... да, обобщения FTW.

1 голос
/ 04 августа 2020

Прежде всего, параметр типа generi c, который вы передаете в Promise, является типом, который он предоставит при разрешении.

Простой пример:

function numberInThreeSeconds(): Promise<number> {
    return new Promise(resolve => {
        setTimeout(() => resolve(123), 3000)
        //                       ^ generic paramter type passed to Promise
    })
}

Итак, учитывая, что ...

«Я понимаю, что универсальные типы параметризуют типы, такие как функции, параметризуют значение, но почему здесь нам вообще нужно параметризовать типы?»

Вы делаете это так, чтобы разрешенное значение resolveOrTimeout имело известный тип.

resolveOrTimeout(numberInThreeSeconds(), 5000)
  .then(val => console.log(val * 2)) // val is known to be of type number

Поскольку известно, что numberInThreeSeconds разрешается в number, тогда использование дженериков позволяет функции возвращать обещание, которое разрешается к тому же типу, что и ее первый аргумент.

«А также, почему даже при вызове функции не предоставляются переменные типа как T в универсальных шаблонах, компилятор не сообщает об ошибках?»

Потому что Функции generi c могут вывести свои общие c параметры на основе использования. В самом простом случае:

function identity<T>(arg: T): T { return arg }
const a = identity('abc') // string
const b = identity(123) // number

Поскольку машинописный текст знает тип аргумента, он может вывести T, каков бы ни был этот тип, а затем он возвращает тот же тип T.

То же самое происходит с resolveOrTimeout<T>.

function resolveOrTimeout<T>(promise: Promise<T>, timeout: number): Promise<T> {}

Аргумент promise: Promise<T> сообщает машинописному тексту, что он может вывести T, требуя, чтобы аргумент был обещанием, а затем заглядывал внутрь этого обещания. чтобы найти его разрешенный тип.

И теперь он также может возвращать Promise<T>, что говорит о том, что эта функция возвращает обещание, которое разрешается к тому же типу, что и аргумент promise.

Дополнительные сведения о том, как работают обобщенные и обобщенные типы, здесь

0 голосов
/ 04 августа 2020

В этом случае generi c T действительно бесполезен. Однако предположим, что мы изменили код на этот:

function resolveOrTimeout<T>(promise: Promise<T>, timeout: T): Promise<T> {
}

При вводе данных компилятор поймет, что выходом этой функции будет обещание типа параметра timout. Это помогает писать код, который является типизированным, но может быть написан более "generi c"

generi c используется, если тип значения не важен для контекста. Например, предположим, что мы создаем функцию, которая помещает параметр generi c в массив. Тип этого параметра нам не понадобится, поскольку мы помещаем его только в массив. С помощью generi c мы можем сообщить машинописному тексту, что эта функция принимает какой-то параметр, и на выходе будет массив того же типа.

function toArray(input: T): T[] {
    return [input];
}

Во время компиляции компилятор определит это . Приведенный ниже код приведет к ошибке, в которой говорится, что toArray возвращает string[], но переменная ожидает numer[]

let someArray: number[] = toArray("Some string")
...