У меня есть объект LazyPromise
с методом all
. Это работает как Promise.all
, но промисы запускают вызов не при инициализации, например:
// It started executing as soon as it was declared
const p1 = Promise.all([Promise.resolve(1), Promise.resolve('foo')])
// It started executing only when `p2.then()` is called
const p2 = LazyPromise.all([() => Promise.resolve(1), () => Promise.resolve('foo')])
это реализация
class LazyPromise {
static all(lazies: Array<() => Promise<any>>){
return Promise.all(lazies.map(lazy => lazy()));
}
}
Применение
const result = LazyPromise.all([() => Promise.resolve(1), () => Promise.resolve('foo')]).then(result => console.info(result))
Я хочу получить результат с правильными типами.
Теперь результат any[]
Я хочу: [number, string]
Как определить правильные типы результатов?
@Bergi all<T>(values: Iterable<T | PromiseLike<T>>): Promise<Awaited<T>[]>;
но это совершенно другая реализация
Вам действительно следует сделать LazyPromise объектом, а не классом, если вы никогда не планируете создавать его экземпляры. Или просто простая функция вместо метода пространства имен.
Это немного сложно. Вам нужен универсальный вариант, но из-за того, как выводятся типы функций, самый очевидный способ сделать это не удается:
class LazyPromise {
static all<T>(lazies: Array<() => Promise<T>>){
return Promise.all(lazies.map(lazy => lazy()));
}
}
const result = LazyPromise.all([
() => Promise.resolve(1),
() => Promise.resolve('foo') // error, expects a number
]).then(result => console.info(result))
Компилятор не может или не будет выводить объединение возвращаемых значений переходников для T
. Вы можете обойти это, объявив вместо этого ограничение функции и извлекая тип возвращаемого значения:
// Little utility helper type
type ExtractPromiseType<T> = T extends Promise<infer R> ? R : never
class LazyPromise {
static all<F extends () => any>(
lazies: Array<F>
): Promise<Array<ExtractPromiseType<ReturnType<F>>>> {
return Promise.all(lazies.map(lazy => lazy()));
}
}
const result = LazyPromise.all([() => Promise.resolve(1), () => Promise.resolve('foo')])
async function foo() {
const res = await result
const first = res[0] // number | string
const second = res[1] // number | string
}
Обратите внимание, что это не рассматривает массив как кортеж, а скорее так, как Typescript обычно обрабатывает гетерогенные массивы, то есть как объединение возможных типов. Сигнатура функции немного сложнее, но она дает компилятору достаточно подсказок, чтобы получить то, что вы хотите.
Если вы сделаете его универсальным в массиве, например. T extends unknown[]
, вместо функции вы можете получить обратно кортеж. Также обратите внимание, что вы можете получить общий тип из обещания с помощью Ожидается.
Наверное, сначала ответить на второй вопрос? Я писал эту утилиту-помощник задолго до версии 4.5, когда в документации говорилось, что они добавили эту встроенную утилиту. Что касается второго, да, это, вероятно, чище.
Отвечу сам после небольшого погружения в определение машинописного текста Promise.all
. Последний трюк заключается в следующем:
class LazyPromise {
all<T>(values: [() => Promise<T>]): Promise<T[]>;
all<T1, T2>(values: [() => Promise<T1>, () => Promise<T2>]): Promise<[T1, T2]>;
all<T1, T2, T3>(values: [() => Promise<T1>, () => Promise<T2>, () => Promise<T3>]): Promise<[T1, T2, T3]>;
all<T1, T2, T3, T4>(values: [() => Promise<T1>, () => Promise<T2>, () => Promise<T3>, () => Promise<T4>]): Promise<[T1, T2, T3, T4]>;
all<T1, T2, T3, T4, T5>(values: [() => Promise<T1>, () => Promise<T2>, () => Promise<T3>, () => Promise<T4>, () => Promise<T5>]): Promise<[T1, T2, T3, T4, T5]>;
async all<T>(values: ReadonlyArray<() => Promise<T>>): Promise<ReadonlyArray<Awaited<T>>> {
return Promise.all(values.map((value) => value()));
}
}
не красиво, но работает
const result = LazyPromise.all([
() => Promise.resolve(1),
() => Promise.resolve('foo')
]) // result is [number, string]
Вы смотрели, как объявляются типы для
Promise.all
?