#typescript #typescript-generics
Вопрос:
Проблема
Учитывая эти типы:
type SelectAndInclude = {
select: any;
include: any;
};
type HasSelect = {
select: any;
};
type HasInclude = {
include: any;
};
type CheckSelect<T, S, U> = T extends SelectAndInclude
? "Please either choose `select` or `include`"
: T extends HasSelect
? U
: T extends HasInclude
? U
: S;
declare function findMany<T extends {select?: string, include?: string}>(args: T): CheckSelect<T, Promise<1>, Promise<2>>;
Почему следующие функции имеют другой возвращаемый тип?
function wrapperWorking<T extends {select?: string, include?: string}>(args: T){
// correct return type inferred here
return findMany(args);
}
async function wrapperNotWorking<T extends {select?: string, include?: string}>(args: T){
// result is always 1, which is wrong, since args is generic and not known here
// result should probably have the type:
// "Please either choose `select` or `include`" | 1 | 2
const result = await findMany(args);
return resu<
}
Они по сути одинаковы, но их возвращаемый тип отличается.
Фактический и ожидаемый результат можно увидеть здесь:
async function main() {
const isErrorText = await wrapperWorking({select:"foo",include: "bar"});
const is1 = await wrapperWorking({});
const is2 = await wrapperWorking({select: "foo"});
const is2Too = await wrapperWorking({include: "bar"});
const shouldBeErrorTextButIs1 = await wrapperNotWorking({select:"foo",include: "bar"});
const is1Too = await wrapperNotWorking({});
const shouldBe2ButIs1 = await wrapperNotWorking({select: "foo"});
const shouldBe2TButIs1Too = await wrapperNotWorking({include: "bar"});
}
Дополнительный контекст
Я хотел обернуть функцию libary для выполнения некоторых действий до и после ее вызова, например:
async function wrapFindMany<T extends {select?: string, include?: string}>(args: T){
await doSomethingBefore(args); // (e.g. async validation, sanitizing)
const result = await findMany(args);
await doSomethingAfterwards(result); // (e.g. writing to a DB)
return resu<
}
Есть ли способ достичь этого без повторного объявления возвращаемого типа и выполнения хакерских any
/ unknown
приведений?
Комментарии:
1. Не связано с вашим основным вопросом, но то, что вы хотите сделать, — это своего рода сквозная проблема. Взгляните на декораторы методов
2. Можете ли вы украсить существующие классы (импортированные из библиотеки) или даже объекты? Я также думаю, что выполнение чего-то вроде:
@Before((args) => validateArgs(args)) @After((result) => save(result)) findMany.[...]
, нарушает читаемость и не кажется естественным, особенно если это усложняется. Но я вижу ваши замечания, я думаю, это может иметь смысл в других сценариях.3. Я не совсем уверен, но взгляните на фабрики декораторов . Может быть, вы можете обернуть свои сторонние библиотечные функции в свою собственную функцию-оболочку.
4. Соответствует ли этот подход вашим потребностям? Если это так, я с удовольствием напишу ответ; если нет, пожалуйста, уточните неудачный вариант использования. Обратите внимание, что «без повторного объявления возвращаемого типа и выполнения хакерских приведений», вероятно, невозможно… ну, вам не нужен
any
orunknown
, но вам все равно нужны утверждения типа (кстати, термин «приведение» неоднозначен и его лучше избегать, поэтому я говорю «утверждение типа» здесь, поскольку это то, что есть у TS)5. Кроме того, есть ли какая-то причина, по которой вы просто не делаете этого ? Объединение типов обещаний, по-видимому, сопряжено с опасностью, тогда как обещание типов объединения было бы просто отлично. Мне трудно представить, почему можно предпочесть первое последнему. Если вы согласны с переключением, то это было бы моим предложением, потому что оно ведет себя намного лучше. Дайте мне знать.