Типирование с обещанием фиксированных результатов

Я пытаюсь написать функцию, которая принимает массив 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.

Я не уверен, что ты сможешь. Что-то вроде tsplay.dev/WP2DYN даст вам [number, string, never] (поскольку Promise.reject является Promise<never>), но при реальном использовании вы не будете знать заранее, разрешится данная запись или нет, поэтому вам придется сделать их все T | Error.

jonrsharpe 23.05.2024 16:54
вот как бы ко всем им добавить |Error
Tadhg McDonald-Jensen 25.05.2024 07:49
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой Zod и раскрыть некоторые ее особенности, например, возможности валидации и трансформации данных, а также...
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Мне нравится библиотека Mantine Component , но заставить ее работать без проблем с Remix бывает непросто.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
TypeScript против JavaScript
TypeScript против JavaScript
TypeScript vs JavaScript - в чем различия и какой из них выбрать?
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Не все нужно хранить на стороне сервера. Иногда все, что вам нужно, это постоянное хранилище на стороне клиента для хранения уникальных для клиента...
Что такое ленивая загрузка в Angular и как ее применять
Что такое ленивая загрузка в Angular и как ее применять
Ленивая загрузка - это техника, используемая в Angular для повышения производительности приложения путем загрузки модулей только тогда, когда они...
0
2
72
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я хочу сказать, что причина, по которой это не работает так, как вы ожидаете, связана с ограничениями машинописного текста относительно того, как проверяются обобщенные типы и типы объединения. В частности, поскольку 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] или указать мне на документ об этом синтаксисе? Спасибо!

jroy 24.05.2024 17:11
[...T] функционально идентичен просто T, но имеет немного другое общее разрешение, на которое, к сожалению, у меня нет официальной ссылки. Просто предыдущий опыт вот что работает
Tadhg McDonald-Jensen 25.05.2024 07:43

Также посмотрите пример jonrsharpe, опубликованный в качестве комментария, и модифицированную версию, которую я только что прокомментировал ниже, соответствующая документация для отображаемых типов кортежей находится здесь: typescriptlang.org/docs/handbook/release-notes/…

Tadhg McDonald-Jensen 25.05.2024 07:55

Другие вопросы по теме