Я построил небольшую библиотеку на уровне типов, чтобы упростить написание пар функций вида
function a(...): T | undefined;
function b(...): T;
, где b получается из a путем выдачи исключения, если возвращается undefined. Основная идея c заключается в следующем:
export enum FailMode { CanFail, CanNotFail }
export const canFail = FailMode.CanFail;
export const canNotFail = FailMode.CanNotFail
export type Failure<F extends FailMode> = F extends FailMode.CanFail ? undefined : never;
export type Maybe<T, F extends FailMode> = T | Failure<F>;
export function failure<F extends FailMode>(mode: F): Failure<F> {
if (mode === canFail) return undefined as Failure<F>;
throw new Error("failure");
}
Теперь мы можем объединить a
и b
в одну функцию, которая принимает один дополнительный параметр для различения guish типов:
function upperCaseIfYouCan<F extends FailMode>(x: string | undefined, mode: F): Maybe<string, F> {
if (x === undefined)
return failure<F>(mode);
return x.toUpperCase();
}
// both of these now work
let y: string = upperCaseIfYouCan("foo", canNotFail); // can throw, but will never return undefined
let z: string | undefined = upperCaseIfYouCan("foo", canFail); // cannot throw, but can return undefined
Теперь в большинстве случаев мне нужен вариант canNotFail
, и мне интересно, есть ли способ сделать его «по умолчанию», чтобы мне не нужно было передавать canNotFail
параметр в этом случае, чтобы работало следующее:
let y: string = upperCaseIfYouCan("foo"); // can throw
Я уже определил, что этого нельзя достичь с помощью аргументов по умолчанию, потому что тип аргумента по умолчанию должен быть унифицированным с F
. Есть ли способ добиться этого другим способом, чтобы определение таких функций, как upperCaseIfYouCan
, было таким же простым?