英文:
Type a function that, given another function, returns a similar function as the given function without 1 argument
问题
I'm here to help with the translation. Here's the translated code:
我想要创建一个名为`outerFn`的函数,该函数以一个名为`innerFn`的函数作为参数。`innerFn`函数接受一个对象类型的参数,而且该对象将需要具有一个`user`参数。然后,`outerFn`应该返回一个名为`returnFn`的函数,其类型应该与`innerFn`类似,但其参数对象不应该要求`user`参数(但仍然需要所有其他参数)。
我希望这样做是为了让我的`outerFn`能够返回一个`returnFn`,该函数实际上调用`innerFn`,但自动注入`user`(即,无需我发送`user`)。
例如:
```typescript
type User = { user: { id: number, name: string } };
type Place = { Place: string };
type UserAndPlace = User & Place;
type InnerFn1Resolve = { something: string };
type InnerFn1 = (args: UserAndPlace) => Promise<InnerFn1Resolve>;
const innerFn1: InnerFn1 = async (args) => Promise.resolve({ something: 'yes' });
type ReturnFn1 = (args: Place) => Promise<InnerFn1Resolve>;
const returnFn1: ReturnFn1 = outerFn(innerFn1);
type Job = { job: number };
type UserAndJob = User & Job;
type InnerFn2Resolve = { else: number };
type InnerFn2 = (args: UserAndJob) => Promise<InnerFn2Resolve>;
const innerFn2: InnerFn2 = async (args) => Promise.resolve({ else: 1 });
type ReturnFn2 = (args: Job) => Promise<InnerFn2Resolve>;
const returnFn2: ReturnFn2 = outerFn(innerFn2);
我尝试了以下outerFn
的实现。上述类型是正确的,但outerFn
的返回部分出现了问题。
type ArgsWithUser = Record<string, unknown> & User;
type InnerFn<Args extends ArgsWithUser, Response> = (args: Args) => Promise<Response>
type ReturnFn<Args extends ArgsWithUser, Response> = (args: Omit<Args, 'user'>) => Promise<Response>;
const outerFn = <Args extends ArgsWithUser, Response>(innerFn: InnerFn<Args, Response>): ReturnFn<Args, Response> => {
const user: User['user'] = {
id: 1,
name: 'Something',
};
return async (argsWithoutUser) => innerFn({ ...argsWithoutUser, user });
};
我得到了以下关于innerFn
参数(即{ ...argsWithoutUser, user }
)的错误。
参数类型
Omit<Args, "user"> & { user: User; }
不能分配给类型Args
。
Omit<Args, "user"> & { user: User; }
可以分配给类型Args
的约束条件,但Args
可以用不同的子类型来实例化。ts(2345)
我真的不理解这个错误。我明白它试图表明返回值可能与Args
不完全匹配...但我也不明白为什么会出现这种情况,感觉自己可能在某些方面弄错了类型。
如果不必要的话,我可以将对象强制转换为Args
,但我想要避免强制转换(我认为不应该需要强制转换)。
在TypeScript Playground重现中,您可以看到这个错误。
我特别想弄清楚的是为什么类型不正确。我已经使用TypeScript超过3年,通常我很明白为什么类型
英文:
I would like to create a function (to be known as outerFn
) that is given a function (to be known as innerFn
) as an argument. The innerFn
function is given an object type argument, and that object will be required to have a user
parameter. The outerFn
should then return a function (to be known as returnFn
) that should have a similar type as innerFn
but its argument object should not require the user
parameter (but still require all other arguments).
I would like this so that my outerFn
can return a returnFn
that actually calls the innerFn
but injects the user
automatically (i.e. without me having to send the user
)
ex.
type User = { user: { id: number, name: string } };
type Place = { Place: string };
type UserAndPlace = User & Place;
type InnerFn1Resolve = { something: string };
type InnerFn1 = (args: UserAndPlace) => Promise<InnerFn1Resolve>;
const innerFn1: InnerFn1 = async (args) => Promise.resolve({ something: 'yes' });
type ReturnFn1 = (args: Place) => Promise<InnerFn1Resolve>;
const returnFn1: ReturnFn1 = outerFn(innerFn1);
type Job = { job: number };
type UserAndJob = User & Job;
type InnerFn2Resolve = { else: number };
type InnerFn2 = (args: UserAndJob) => Promise<InnerFn2Resolve>;
const innerFn2: InnerFn2 = async (args) => Promise.resolve({ else: 1 });
type ReturnFn2 = (args: Job) => Promise<InnerFn2Resolve>;
const returnFn2: ReturnFn2 = outerFn(innerFn2);
I have tried the following for the outerFn
. The typing is correct for the above...but the outerFn
return complains.
type ArgsWithUser = Record<string, unknown> & User;
type InnerFn<Args extends ArgsWithUser, Response> = (args: Args) => Promise<Response>
type ReturnFn<Args extends ArgsWithUser, Response> = (args: Omit<Args, 'user'>) => Promise<Response>
const outerFn = <Args extends ArgsWithUser, Response>(innerFn: InnerFn<Args, Response>): ReturnFn<Args, Response> => {
const user: User['user'] = {
id: 1,
name: 'Something',
};
return async (argsWithoutUser) => innerFn({ ...argsWithoutUser, user });
};
I get the following error for the parameters of innerFn
(i.e. { ...argsWithoutUser, user }
)
> Argument of type 'Omit<Args, "user"> & { user: User; }' is not assignable to parameter of type 'Args'.
'Omit<Args, "user"> & { user: User; }' is assignable to the constraint of type 'Args', but 'Args' could be instantiated with a different subtype of constraint 'ArgsWithUser'.ts(2345)
I just...really don't understand the error. I get that it's trying to say that the return value might not exactly match Args
...but I also don't see why that would be the case and feel like I must be messing up the types in some way.
I can cast the object as Args
but would like to avoid casting if it isn't necessary (and I don't believe it should be).
Here's a TypeScript Playground recreation where you can see the error.
What I am specifically trying to figure out is why the typings are incorrect. I've been working with TypeScript for 3+ years and think I have a pretty good understanding generally on why typings are wrong...but in this instance, I can't seem to wrap my head around why what I have is wrong.
Any help would be much appreciated.
答案1
得分: 1
以下是您要翻译的内容:
"The error you're getting is technically correct. The compiler cannot be sure that the argument type of innerFn
will actually accept a general User
for its user
property. Here is an example of the situation the compiler is worried about:
const innerFn = async (arg: {
user: { id: number, name: "a" | "b" }
}) => ({ a: 1, b: 2 }[arg.user.name]).toFixed(2);
Here innerFn
requires that its argument be of type { user: { id: number, name: "a" | "b" } }
. This is strictly narrower than User
. If you try to call innerFn()
with an argument of type User
, there's a good chance you'll get a runtime error. If arg.user.name
is neither "a"
nor "b"
, then you will dereference undefined
and get a TypeError
.
But your typings for outerFn()
happily accept innerFn
:
const returnFn = outerFn(innerFn); // no compiler error
The type Args
is inferred as {user: {id: number; name: "a" | "b"}}
, and Omit<Args, "user"> & { user: { id: number; name: string; }; }
is just the same as User
. But that's not what innerFn
needs. That is, the problem is that
// 'Omit<Args, "user"> & { user: { id: number; name: string; }; }' is assignable to the constraint of
// type 'Args', but 'Args' could be instantiated with a different subtype of constraint 'ArgsWithUser'.
And so if you run returnFn()
you will get that runtime error, without a compiler warning:
returnFn({}).catch(e => console.log(e)) // no compiler error, but:
// 💥 (intermediate value)[arg.user.name] is undefined
So that's the problem. This is admittedly unlikely, so you might want to just assert (what you called "cast"ing) and move on.
If instead you want to refactor to avoid this problem, you can do so in a way that doesn't run afoul of this by adding User
to a type instead of omitting it:
type InnerFn<A, R> = (args: A & User) => Promise<R>
type ReturnFn<A, R> = (args: A) => Promise<R>
const outerFn = <A, R>(innerFn: InnerFn<A, R>): ReturnFn<A, R> => {
const user: User['user'] = {
id: 1,
name: 'Something',
};
return async (argsWithoutUser) => innerFn({ ...argsWithoutUser, user });
};
This compiles with no error and still works for the examples you present. This may or may not actually work for your use case, but at least the compiler is happy."
英文:
The error you're getting is technically correct. The compiler cannot be sure that the argument type of innerFn
will actually accept a general User
for its user
property. Here is an example of the situation the compiler is worried about:
const innerFn = async (arg: {
user: { id: number, name: "a" | "b" }
}) => ({ a: 1, b: 2 }[arg.user.name]).toFixed(2);
Here innerFn
requires that its argument be of type { user: { id: number, name: "a" | "b" } }
. This is strictly narrower than User
. If you try to call innerFn()
with an argument of type User
, there's a good chance you'll get a runtime error. If if arg.user.name
is neither "a"
nor "b"
then you will dereference undefined
and get a TypeError
.
But your typings for outerFn()
happily accept innerFn
:
const returnFn = outerFn(innerFn); // no compiler error
The type Args
is inferred as {user: {id: number; name: "a" | "b"}}
, and Omit<Args, "user"> & { user: { id: number; name: string; }; }
is just the same as User
. But that's not what innerFn
needs. That is, the problem is that
// 'Omit<Args, "user"> & { user: { id: number; name: string; }; }' is assignable to the constraint of
// type 'Args', but 'Args' could be instantiated with a different subtype of constraint 'ArgsWithUser'.
And so if you run returnFn()
you will get that runtime error, without a compiler warning:
returnFn({}).catch(e => console.log(e)) // no compiler error, but:
// 💥 (intermediate value)[arg.user.name] is undefined
So that's the problem. This is admittedly unlikely so you might want to just assert (what you called "cast"ing) and move on.
If instead you want to refactor to avoid this problem, you can do so in a way that doesn't run afoul of this by adding User
to a type instead of omitting it:
type InnerFn<A, R> = (args: A & User) => Promise<R>
type ReturnFn<A, R> = (args: A) => Promise<R>
const outerFn = <A, R>(innerFn: InnerFn<A, R>): ReturnFn<A, R> => {
const user: User['user'] = {
id: 1,
name: 'Something',
};
return async (argsWithoutUser) => innerFn({ ...argsWithoutUser, user });
};
This compiles with no error, and still works for the examples you present. This may or may not actually work for your use case, but at least the compiler is happy.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论