Я пытаюсь написать функцию, которая принимает массив PromiseSettledResult (тип , возвращаемый Promise.allSettled ) и возвращает массив выполненных значений или причин отклонения:
function unwrapResults<T>(results: ReadonlyArray<PromiseSettledResult<T>>): Array<T> {
return results.map((result) => result.status === "fulfilled" ? result.value : result.reason);
}
Это работает в следующем примере:
const results = await Promise.allSettled([
Promise.resolve(1),
Promise.resolve("ok"),
Promise.reject(new Error("error"))
]);
const unwrappedResults = unwrapResults<number | string | Error>(results);
Проблема в том, что:
unwrapResults.unwrappedResults будут типа number | string | Error.Есть ли способ улучшить это? В идеале я бы ожидал, что при вызове функции будет меньше ввода вручную, и unwrappedResults[0] будет number, unwrappedResults[1] будет string, а unwrappedResults[2] будет Error.






Я хочу сказать, что причина, по которой это не работает так, как вы ожидаете, связана с ограничениями машинописного текста относительно того, как проверяются обобщенные типы и типы объединения. В частности, поскольку PromiseSettledResult<string | number> не идентично PromiseSettledResult<string> | PromiseSettledResult<number>, объединения в конечном итоге имеют немного другую структуру, поскольку они представлены машинописным текстом, но оба поля .value в конечном итоге будут string | number
Итак, чтобы заставить это работать, нам придется переместить наши дженерики на уровень выше и представить тип PromiseSettledResult, а затем извлечь поле .value из дженерика, используя условный тип. примерно так:
type UnwrappedSettled<T extends PromiseSettledResult<any>> = T extends PromiseFulfilledResult<infer V> ? V : Error
function unwrapOneResult<T extends PromiseSettledResult<unknown>>(result: T): UnwrappedSettled<T>{
return result.status === "fulfilled" ? result.value : result.reason
}
async function test(){
const results = await Promise.allSettled([
Promise.resolve(1),
Promise.resolve("ok"),
Promise.reject(new Error("error"))
]);
const unwrapped = results.map(unwrapOneResult)
// ^? const unwrapped: (string | number | Error)[]
}
обратите внимание, что необходимая часть для правильной работы - это то, что T extends PromiseFufilledResult<infer V> будет применяться к каждому элементу в объединении T индивидуально, а затем возвращается объединение результата, так мы обходим часть, которая PromiseFulfilledResult<string> | PromiseFulfilledResult<number> действовала по-другому, вы можно представить это как map для объединений типов.
Еще одна техническая особенность, благодаря которой эта работа работает, заключается в том, что в javascript причиной отклонения технически может быть any. Чтобы избежать полного разрушения нашей типовой безопасности, мы явно говорим, что тип отклоненного результата находится Error в другой ветви этого условного типа. Технически Promise.reject(new Error("error")) не сохраняет никакой информации о типе reason, поэтому обработка его по своей сути не должна быть типобезопасной. :'(
Итак, написание вашего unwrapResultsаналогично выполнимо с сопоставленным типом в качестве результата, поэтому он пытается сохранить кортежи, когда это возможно.
function unwrapResults<T extends Array<PromiseSettledResult<any>>>(results: [...T]): {[K in keyof T]: UnwrappedSettled<T[K]>} {
// @ts-expect-error the following isn't assignable to the mapped type, see https://github.com/microsoft/TypeScript/issues/29841#issuecomment-619505904 for why this isn't allowed in the general case.
return results.map(unwrapOneResult)
}
(В этой версии unwrapOneResult можно вставить)
Потрясающий! Я очень благодарен за ваши объяснения и предложенное решение, но боюсь, сколько мне еще предстоит узнать! Не могли бы вы рассказать немного о типе параметра [...T] или указать мне на документ об этом синтаксисе? Спасибо!
[...T] функционально идентичен просто T, но имеет немного другое общее разрешение, на которое, к сожалению, у меня нет официальной ссылки. Просто предыдущий опыт вот что работаетТакже посмотрите пример jonrsharpe, опубликованный в качестве комментария, и модифицированную версию, которую я только что прокомментировал ниже, соответствующая документация для отображаемых типов кортежей находится здесь: typescriptlang.org/docs/handbook/release-notes/…
Я не уверен, что ты сможешь. Что-то вроде tsplay.dev/WP2DYN даст вам
[number, string, never](посколькуPromise.rejectявляетсяPromise<never>), но при реальном использовании вы не будете знать заранее, разрешится данная запись или нет, поэтому вам придется сделать их всеT | Error.